You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
239 lines
9.3 KiB
239 lines
9.3 KiB
package locator_test
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"strings"
|
|
"testing"
|
|
|
|
"g.activething.com/go/DateKeys/datekey"
|
|
"g.activething.com/go/DateKeys/extension"
|
|
"g.activething.com/go/DateKeys/internal/testkit"
|
|
"g.activething.com/go/DateKeys/locator"
|
|
"g.activething.com/go/DateKeys/profile"
|
|
)
|
|
|
|
const cid = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"
|
|
|
|
func sample(t *testing.T) (*locator.Locator, []byte, []byte) {
|
|
t.Helper()
|
|
dkc := make([]byte, 5000)
|
|
rand.Read(dkc)
|
|
loc, rest, err := locator.NewEnvelope(dkc)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
loc.Addresses = []locator.Address{{URI: "https://ejemplo.org/foto.jpg", Offset: 123456}, {URI: "ipfs://" + cid}}
|
|
return loc, rest, dkc
|
|
}
|
|
|
|
// Spec v0.11 §44.1: the envelope is the .dkc in age, split in a header that
|
|
// the locator carries and a rest without a mark.
|
|
func TestEnvelope(t *testing.T) {
|
|
loc, rest, dkc := sample(t)
|
|
if bytes.Contains(rest, []byte("age-encryption")) || len(rest) != int(loc.RestSize) || len(loc.EnvelopeHeader) > locator.MaxHeaderLen || !bytes.HasSuffix(loc.EnvelopeHeader, []byte("\n")) {
|
|
t.Fatalf("rest of %d bytes, header %q", len(rest), loc.EnvelopeHeader)
|
|
}
|
|
got, err := loc.OpenEnvelope(rest)
|
|
if err != nil || !bytes.Equal(got, dkc) {
|
|
t.Fatalf("the envelope: %v", err)
|
|
}
|
|
// Inside another file: the rest is read from its offset, and only that.
|
|
host := bytes.Repeat([]byte("JPEG"), 1000)
|
|
file, offset := locator.Hide(host, rest)
|
|
trailing := append(bytes.Clone(file), []byte("more after")...)
|
|
for _, f := range [][]byte{file, trailing} {
|
|
r, err := loc.RestIn(f, offset)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, err := loc.OpenEnvelope(r); err != nil || !bytes.Equal(got, dkc) {
|
|
t.Errorf("rest in a host: %v", err)
|
|
}
|
|
}
|
|
if _, err := loc.RestIn(host, uint64(len(host))); err == nil {
|
|
t.Error("a rest beyond the end of the resource")
|
|
}
|
|
// Whoever recompresses the host breaks the rest, and the locator says so.
|
|
bad := bytes.Clone(rest)
|
|
bad[len(bad)/2] ^= 1
|
|
if _, err := loc.OpenEnvelope(bad); err == nil || !strings.Contains(err.Error(), "SHA-256 of the rest") {
|
|
t.Errorf("a rest changed: %v", err)
|
|
}
|
|
if _, err := loc.OpenEnvelope(rest[:len(rest)-1]); err == nil {
|
|
t.Error("a short rest")
|
|
}
|
|
wrong := *loc
|
|
wrong.CapsuleDigest[0] ^= 1
|
|
if _, err := wrong.OpenEnvelope(rest); err == nil || !strings.Contains(err.Error(), "capsule_digest") {
|
|
t.Errorf("another capsule_digest: %v", err)
|
|
}
|
|
}
|
|
|
|
// Spec §44.1: the plaintext of the locator measures 4096 bytes, or the least
|
|
// multiple that holds it, so its length does not tell the addresses.
|
|
func TestLocatorPlaintext(t *testing.T) {
|
|
loc, _, _ := sample(t)
|
|
one := *loc
|
|
one.Addresses = loc.Addresses[:1]
|
|
for _, l := range []*locator.Locator{&one, loc} {
|
|
b, err := l.Marshal()
|
|
if err != nil || len(b) != locator.Block {
|
|
t.Fatalf("%d bytes, %v", len(b), err)
|
|
}
|
|
back, err := locator.Unmarshal(b)
|
|
if err != nil || len(back.Addresses) != len(l.Addresses) || back.Addresses[0] != l.Addresses[0] || back.EnvelopeKey != l.EnvelopeKey ||
|
|
!bytes.Equal(back.EnvelopeHeader, l.EnvelopeHeader) || back.RestDigest != l.RestDigest || back.RestSize != l.RestSize || back.CapsuleDigest != l.CapsuleDigest {
|
|
t.Fatalf("the round trip: %v", err)
|
|
}
|
|
}
|
|
// Eight long addresses make it larger: the least multiple of 4096.
|
|
big := *loc
|
|
big.Addresses = nil
|
|
for range locator.MaxAddresses {
|
|
big.Addresses = append(big.Addresses, locator.Address{URI: "https://ejemplo.org/" + strings.Repeat("a", 900), Offset: 1 << 40})
|
|
}
|
|
b, err := big.Marshal()
|
|
if err != nil || len(b)%locator.Block != 0 || len(b) < 2*locator.Block {
|
|
t.Fatalf("%d bytes, %v", len(b), err)
|
|
}
|
|
if _, err := locator.Unmarshal(b); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Anything other than that length is not canonical.
|
|
b1, _ := one.Marshal()
|
|
if _, err := locator.Unmarshal(append(bytes.Clone(b1), make([]byte, locator.Block)...)); err == nil {
|
|
t.Error("a plaintext of two blocks for one")
|
|
}
|
|
if _, err := locator.Unmarshal(b1[:len(b1)-1]); err == nil {
|
|
t.Error("a truncated plaintext")
|
|
}
|
|
nonzero := bytes.Clone(b1)
|
|
nonzero[len(nonzero)-1] = 1
|
|
if _, err := locator.Unmarshal(nonzero); err == nil {
|
|
t.Error("padding that is not zeros")
|
|
}
|
|
}
|
|
|
|
func TestAddresses(t *testing.T) {
|
|
for _, ok := range []string{"https://ejemplo.org/a.bin", "https://ejemplo.org:8443/x?y=1", "ipfs://" + cid, "ipfs://" + cid + "/ruta",
|
|
"https://1.1.1.1/a", "https://[2606:4700::1111]/a", "https://ejemplo.org/a%20b", "https://ejemplo.org/~ana/(1),x;y=z"} {
|
|
if err := locator.CheckURI(ok); err != nil {
|
|
t.Errorf("%s: %v", ok, err)
|
|
}
|
|
}
|
|
for _, bad := range []string{"", "http://ejemplo.org/a", "file:///etc/passwd", "ftp://x/y", "https://user:pass@ejemplo.org/", "https://", "ipfs://notacid",
|
|
"https://ejemplo.org/ñ", "https://ejemplo.org/a b", "ipfs://Qm" + strings.Repeat("a", 44), "https://" + strings.Repeat("a", 1100),
|
|
// Review: addresses that are not public, and names that only a machine
|
|
// or a local network resolves.
|
|
"https://100.64.0.1/", "https://0.1.2.3/", "https://192.0.0.8/", "https://198.18.0.1/", "https://240.0.0.1/", "https://255.255.255.255/",
|
|
"https://[64:ff9b::7f00:1]/", "https://[64:ff9b::a9fe:a9fe]/", "https://[::127.0.0.1]/", "https://[::ffff:127.0.0.1]/",
|
|
"https://[2002:7f00:1::1]/", "https://[2001::1]/", "https://[2001:db8::1]/", "https://[fec0::1]/", "https://[fc00::1]/", "https://[ff02::1]/",
|
|
"https://localhost/", "https://foo.localhost/", "https://intranet/", "https://a.local/", "https://router.home.arpa/", "https://x.internal/",
|
|
// Characters that RFC 3986 does not allow, a broken percent sign and a
|
|
// "." or ".." segment.
|
|
"https://a.org/%zz", "https://a.org/%", "https://a.org/<script>", "https://a.org/{x}", "https://a.org/a|b", "https://a.org/^", "https://a.org/`",
|
|
"ipfs://" + cid + "/../../ipns/x", "ipfs://" + cid + "/%2e%2e/x", "https://a.org/./x",
|
|
// Base32 that is not a CID v1: no version 1, or a multihash cut.
|
|
"ipfs://b" + strings.Repeat("a", 39), "ipfs://" + cid[:len(cid)-2]} {
|
|
if err := locator.CheckURI(bad); err == nil {
|
|
t.Errorf("%q accepted", bad)
|
|
}
|
|
}
|
|
if h := (locator.Address{URI: "https://ejemplo.org:8443/x"}).Host(); h != "ejemplo.org" {
|
|
t.Errorf("host %q", h)
|
|
}
|
|
if h := (locator.Address{URI: "ipfs://" + cid}).Host(); h != cid {
|
|
t.Errorf("cid %q", h)
|
|
}
|
|
loc, _, _ := sample(t)
|
|
loc.Addresses = nil
|
|
if _, err := loc.Marshal(); err == nil {
|
|
t.Error("no addresses")
|
|
}
|
|
loc.Addresses = make([]locator.Address, 9)
|
|
if _, err := loc.Marshal(); err == nil {
|
|
t.Error("nine addresses")
|
|
}
|
|
}
|
|
|
|
// Spec §44.1: the locator is an age file with one tlock stanza for the round
|
|
// of the DateKey: it opens with that release and with no other.
|
|
func TestSealedLocator(t *testing.T) {
|
|
p := profile.Quicknet()
|
|
loc, _, _ := sample(t)
|
|
sealed, err := locator.Seal(p, 1000, loc)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if bytes.Contains(sealed, []byte(cid)) || bytes.Contains(sealed, loc.EnvelopeKey[:]) {
|
|
t.Error("the locator is not hidden")
|
|
}
|
|
back, err := locator.Open(p, 1000, testkit.Release(1000), sealed)
|
|
if err != nil || back.EnvelopeKey != loc.EnvelopeKey || back.Addresses[1].URI != loc.Addresses[1].URI {
|
|
t.Fatalf("Open: %v", err)
|
|
}
|
|
// Another round: unusable.
|
|
if _, err := locator.Open(p, 1001, testkit.Release(1001), sealed); err == nil {
|
|
t.Error("a locator for round 1000 opened with round 1001")
|
|
}
|
|
if _, err := locator.Open(p, 1000, testkit.Release(1001), sealed); err == nil {
|
|
t.Error("a locator opened with another release")
|
|
}
|
|
}
|
|
|
|
func TestInfo(t *testing.T) {
|
|
p := profile.Quicknet()
|
|
dk, err := datekey.Resolve(p, testkit.Genesis().AddDate(0, 0, 400))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
loc, _, _ := sample(t)
|
|
sealed, err := locator.Seal(p, dk.Round, loc)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
other, err := locator.Seal(p, dk.Round+1, loc)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Review: the locator is an age file with one tlock stanza for the round
|
|
// of the DateKey; anything else is refused by the writer, and unusable.
|
|
for name, s := range map[string][]byte{"not an age file": []byte("an age file"), "another round": other} {
|
|
if _, err := (&locator.Info{DateKey: dk, Sealed: s}).Extension(); err == nil {
|
|
t.Errorf("%s: written", name)
|
|
}
|
|
}
|
|
for _, in := range []*locator.Info{
|
|
{DateKey: dk},
|
|
{Note: "Cartas del viaje a Lisboa", DateKey: dk},
|
|
{Note: "Con localizador", DateKey: dk, Sealed: sealed},
|
|
} {
|
|
x, err := in.Extension()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out, err := locator.ParseInfo(x)
|
|
if err != nil || out.Note != in.Note || out.DateKey != in.DateKey || !bytes.Equal(out.Sealed, in.Sealed) {
|
|
t.Errorf("%+v: %v", in, err)
|
|
}
|
|
}
|
|
// What makes the extension unusable.
|
|
good, _ := (&locator.Info{Note: "n", DateKey: dk}).Extension()
|
|
for name, x := range map[string]extension.Extension{
|
|
"another id": {ID: extension.NoteID, Version: 1, Data: good.Data},
|
|
"version 2": {ID: extension.CapsuleID, Version: 2, Data: good.Data},
|
|
"no data": {ID: extension.CapsuleID, Version: 1},
|
|
"not CBOR": {ID: extension.CapsuleID, Version: 1, Data: []byte("nope")},
|
|
"a bad DateKey": {ID: extension.CapsuleID, Version: 1, Data: []byte{0xa1, 0x01, 0x61, 0x6e}},
|
|
"a note with tab": {ID: extension.CapsuleID, Version: 1, Data: []byte{0xa2, 0x00, 0x63, 'a', '\t', 'b', 0x01, 0x60}},
|
|
} {
|
|
if _, err := locator.ParseInfo(x); err == nil {
|
|
t.Errorf("%s: accepted", name)
|
|
}
|
|
}
|
|
if _, err := (&locator.Info{Note: "a\nb", DateKey: dk}).Extension(); err == nil {
|
|
t.Error("a note with a line feed")
|
|
}
|
|
}
|