Spec v0.13 draft: the address of a name on a network with NAT64

On an IPv6-only network with DNS64, a name that has only IPv4 addresses
resolves to an address of NAT64, which spec v0.12, section 44.1, always
rejected: a reader on such a network, as many mobile ones, could not
download the rest of an envelope. The draft v0.13 counts an address of
64:ff9b::/96, or of the NAT64 prefix of the network, by the IPv4 address it
holds (RFC 6052). An address of NAT64 written in a locator is still
rejected. Section 76 records the change with its case.

locator.CheckResolvedIP implements it for the readers that download, and
testdata/vectors/resolved_ip.json gives 42 cases. SpecVersion stays 0.12
until the author approves the draft.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.13
dev 1 day ago
parent fe405e2348
commit a83b44d1c8

@ -3,6 +3,27 @@
All notable changes to this module are documented here. The project follows
semantic versioning; `v0.x` versions make no API stability promise.
## Unreleased — specification v0.13 draft
Implements the draft v0.13 of the DateKeys Protocol Specification, on the
branch `v0.13` and not approved yet. It changes no format and no verdict.
`SpecVersion` stays 0.12 until the author approves the draft.
- **NAT64** (spec v0.13, §44.1, §76 change 1). `locator.CheckResolvedIP`
checks the IP address that the name of an https address of a locator
resolves to, which a reader checks on every connection: a public address,
or, on an IPv6-only network with DNS64, an address of NAT64 (RFC 6052) of
the well-known prefix `64:ff9b::/96` or of the NAT64 prefix of the network,
whose IPv4 address inside is public. The prefix of the network has one of
the lengths of RFC 6052 and lies in `64:ff9b::/16` or is public; an address
in it counts only by its IPv4 address, even when the prefix is public. An
address of NAT64 written in a locator is still rejected. This module
downloads nothing: the function is for the readers that do, as the
application.
- **Test data.** `vectors/resolved_ip.json`, new: 42 addresses, with the
prefix of the network or none, and the result of `CheckResolvedIP`, with
its text. The other files do not change.
## Unreleased — specification v0.12
Implements the DateKeys Protocol Specification v0.12, which its author

@ -2,7 +2,10 @@
Implementación de referencia en Go de la **DateKeys Protocol Specification
v0.12** ([`spec/`](spec/DateKeys_Protocol_Specification_v0.12.md)), etiquetada
`spec-v0.12`, que no cambia ningún formato de la v0.11.
`spec-v0.12`, que no cambia ningún formato de la v0.11. Esta rama, `v0.13`,
implementa además el borrador v0.13
([`spec/`](spec/DateKeys_Protocol_Specification_v0.13.md)), aún sin aprobar,
que no cambia ningún formato.
[English version](README.md).
DateKeys cifra datos de forma que solo puedan abrirse a partir de un instante

@ -2,7 +2,10 @@
Reference implementation in Go of the **DateKeys Protocol Specification
v0.12** ([`spec/`](spec/DateKeys_Protocol_Specification_v0.12.md)), tagged
`spec-v0.12`, which changes no format of v0.11.
`spec-v0.12`, which changes no format of v0.11. This branch, `v0.13`, also
implements the draft v0.13
([`spec/`](spec/DateKeys_Protocol_Specification_v0.13.md)), not approved yet,
which changes no format.
[Versión en español](README.es.md).
DateKeys encrypts data so that it can only be opened after a chosen instant.

