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", "https://" + strings.Repeat("a", 63) + ".org/", "https://a--b.ejemplo.org/", "https://Ejemplo.ORG:443/"} { 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/