@ -7,8 +7,9 @@ reviewer together with the specification, the fixtures and the mutation corpus
(plan §10).
Paths are relative to the repository root. `§` numbers refer to
`spec/DateKeys_Protocol_Specification_v0.12.md`, tagged `spec-v0.12`;
v0.11, tagged `spec-v0.11`, numbers its sections the same. A case of §64 that is not in the repository yet is marked
`spec/DateKeys_Protocol_Specification_v0.13.md`, the draft of the branch
`v0.13`, not approved yet; v0.12, tagged `spec-v0.12`, and v0.11, tagged
`spec-v0.11`, number their sections the same. A case of §64 that is not in the repository yet is marked
*pending*.
## Section map
@ -74,7 +75,7 @@ v0.11, tagged `spec-v0.11`, numbers its sections the same. A case of §64 that i
| 42 | `credential_id` | `capsule.Encrypt` (16 bytes from `crypto/rand`) | `capsule.TestPortableKeysAreNeverReused` |
| 43 | `verification_metadata` | `accesskey.Verification`, `decodeVerification` (the closed map `{0: capsule_digest}`); `capsule.Open` (`checkCapsuleDigest`, seekable readers) | `accesskey.TestDecodeRejects` *empty verification map*, `TestDecodeBodyStructure`; mutation *capsule_digest of the .dkk does not match* |
| 44 | Application extensions in `.dkk` | `AccessKey.Critical/Noncritical`; `capsule.Open` (`checkAccessKey`, `Opened.UnusableAccessKeyExtensions`) | `accesskey.TestEncodeRejectsAbsenceAsEmptyMap`, `TestDecodeBodyExtensionRules`, `TestFixtureWithExtension`; `capsule.TestAccessKeyFixtureWithExtension`; mutation *known critical .dkk extension with invalid data* |
| 44.1 | The extension `datekeys.capsule` of a `.dkk`, noncritical: the note, the DateKey and an optional locator, an age file with one tlock stanza for the round of that DateKey, unusable for another round or chain; its plaintext, 1 to 8 addresses, `I_SOBRE`, the header of the envelope, the digest and the size of the rest, `capsule_digest` and a zero padding of at least one byte, of exactly 4096 bytes or the least multiple that holds it; the envelope, the `.dkc` in age split into the header and a rest without a mark, alone or inside a host at an offset; the addresses, ASCII of RFC 3986, read without decoding, the scheme in lower case: `https` with a host of labels of 1 to 63 letters, digits and hyphens that neither start nor end with a hyphen, or a public IP outside the special-purpose blocks of IANA, an IPv4 without leading zeros, a port of 1 to 65535 without leading zeros, no local name in either case, no dot segment, or `ipfs` with a CID v1 of at most 128 characters in canonical base32; a reader rejects each address that breaks them and uses the others; the rest and the `.dkc` checked by their digests; a writer never writes a rejected address and decodes what it writes | `locator` (`Info`, `Info.Extension`, `ParseInfo`, `Info.OpenLocator`, `Standard`; `Locator`, `Locator.Marshal`, `Unmarshal`, `Locator.Usable`, `PlaintextLength`, `Block`, `MaxAddresses`, `MaxURILen`, `MaxHeaderLen`; `Seal`, `Open`; `NewEnvelope`, `Locator.OpenEnvelope`, `Hide`, `Locator.RestIn`; `Address`, `Address.Host`, `CheckURI`), which downloads nothing; `extension.CapsuleID`, `extension.Standard` (`ValidateCapsule`); `accesskey.AccessKey.MarshalBody` (`extension.CheckWrite`); `spec/datekeys.cddl` (`capsule-locator`, `capsule-address`) | `locator.TestEnvelope`, `TestLocatorPlaintext`, `TestAddresses` (the blocks of IANA, NAT64, mapped and 6to4 addresses, local names, characters outside RFC 3986, dot segments, CIDs that do not decode), `TestSealedLocator` (another round or release: unusable), `TestInfo`, `TestUsableAddresses`, `TestLeastMultiple`, `TestPaddingBoundaries`, `TestLocatorVectors` (`testdata/vectors/locator.json`); `capsule.TestRegisteredExtensionsWhereRegistered` (never in a capsule); the cases of §64 of v0.11 and v0.12 for `datekeys.capsule` in `locator.json`: the addresses, a locator with a rejected address and a usable one, the resources of the rest, the data of the extension and the plaintexts of the locator |
| 44.1 | The extension `datekeys.capsule` of a `.dkk`, noncritical: the note, the DateKey and an optional locator, an age file with one tlock stanza for the round of that DateKey, unusable for another round or chain; its plaintext, 1 to 8 addresses, `I_SOBRE`, the header of the envelope, the digest and the size of the rest, `capsule_digest` and a zero padding of at least one byte, of exactly 4096 bytes or the least multiple that holds it; the envelope, the `.dkc` in age split into the header and a rest without a mark, alone or inside a host at an offset; the addresses, ASCII of RFC 3986, read without decoding, the scheme in lower case: `https` with a host of labels of 1 to 63 letters, digits and hyphens that neither start nor end with a hyphen, or a public IP outside the special-purpose blocks of IANA, an IPv4 without leading zeros, a port of 1 to 65535 without leading zeros, no local name in either case, no dot segment, or `ipfs` with a CID v1 of at most 128 characters in canonical base32; a reader rejects each address that breaks them and uses the others; the rest and the `.dkc` checked by their digests; a writer never writes a rejected address and decodes what it writes | `locator` (`Info`, `Info.Extension`, `ParseInfo`, `Info.OpenLocator`, `Standard`; `Locator`, `Locator.Marshal`, `Unmarshal`, `Locator.Usable`, `PlaintextLength`, `Block`, `MaxAddresses`, `MaxURILen`, `MaxHeaderLen`; `Seal`, `Open`; `NewEnvelope`, `Locator.OpenEnvelope`, `Hide`, `Locator.RestIn`; `Address`, `Address.Host`, `CheckURI`; `CheckResolvedIP`, the address a name resolves to, NAT64 included, of the draft v0.13), which downloads nothing; `extension.CapsuleID`, `extension.Standard` (`ValidateCapsule`); `accesskey.AccessKey.MarshalBody` (`extension.CheckWrite`); `spec/datekeys.cddl` (`capsule-locator`, `capsule-address`) | `locator.TestEnvelope`, `TestLocatorPlaintext`, `TestAddresses` (the blocks of IANA, NAT64, mapped and 6to4 addresses, local names, characters outside RFC 3986, dot segments, CIDs that do not decode), `TestSealedLocator` (another round or release: unusable), `TestInfo`, `TestUsableAddresses`, `TestLeastMultiple`, `TestPaddingBoundaries`, `TestLocatorVectors` (`testdata/vectors/locator.json`); `TestResolvedIPVectors` (`testdata/vectors/resolved_ip.json`, `internal/testkit.ResolvedIPVectors`), `TestResolvedIPWellKnownPrefix`; `capsule.TestRegisteredExtensionsWhereRegistered` (never in a capsule); the cases of §64 of v0.11 and v0.12 for `datekeys.capsule` in `locator.json`: the addresses, a locator with a rejected address and a usable one, the resources of the rest, the data of the extension and the plaintexts of the locator |
| 45 | Release API | `provider.ReleaseSource` interface only (server out of scope, plan §2) | — |
| 46 | Release Queue | out of scope (server) | — |
| 47 | Release Cache | every release is verified again: `capsule.Open` step 10 and `agewrap.TimeIdentity` | mutations *release of another round* |

@ -191,7 +191,11 @@ func format3Vectors(dir string) error {
if err != nil {
return err
}
for name, v := range map[string]any{"paths.json": paths, "path_fold.json": fold, "head_schema.json": heads, "security.json": security, "ed25519_strict.json": strict, "note.json": note, "wordkey.json": words} {
resolved, err := testkit.ResolvedIPVectors()
if err != nil {
return err
}
for name, v := range map[string]any{"paths.json": paths, "path_fold.json": fold, "head_schema.json": heads, "security.json": security, "ed25519_strict.json": strict, "note.json": note, "wordkey.json": words, "resolved_ip.json": resolved} {
if err := testkit.WriteJSON(filepath.Join(dir, name), v); err != nil {
return err
}

@ -0,0 +1,109 @@
package testkit
import (
"errors"
"fmt"
"net/netip"
"g.activething.com/go/DateKeys/locator"
)
// ResolvedIPVectorFile is testdata/vectors/resolved_ip.json: the IP address
// that the name of an https address of a locator resolved to, the NAT64
// prefix of the network, if the reader knows one, and whether the reader
// may connect (spec v0.13, §44.1).
type ResolvedIPVectorFile struct {
Spec string `json:"spec"`
Description string `json:"description"`
Cases []ResolvedIPVector `json:"cases"`
}
// ResolvedIPVector is an address, a NAT64 prefix or "" for none, and the
// result of locator.CheckResolvedIP: ResultOK, or "error" with its text.
type ResolvedIPVector struct {
Name string `json:"name"`
IP string `json:"ip"`
NAT64 string `json:"nat64"`
Result string `json:"result"`
Error string `json:"error,omitempty"`
}
// ResolvedIPVectors computes testdata/vectors/resolved_ip.json, and fails if
// a case does not get the result it is written for.
func ResolvedIPVectors() (ResolvedIPVectorFile, error) {
f := ResolvedIPVectorFile{
Spec: SpecVersion,
Description: "The IP address that the name of an https address of a locator resolves to, and whether a reader may connect (spec v0.13, 44.1): " +
"a public address, or an address of NAT64 (RFC 6052) of 64:ff9b::/96 or of the NAT64 prefix of the network, whose IPv4 address inside is public. See testdata/README.md.",
}
const bad = "error"
type tc struct{ name, ip, nat64, want string }
cases := []tc{
{"a public IPv4 address", "203.0.114.5", "", ResultOK},
{"a public IPv6 address", "2a01:4f8::1", "", ResultOK},
{"a private IPv4 address", "192.168.1.10", "", bad},
{"loopback", "127.0.0.1", "", bad},
{"IPv6 loopback", "::1", "", bad},
{"an IPv6 link-local address", "fe80::1", "", bad},
{"an IPv6 unique local address", "fd00::1", "", bad},
{"an IPv4-mapped address of a public IPv4 address", "::ffff:203.0.114.5", "", bad},
{"6to4", "2002:cb00:7205::1", "", bad},
{"Teredo", "2001:0:cb00:7205::1", "", bad},
{"the well-known prefix with a public IPv4 address", "64:ff9b::cb00:7205", "", ResultOK},
{"the well-known prefix with 8.8.8.8", "64:ff9b::808:808", "", ResultOK},
{"the well-known prefix with an IPv4 address of 10.0.0.0/8", "64:ff9b::a00:1", "", bad},
{"the well-known prefix with 192.168.1.10", "64:ff9b::c0a8:10a", "", bad},
{"the well-known prefix with loopback", "64:ff9b::7f00:1", "", bad},
{"the well-known prefix with 169.254.169.254", "64:ff9b::a9fe:a9fe", "", bad},
{"the well-known prefix with 100.64.0.1", "64:ff9b::6440:1", "", bad},
{"the well-known prefix with 0.0.0.0", "64:ff9b::", "", bad},
{"the well-known prefix with 255.255.255.255", "64:ff9b::ffff:ffff", "", bad},
{"the well-known prefix with a documentation address", "64:ff9b::cb00:7105", "", bad},
{"an address of 64:ff9b::/16 outside 64:ff9b::/96", "64:ff9b::1:cb00:7205", "", bad},
{"the local-use prefix of RFC 8215 without the prefix of the network", "64:ff9b:1::cb00:7205", "", bad},
{"the well-known prefix, given as the prefix of the network", "64:ff9b::cb00:7205", "64:ff9b::/96", ResultOK},
{"the well-known prefix with another prefix of the network", "64:ff9b::cb00:7205", "64:ff9b:1::/48", ResultOK},
{"the local-use prefix of RFC 8215, /48", "64:ff9b:1:cb00:72:500::", "64:ff9b:1::/48", ResultOK},
{"the local-use prefix, /48, with a private IPv4 address", "64:ff9b:1:c0a8:1:a00::", "64:ff9b:1::/48", bad},
{"the local-use prefix, /48, with bits 64 to 71 set", "64:ff9b:1:cb00:172:500::", "64:ff9b:1::/48", bad},
{"a public prefix of the network, /96", "2a01:4f8:c0:64::cb00:7205", "2a01:4f8:c0:64::/96", ResultOK},
{"a public prefix of the network, /96, with a private IPv4 address", "2a01:4f8:c0:64::c0a8:10a", "2a01:4f8:c0:64::/96", bad},
{"a public prefix of the network, /96, with loopback", "2a01:4f8:c0:64::7f00:1", "2a01:4f8:c0:64::/96", bad},
{"a public prefix of the network, /32", "2a01:4f8:cb00:7205::", "2a01:4f8::/32", ResultOK},
{"a public prefix of the network, /40", "2a01:4f8:c0cb:72:5::", "2a01:4f8:c000::/40", ResultOK},
{"a public prefix of the network, /56", "2a01:4f8:c0:64cb:0:7205::", "2a01:4f8:c0:6400::/56", ResultOK},
{"a public prefix of the network, /64", "2a01:4f8:c0:64:cb:72:500:0", "2a01:4f8:c0:64::/64", ResultOK},
{"a public prefix of the network, /64, with a private IPv4 address", "2a01:4f8:c0:64:c0:a801:a00:0", "2a01:4f8:c0:64::/64", bad},
{"a public address outside the prefix of the network", "2a01:4f8::1", "64:ff9b:1::/48", ResultOK},
{"a prefix of the network of 80 bits", "64:ff9b:1::cb00:7205", "64:ff9b:1::/80", bad},
{"a link-local prefix of the network", "fe80::cb00:7205", "fe80::/96", bad},
{"a unique local prefix of the network", "fd00::cb00:7205", "fd00::/96", bad},
{"a prefix of the network of 2001:db8::/32", "2001:db8::cb00:7205", "2001:db8::/96", bad},
{"a prefix of the network with bits after its length", "64:ff9b::cb00:7205", "64:ff9b::1/96", bad},
{"an IPv4 prefix of the network", "203.0.114.5", "203.0.114.0/24", bad},
}
var errs []error
for _, c := range cases {
ip, err := netip.ParseAddr(c.ip)
if err != nil {
return f, fmt.Errorf("%s: %w", c.name, err)
}
var p netip.Prefix
if c.nat64 != "" {
// ParsePrefix keeps the bits after the length, which the check
// refuses.
if p, err = netip.ParsePrefix(c.nat64); err != nil {
return f, fmt.Errorf("%s: %w", c.name, err)
}
}
v := ResolvedIPVector{Name: c.name, IP: c.ip, NAT64: c.nat64, Result: ResultOK}
if err := locator.CheckResolvedIP(ip, p); err != nil {
v.Result, v.Error = bad, err.Error()
}
if v.Result != c.want {
errs = append(errs, fmt.Errorf("resolved ip %q: %s %s, want %s", c.name, v.Result, v.Error, c.want))
}
f.Cases = append(f.Cases, v)
}
return f, errors.Join(errs...)
}

@ -271,6 +271,10 @@ func TestVectorFilesAreCurrent(t *testing.T) {
if err != nil {
t.Fatal(err)
}
resolved, err := testkit.ResolvedIPVectors()
if err != nil {
t.Fatal(err)
}
for _, v := range []struct {
file string
want any
@ -285,6 +289,7 @@ func TestVectorFilesAreCurrent(t *testing.T) {
{"ed25519_strict.json", strict, &testkit.Ed25519StrictFile{}},
{"note.json", note, &testkit.NoteVectorFile{}},
{"wordkey.json", words, &testkit.WordKeyVectorFile{}},
{"resolved_ip.json", resolved, &testkit.ResolvedIPVectorFile{}},
} {
if err := testkit.ReadJSON("../../testdata/vectors/"+v.file, v.got); err != nil {
t.Fatal(err)

@ -0,0 +1,103 @@
package locator
import (
"fmt"
"net/netip"
)
// The prefixes of NAT64 (RFC 6052): the well-known prefix, which every
// reader accepts, and the block where the prefix of a network may also be,
// besides a public IPv6 one (spec v0.13, §44.1).
var (
nat64WellKnown = netip.MustParsePrefix("64:ff9b::/96")
nat64Block = netip.MustParsePrefix("64:ff9b::/16")
)
// CheckResolvedIP reports why a reader must not connect to ip, the address
// that the name of an https address resolved to (spec v0.13, §44.1). A
// reader checks it on every connection, redirections included; this package
// downloads nothing.
//
// ip must be public, as an IP address written in a locator must be. On an
// IPv6-only network with DNS64 and NAT64, a name that has only IPv4
// addresses resolves to an IPv6 address that holds one (RFC 6052): one of
// the well-known prefix 64:ff9b::/96 is public when the IPv4 address in its
// last 32 bits is. nat64 is the NAT64 prefix of the network, which the
// reader discovers with RFC 7050 or its system gives, or the zero Prefix for
// none: an address in it is public only when the IPv4 address it holds, at
// the positions of RFC 6052, is, even when the prefix is a public one. nat64
// must have one of the lengths of RFC 6052 and lie in 64:ff9b::/16 or be a
// public IPv6 prefix.
//
// An IPv4-mapped address is checked as the IPv6 address it is, and is not
// public: a reader passes an IPv4 address as its 4 bytes.
func CheckResolvedIP(ip netip.Addr, nat64 netip.Prefix) error {
if !ip.IsValid() {
return fmt.Errorf("locator: no IP address")
}
if nat64.IsValid() {
if err := checkNAT64Prefix(nat64); err != nil {
return err
}
}
// The prefixes of NAT64 decide first: the prefix of a network may be a
// public one, and an address in it reaches the IPv4 address it holds,
// which may be private.
for _, p := range []netip.Prefix{nat64WellKnown, nat64} {
if !p.IsValid() || !p.Contains(ip) {
continue
}
v4, ok := nat64IPv4(ip, p.Bits())
switch {
case !ok:
return fmt.Errorf("locator: an https address whose name resolves to %s, an address of the NAT64 prefix %s whose bits 64 to 71 are not zero", ip, p)
case !publicIP(v4):
return fmt.Errorf("locator: an https address whose name resolves to %s, an address of NAT64 that holds %s, an IP address that is not public", ip, v4)
}
return nil
}
if publicIP(ip) {
return nil
}
return fmt.Errorf("locator: an https address whose name resolves to %s, an IP address that is not public", ip)
}
// checkNAT64Prefix reports why p cannot be the NAT64 prefix of a network
// (RFC 6052, spec v0.13, §44.1).
func checkNAT64Prefix(p netip.Prefix) error {
switch {
case !p.Addr().Is6() || p.Addr().Is4In6():
return fmt.Errorf("locator: the NAT64 prefix %s is not an IPv6 prefix", p)
case p.Masked() != p:
return fmt.Errorf("locator: the NAT64 prefix %s has bits set after its length", p)
}
switch p.Bits() {
case 32, 40, 48, 56, 64, 96:
default:
return fmt.Errorf("locator: the NAT64 prefix %s is not of 32, 40, 48, 56, 64 or 96 bits (RFC 6052)", p)
}
if !nat64Block.Contains(p.Addr()) && !publicIP(p.Addr()) {
return fmt.Errorf("locator: the NAT64 prefix %s is neither in 64:ff9b::/16 nor a public IPv6 prefix", p)
}
return nil
}
// nat64IPv4 extracts the IPv4 address that ip, an address of a NAT64 prefix
// of bits bits, holds, at the positions of RFC 6052, section 2.2: the 32
// bits after the prefix, skipping bits 64 to 71, which must be zero.
func nat64IPv4(ip netip.Addr, bits int) (netip.Addr, bool) {
b := ip.As16()
if bits < 96 && b[8] != 0 {
return netip.Addr{}, false
}
var v4 [4]byte
n := 0
for i := bits / 8; n < 4; i++ {
if i == 8 {
continue
}
v4[n] = b[i]
n++
}
return netip.AddrFrom4(v4), true
}

@ -0,0 +1,66 @@
package locator_test
import (
"net/netip"
"path/filepath"
"testing"
"g.activething.com/go/DateKeys/internal/testkit"
"g.activething.com/go/DateKeys/locator"
)
// testdata/vectors/resolved_ip.json: the address a name resolves to, with
// the NAT64 prefix of the network or none, and whether a reader may connect
// (spec v0.13, §44.1).
func TestResolvedIPVectors(t *testing.T) {
var f testkit.ResolvedIPVectorFile
if err := testkit.ReadJSON(filepath.Join("..", "testdata", "vectors", "resolved_ip.json"), &f); err != nil {
t.Fatal(err)
}
if len(f.Cases) < 40 {
t.Fatalf("%d cases", len(f.Cases))
}
for _, c := range f.Cases {
ip := netip.MustParseAddr(c.IP)
var p netip.Prefix
if c.NAT64 != "" {
p = netip.MustParsePrefix(c.NAT64)
}
err := locator.CheckResolvedIP(ip, p)
switch {
case c.Result == testkit.ResultOK && err != nil:
t.Errorf("%s: %v", c.Name, err)
case c.Result != testkit.ResultOK && (err == nil || err.Error() != c.Error):
t.Errorf("%s: %v, want %s", c.Name, err, c.Error)
}
}
}
// An address of NAT64 is public exactly when the IPv4 address it holds is,
// for every IPv4 address of the special-purpose blocks and their edges.
func TestResolvedIPWellKnownPrefix(t *testing.T) {
wkp := netip.MustParsePrefix("64:ff9b::/96")
for _, s := range []string{
"0.0.0.0", "0.255.255.255", "1.0.0.0", "9.255.255.255", "10.0.0.0", "10.255.255.255", "11.0.0.0",
"100.63.255.255", "100.64.0.0", "100.127.255.255", "100.128.0.0", "126.255.255.255", "127.0.0.0",
"127.255.255.255", "128.0.0.0", "169.253.255.255", "169.254.0.0", "169.254.255.255", "169.255.0.0",
"172.15.255.255", "172.16.0.0", "172.31.255.255", "172.32.0.0", "192.0.0.0", "192.0.0.255", "192.0.1.0",
"192.0.2.0", "192.0.3.0", "192.88.99.0", "192.88.100.0", "192.167.255.255", "192.168.0.0",
"192.168.255.255", "192.169.0.0", "198.17.255.255", "198.18.0.0", "198.19.255.255", "198.20.0.0",
"198.51.100.0", "203.0.113.0", "203.0.114.0", "223.255.255.255", "224.0.0.0", "240.0.0.0", "255.255.255.255",
} {
v4 := netip.MustParseAddr(s)
b := v4.As4()
var a [16]byte
copy(a[:], netip.MustParseAddr("64:ff9b::").AsSlice())
copy(a[12:], b[:])
ip := netip.AddrFrom16(a)
for _, p := range []netip.Prefix{{}, wkp} {
got := locator.CheckResolvedIP(ip, p) == nil
want := locator.CheckResolvedIP(v4, netip.Prefix{}) == nil
if got != want {
t.Errorf("%s (%s) with prefix %v: public %v, its IPv4 address %v", ip, v4, p, got, want)
}
}
}
}

File diff suppressed because it is too large Load Diff

@ -46,7 +46,13 @@
names of certificates and the warning of the seal in the verdicts, a
profile of the certificate field by field, the addresses and the padding
of the locator, and errata. Its §76 records each change with its case.
- `datekeys.cddl`: the CBOR schemas of v0.12, the three control
- `DateKeys_Protocol_Specification_v0.13.md`: the draft v0.13, work in
progress and not approved; the branch `v0.13` implements it. It changes no
format and no verdict: the IP address that the name of a locator resolves
to may be an address of NAT64 whose IPv4 address inside is public, so that
a reader on an IPv6-only network downloads the rest of an envelope. Its §76
records the change with its case.
- `datekeys.cddl`: the CBOR schemas of v0.12, unchanged in the draft v0.13, the three control
versions and the security and head objects of format 3 included, with the
encoding rules CDDL cannot express. Those of v0.9 and v0.8.2 are at the tags
`spec-v0.9` and `spec-v0.8.2`.

23
testdata/README.md vendored

@ -56,6 +56,7 @@ Conventions for every file:
| `vectors/security_cms.json` | security areas with a signature of `alg` 2 or a seal of `seal_type` 2, each with its context, verdicts, results and lines | §29.7, §29.10, §29.11 |
| `vectors/ed25519_strict.json` | Ed25519 signatures and the result of the strict profile of the author signature | §29.9 |
| `vectors/note.json` | the data of the public note and the result of its rules | §24.1, §29.6 |
| `vectors/resolved_ip.json` | the IP address a name of a locator resolves to, NAT64 included, and whether a reader may connect | §44.1 (draft v0.13) |
| `vectors/wordkey.json` | the key of words: the words of a text, what a writer refuses, and the identity the words derive | §38.1, §64 |
| `vectors/locator.json` | the extension `datekeys.capsule` of a `.dkk`, its envelope and its locator, and what a reader rejects and uses of them | §44.1, §64 |
| `vectors/mutations.json` | the mutation corpus: the 178 mutations of §64 and further cases | §63, §64 |
@ -585,6 +586,28 @@ feed, a space at either end, U+202E, U+200B, a byte order mark, a
noncharacter, a byte that is not UTF-8 and the UTF-8 of a lone surrogate,
refused.
## `vectors/resolved_ip.json`
The IP address that the name of an https address of a locator resolves to,
which a reader checks on every connection (spec §44.1 of the draft v0.13):
`ip`, `nat64`, the NAT64 prefix of the network that the reader knows, or ""
for none, and `result`, `ok`, or `error` with the text of the reference in
`error`.
```json
{ "name": "the well-known prefix with 192.168.1.10", "ip": "64:ff9b::c0a8:10a", "nat64": "", "result": "error", "error": "locator: an https address whose name resolves to 64:ff9b::c0a8:10a, an address of NAT64 that holds 192.168.1.10, an IP address that is not public" }
```
A public address is accepted. An address of NAT64 (RFC 6052) of
`64:ff9b::/96`, or of the prefix of the network, counts by the IPv4 address
it holds, at the positions of RFC 6052: the cases put a public one and one of
several blocks that are not public in each, and the prefix of the network in
each length of RFC 6052. A prefix of another length, with bits after its
length, outside `64:ff9b::/16` and the public IPv6 addresses, or of IPv4, is
refused. Among the addresses that are not public: IPv4-mapped, 6to4, Teredo,
link-local, unique local, loopback, and the local-use prefix of RFC 8215
without the prefix of the network.
## `vectors/wordkey.json`
The key of words of spec §38.1, the cases that §64 of v0.11 asks for, in

@ -0,0 +1,287 @@
{
"spec": "0.12",
"description": "The IP address that the name of an https address of a locator resolves to, and whether a reader may connect (spec v0.13, 44.1): a public address, or an address of NAT64 (RFC 6052) of 64:ff9b::/96 or of the NAT64 prefix of the network, whose IPv4 address inside is public. See testdata/README.md.",
"cases": [
{
"name": "a public IPv4 address",
"ip": "203.0.114.5",
"nat64": "",
"result": "ok"
},
{
"name": "a public IPv6 address",
"ip": "2a01:4f8::1",
"nat64": "",
"result": "ok"
},
{
"name": "a private IPv4 address",
"ip": "192.168.1.10",
"nat64": "",
"result": "error",
"error": "locator: an https address whose name resolves to 192.168.1.10, an IP address that is not public"
},
{
"name": "loopback",
"ip": "127.0.0.1",
"nat64": "",
"result": "error",
"error": "locator: an https address whose name resolves to 127.0.0.1, an IP address that is not public"
},
{
"name": "IPv6 loopback",
"ip": "::1",
"nat64": "",
"result": "error",
"error": "locator: an https address whose name resolves to ::1, an IP address that is not public"
},
{
"name": "an IPv6 link-local address",
"ip": "fe80::1",
"nat64": "",
"result": "error",
"error": "locator: an https address whose name resolves to fe80::1, an IP address that is not public"
},
{
"name": "an IPv6 unique local address",
"ip": "fd00::1",
"nat64": "",
"result": "error",
"error": "locator: an https address whose name resolves to fd00::1, an IP address that is not public"
},
{
"name": "an IPv4-mapped address of a public IPv4 address",
"ip": "::ffff:203.0.114.5",
"nat64": "",
"result": "error",
"error": "locator: an https address whose name resolves to ::ffff:203.0.114.5, an IP address that is not public"
},
{
"name": "6to4",
"ip": "2002:cb00:7205::1",
"nat64": "",
"result": "error",
"error": "locator: an https address whose name resolves to 2002:cb00:7205::1, an IP address that is not public"
},
{
"name": "Teredo",
"ip": "2001:0:cb00:7205::1",
"nat64": "",
"result": "error",
"error": "locator: an https address whose name resolves to 2001:0:cb00:7205::1, an IP address that is not public"
},
{
"name": "the well-known prefix with a public IPv4 address",
"ip": "64:ff9b::cb00:7205",
"nat64": "",
"result": "ok"
},
{
"name": "the well-known prefix with 8.8.8.8",
"ip": "64:ff9b::808:808",
"nat64": "",
"result": "ok"
},
{
"name": "the well-known prefix with an IPv4 address of 10.0.0.0/8",
"ip": "64:ff9b::a00:1",
"nat64": "",
"result": "error",
"error": "locator: an https address whose name resolves to 64:ff9b::a00:1, an address of NAT64 that holds 10.0.0.1, an IP address that is not public"
},
{
"name": "the well-known prefix with 192.168.1.10",
"ip": "64:ff9b::c0a8:10a",
"nat64": "",
"result": "error",
"error": "locator: an https address whose name resolves to 64:ff9b::c0a8:10a, an address of NAT64 that holds 192.168.1.10, an IP address that is not public"
},
{
"name": "the well-known prefix with loopback",
"ip": "64:ff9b::7f00:1",
"nat64": "",
"result": "error",
"error": "locator: an https address whose name resolves to 64:ff9b::7f00:1, an address of NAT64 that holds 127.0.0.1, an IP address that is not public"
},
{
"name": "the well-known prefix with 169.254.169.254",
"ip": "64:ff9b::a9fe:a9fe",
"nat64": "",
"result": "error",
"error": "locator: an https address whose name resolves to 64:ff9b::a9fe:a9fe, an address of NAT64 that holds 169.254.169.254, an IP address that is not public"
},
{
"name": "the well-known prefix with 100.64.0.1",
"ip": "64:ff9b::6440:1",
"nat64": "",
"result": "error",
"error": "locator: an https address whose name resolves to 64:ff9b::6440:1, an address of NAT64 that holds 100.64.0.1, an IP address that is not public"
},
{
"name": "the well-known prefix with 0.0.0.0",
"ip": "64:ff9b::",
"nat64": "",
"result": "error",
"error": "locator: an https address whose name resolves to 64:ff9b::, an address of NAT64 that holds 0.0.0.0, an IP address that is not public"
},
{
"name": "the well-known prefix with 255.255.255.255",
"ip": "64:ff9b::ffff:ffff",
"nat64": "",
"result": "error",
"error": "locator: an https address whose name resolves to 64:ff9b::ffff:ffff, an address of NAT64 that holds 255.255.255.255, an IP address that is not public"
},
{
"name": "the well-known prefix with a documentation address",
"ip": "64:ff9b::cb00:7105",
"nat64": "",
"result": "error",
"error": "locator: an https address whose name resolves to 64:ff9b::cb00:7105, an address of NAT64 that holds 203.0.113.5, an IP address that is not public"
},
{
"name": "an address of 64:ff9b::/16 outside 64:ff9b::/96",
"ip": "64:ff9b::1:cb00:7205",
"nat64": "",
"result": "error",
"error": "locator: an https address whose name resolves to 64:ff9b::1:cb00:7205, an IP address that is not public"
},
{
"name": "the local-use prefix of RFC 8215 without the prefix of the network",
"ip": "64:ff9b:1::cb00:7205",
"nat64": "",
"result": "error",
"error": "locator: an https address whose name resolves to 64:ff9b:1::cb00:7205, an IP address that is not public"
},
{
"name": "the well-known prefix, given as the prefix of the network",
"ip": "64:ff9b::cb00:7205",
"nat64": "64:ff9b::/96",
"result": "ok"
},
{
"name": "the well-known prefix with another prefix of the network",
"ip": "64:ff9b::cb00:7205",
"nat64": "64:ff9b:1::/48",
"result": "ok"
},
{
"name": "the local-use prefix of RFC 8215, /48",
"ip": "64:ff9b:1:cb00:72:500::",
"nat64": "64:ff9b:1::/48",
"result": "ok"
},
{
"name": "the local-use prefix, /48, with a private IPv4 address",
"ip": "64:ff9b:1:c0a8:1:a00::",
"nat64": "64:ff9b:1::/48",
"result": "error",
"error": "locator: an https address whose name resolves to 64:ff9b:1:c0a8:1:a00::, an address of NAT64 that holds 192.168.1.10, an IP address that is not public"
},
{
"name": "the local-use prefix, /48, with bits 64 to 71 set",
"ip": "64:ff9b:1:cb00:172:500::",
"nat64": "64:ff9b:1::/48",
"result": "error",
"error": "locator: an https address whose name resolves to 64:ff9b:1:cb00:172:500::, an address of the NAT64 prefix 64:ff9b:1::/48 whose bits 64 to 71 are not zero"
},
{
"name": "a public prefix of the network, /96",
"ip": "2a01:4f8:c0:64::cb00:7205",
"nat64": "2a01:4f8:c0:64::/96",
"result": "ok"
},
{
"name": "a public prefix of the network, /96, with a private IPv4 address",
"ip": "2a01:4f8:c0:64::c0a8:10a",
"nat64": "2a01:4f8:c0:64::/96",
"result": "error",
"error": "locator: an https address whose name resolves to 2a01:4f8:c0:64::c0a8:10a, an address of NAT64 that holds 192.168.1.10, an IP address that is not public"
},
{
"name": "a public prefix of the network, /96, with loopback",
"ip": "2a01:4f8:c0:64::7f00:1",
"nat64": "2a01:4f8:c0:64::/96",
"result": "error",
"error": "locator: an https address whose name resolves to 2a01:4f8:c0:64::7f00:1, an address of NAT64 that holds 127.0.0.1, an IP address that is not public"
},
{
"name": "a public prefix of the network, /32",
"ip": "2a01:4f8:cb00:7205::",
"nat64": "2a01:4f8::/32",
"result": "ok"
},
{
"name": "a public prefix of the network, /40",
"ip": "2a01:4f8:c0cb:72:5::",
"nat64": "2a01:4f8:c000::/40",
"result": "ok"
},
{
"name": "a public prefix of the network, /56",
"ip": "2a01:4f8:c0:64cb:0:7205::",
"nat64": "2a01:4f8:c0:6400::/56",
"result": "ok"
},
{
"name": "a public prefix of the network, /64",
"ip": "2a01:4f8:c0:64:cb:72:500:0",
"nat64": "2a01:4f8:c0:64::/64",
"result": "ok"
},
{
"name": "a public prefix of the network, /64, with a private IPv4 address",
"ip": "2a01:4f8:c0:64:c0:a801:a00:0",
"nat64": "2a01:4f8:c0:64::/64",
"result": "error",
"error": "locator: an https address whose name resolves to 2a01:4f8:c0:64:c0:a801:a00:0, an address of NAT64 that holds 192.168.1.10, an IP address that is not public"
},
{
"name": "a public address outside the prefix of the network",
"ip": "2a01:4f8::1",
"nat64": "64:ff9b:1::/48",
"result": "ok"
},
{
"name": "a prefix of the network of 80 bits",
"ip": "64:ff9b:1::cb00:7205",
"nat64": "64:ff9b:1::/80",
"result": "error",
"error": "locator: the NAT64 prefix 64:ff9b:1::/80 is not of 32, 40, 48, 56, 64 or 96 bits (RFC 6052)"
},
{
"name": "a link-local prefix of the network",
"ip": "fe80::cb00:7205",
"nat64": "fe80::/96",
"result": "error",
"error": "locator: the NAT64 prefix fe80::/96 is neither in 64:ff9b::/16 nor a public IPv6 prefix"
},
{
"name": "a unique local prefix of the network",
"ip": "fd00::cb00:7205",
"nat64": "fd00::/96",
"result": "error",
"error": "locator: the NAT64 prefix fd00::/96 is neither in 64:ff9b::/16 nor a public IPv6 prefix"
},
{
"name": "a prefix of the network of 2001:db8::/32",
"ip": "2001:db8::cb00:7205",
"nat64": "2001:db8::/96",
"result": "error",
"error": "locator: the NAT64 prefix 2001:db8::/96 is neither in 64:ff9b::/16 nor a public IPv6 prefix"
},
{
"name": "a prefix of the network with bits after its length",
"ip": "64:ff9b::cb00:7205",
"nat64": "64:ff9b::1/96",
"result": "error",
"error": "locator: the NAT64 prefix 64:ff9b::1/96 has bits set after its length"
},
{
"name": "an IPv4 prefix of the network",
"ip": "203.0.114.5",
"nat64": "203.0.114.0/24",
"result": "error",
"error": "locator: the NAT64 prefix 203.0.114.0/24 is not an IPv6 prefix"
}
]
}
Loading…
Cancel
Save

Powered by TurnKey Linux.