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.
DateKeys/docs/traceability.md

18 KiB

Traceability: DateKeys Protocol Specification v0.8.2 ↔ datekeys-go

This table maps every normative section of the specification to the code that implements it and to the tests that exercise it. It is updated in the same change as any normative code, and it is the document handed to the external 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.8.2.md.

Section map

§ Topic Implementation Tests
3 Guiding principle: verify locally profile.Registry, datekey.Resolve, provider.Verify, capsule.Inspect capsule.TestMutationCorpus
4 Security goals whole module whole suite
7 Threat model creator model in internal/testkit.Build, RewriteAge; third-party edits in the mutation corpus agewrap.TestTimeIdentityStrictness, TestPayloadIdentityStrictness, TestAccessIdentityStrictness, capsule.TestMutationCorpus
9 Provider abstraction provider.Condition, provider.Release, provider.ReleaseSource provider/*
10 Provider Profile profile.Profile, Profile.Validate profile.TestValidateRejectsTamperedProfiles
11 Canonical profile encoding, profile_hash; period in 1..2^53−1, genesis_time in 0..2^53−1 Profile.CanonicalCBOR, Profile.Hash, profile.Decode (unsigned genesis_time, codec.MaxSafeUint) profile.TestQuicknetMatchesGoldenVector, TestQuicknetCBORLayout, TestDecodeRoundTrip, TestIntegerRanges, FuzzDecode; testdata/vectors/profile_quicknet.json
12 Quicknet Provider Profile V1 profile.Quicknet, profile.Quicknet* constants profile.TestQuicknetMatchesGoldenVector
13 Root of trust profile.NewRegistry, profile.Pin, profile.Default, QuicknetProfileHash; chain-hash self-check in Profile.Validate profile.TestRegistry; mutations unknown profile, empty registry
14 DateKey datekey.DateKey datekey/*
15 Date → round resolution datekey.Resolve, datekey.RoundTime datekey.TestGoldenRoundVectors, TestRoundNeverOpensEarly, TestResolveProperty, TestTimezoneIndependence
16 Normative round vector — datekey.TestNormativeRoundVector; testdata/vectors/quicknet_rounds.json
17 Past-round attack provider.Verify (round equality), capsule.Encrypt (round time ≥ requested), agewrap.CheckTimeStanzas provider.TestVerifyRejects; mutations DateKey A + release of round B, tlock stanza round differs from DateKey.round
18 dk1_ representation DateKey.CanonicalJSON, DateKey.Compact datekey.TestGoldenDK1Vectors, TestNormativeRoundVector
19 dk1_ canonicality datekey.Parse datekey.TestGoldenDK1Vectors, TestNumberSpellings, FuzzParse; mutation non-canonical dk1_ JSON; testdata/vectors/dk1.json
20 File extensions and magic magic checks in capsule.ParsePrelude, accesskey.Decode mutation a .dkk offered as a .dkc; accesskey.TestDecodeRejects a .dkc
21 capsule_id capsule.Encrypt (16 bytes from crypto/rand), capsule.DecodeHeader capsule.TestPortableKeysAreNeverReused
22 .dkc framing capsule.Prelude, capsule.ParsePrelude mutations version changed, flags != 0, reserved != 0, magic, length limits; capsule.FuzzParsePrelude
23 PRELUDE Prelude.Bytes capsule.TestConformanceFixtures
24 PUBLIC_HEADER; keys 5 and 6 optional, 1 to 64 extensions each capsule.Header, EncodeHeader, DecodeHeader capsule.TestConformanceFixtures (exact extension data), TestDecodeHeaderRejects, FuzzDecodeHeader, FuzzEncodeImpliesDecode; mutations header schema version changed, unknown key in PUBLIC_HEADER
25 Declared access policy capsule.Policy; capsule.Open step 12 mutations access_policy=… with … structure (four cases), undefined access_policy
26 Header binding capsule.HeaderBinding; capsule.Open step 15 capsule.TestConformanceFixtures; mutation PUBLIC_HEADER_A + SEALED_CONTROL_B
27 Pre-unlock validation capsule.Inspect (steps 1–8), agewrap.Stanzas probe capsule.TestMutationCorpus (no release request for any pre-unlock failure), FuzzInspect
28 Three age files capsule.Encrypt, capsule.Open capsule.TestEncryptRoundTripBothPolicies
29 PAYLOAD_AGE capsule.Encrypt step 4; agewrap.PayloadIdentity, agewrap.CheckPayloadStanzas agewrap.TestPayloadIdentityStrictness; mutation extra stanza in PAYLOAD_AGE
30 PAYLOAD_AGE is a complete age file filippo.io/age public API only capsule.TestInteropAgeOpensPayload (-tags interop, official age CLI)
30.1 CONTROL_CBOR ↔ PAYLOAD_AGE binding agewrap.PayloadIdentity mutation SEALED_CONTROL_A + PAYLOAD_AGE_B; agewrap.TestPayloadIdentityStrictness
31 CONTROL_CBOR; keys 4 and 5 optional; extension entry rules capsule.Control, EncodeControl, DecodeControl; extension capsule.TestConformanceFixtures (exact extension data), TestDecodeControlRejects, FuzzDecodeControl, FuzzEncodeImpliesDecode; mutation unknown critical CONTROL_CBOR extension
32 time_only capsule.Encrypt; agewrap.TimeRecipient fixtures time_only*, empty_payload; capsule.TestInteropTleOpensSealedControl (-tags interop, official tle CLI)
33 time_and_key capsule.Encrypt (seal); agewrap.AccessIdentity fixtures time_and_key_*; capsule.TestEncryptRoundTripBothPolicies
34 SEALED_CONTROL capsule.Encrypt; capsule.Open step 11 capsule.TestConformanceFixtures
35 tlock strict mode agewrap.TimeRecipient, agewrap.TimeIdentity (pinned parameters only, exact stanza arguments) agewrap.TestTimeIdentityStrictness, TestInteroperabilityWithTlockLibrary, TestTimeIdentityRelease
36 Policy ↔ structure capsule.Open step 12, agewrap.CheckAccessStanzas mutations access_policy=… (four cases), non-X25519 stanza in INNER_ACCESS_AGE
36.1 Authenticity semantics documented in README.md, SECURITY.md — (a property the protocol does not provide)
37 X25519 recipient V1 age.X25519Recipient; agewrap.X25519IdentityFromRaw agewrap.TestRawKeys
38 Portable Access Key EncryptOptions.NewPortableKey (fresh I_ACCESS per capsule; no API accepts an existing one); accesskey.AccessKey capsule.TestPortableKeysAreNeverReused
39 Multiple recipients capsule.Encrypt; agewrap.AccessIdentity capsule.TestFixtureRecipients, TestEncryptRoundTripBothPolicies
40 .dkk framing accesskey.Encode, accesskey.Decode accesskey.TestDecodeRejects, FuzzDecode
41 .dkk BODY_CBOR AccessKey.MarshalBody, accesskey.DecodeBody accesskey.TestFixtures
42 credential_id capsule.Encrypt (16 bytes from crypto/rand) capsule.TestPortableKeysAreNeverReused
43 verification_metadata accesskey.Verification; capsule.Open (checkCapsuleDigest, seekable readers) accesskey.TestDecodeRejects empty verification map; 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
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
48 Multi-relay provider/drand.Client (race, first verified release wins) drand.TestRaceWaitsForAValidSignature
49 Direct recovery from the provider provider/drand drand.TestLiveRelays, capsule.TestLiveLifecycle (-tags integration)
50 Historical release dependency documented in README.md —
51 Quicknet release verification provider.Verify provider.TestVerifyPublishedReleases, TestVerifyRejects, TestVerifyUsesThePinnedKeyOnly
52 DNS / MITM provider/drand (no redirects, bounded responses, BLS) drand.TestRedirectsAreNotFollowed, TestRejectMalformedRelayResponses, TestRandomnessMustMatchWhenPresent
53 Harvest now, decrypt later cmd/datekeys warning beyond one year cmd/datekeys.TestLongHorizonWarning
54 Extensions: data absent or a non-empty opaque byte string, never decoded; 1 to 64 per array; extension_version ≤ 2^32−1; one extension_id per object extension.New, Wire.UnmarshalCBOR (explicit key 2 check), Encode, Decode, CheckDisjoint (linear merge), CheckCritical, CheckNoncritical, Unusable extension.TestNew, TestWireData, TestEncodeRejects, TestDecodeRejects, TestCheckDisjoint, TestCheckDisjointIsLinear, TestCheckCritical, TestCheckNoncritical; capsule.TestKnownCriticalExtensions, TestUnusableNoncriticalExtensions; mutations unknown critical … extension, known critical … extension with invalid data, extension_version above 2^32-1, null extension data
55 Auxiliary integrity capsule_digest treated as UX only —
56 Atomic plaintext output capsule.Open contract; cmd/datekeys.writeAtomic cmd/datekeys.TestOutputNotPublishedOnFailureOrOverwrite, TestDecryptFailuresLeaveNothing
57 Parser limits, MUST for encoders and decoders; frame lengths and objects above their frame → ERR_INTEGRITY on encode and decode, CDDL violations → ERR_NON_CANONICAL_CBOR, Provider Profile names and public key → ERR_UNKNOWN_PROFILE; implementation limits not normative capsule.MaxPublicHeaderLen (ParsePrelude, EncodeHeader, DecodeHeader), MaxSealedControlLen (ParsePrelude, Encrypt), accesskey.MaxBodyLen (Decode, DecodeBody, MarshalBody), extension.MaxExtensions, MaxDataLen, codec limits; profile.Validate mutations …_LEN above the limit, 65 extensions in one array; capsule.TestHeaderLimit, TestHugeExtensionArraysAreRejected; accesskey.TestDecodeRejects body length above the limit, TestBodyLimit; profile.TestValidateRejectsTamperedProfiles, TestIntegerRanges
58 Canonical CBOR and the protocol's CBOR profile (major types 0, 2, 3, 4, 5; unsigned integer keys; integers ≤ 2^53−1) codec.Marshal (nil containers as empty, never null), codec.Unmarshal (re-encoding comparison) into typed schemas; codec.MaxSafeUint; the profile covers the head of extension data only codec.TestUnmarshalRejectsNonCanonical (negative integer, float, true, null, text key), TestRoundTripProperty, FuzzUnmarshal; extension.TestWireData
58.1 Absent optional fields are omitted; h'' and null never stand for absence extension.Encode (nil for empty), extension.Wire.UnmarshalCBOR and Decode (empty data), re-encoding check, accesskey verification map codec empty optional array present; accesskey empty extension array, empty verification map, null verification, empty data, null data; mutation empty extension data (h'')
59 Supply-chain security pinned go.mod/go.sum, .gitea/workflows, scripts/check.sh, .goreleaser.yaml, SECURITY.md CI jobs vuln, sbom, verify
60 Conceptual Go interfaces provider.ReleaseSource, provider.Verify, datekey.Resolve, datekey.RoundTime —
61 time_only encryption flow capsule.Encrypt (steps numbered in comments) capsule.TestEncryptRoundTripBothPolicies
62 time_and_key encryption flow capsule.Encrypt capsule.TestEncryptRoundTripBothPolicies, TestPortableKeysAreNeverReused
63 Decryption flow; steps 4 and 14 validate critical extensions (unknown, invalid data) capsule.Inspect (steps 1–8), capsule.Open (steps 9–18), MUST rules inside agewrap identities capsule.TestConformanceFixtures (stage by stage), TestMutationCorpus
64 Mandatory mutation tests capsule/mutation_test.go capsule.TestMutationCorpus: the 23 listed mutations plus 30 more
65 Quicknet vectors internal/testkit.RoundVectors datekey.TestGoldenRoundVectors
66 dk1_ vectors internal/testkit.DK1Vectors datekey.TestGoldenDK1Vectors
67 .dkc vectors testdata/fixtures/*.dkc + *.json, internal/testkit/genfixtures capsule.TestConformanceFixtures
68 .dkk vectors, with the exact extension data; one carries an extension with data testdata/fixtures/*.dkk + *.dkk.json; time_and_key_portable_extension.dkk derived by genfixtures accesskey.TestFixtures, TestFixtureWithExtension; capsule.TestAccessKeyFixtureWithExtension
69 Normative errors, including ERR_EXTENSION_DATA_INVALID errors.go datekeys.TestCatalogueMatchesSpec, TestCode
70 Compatibility magic and version checks, codec.CheckSchema mutations; codec.TestCheckSchema
71 Profile registry profile.Decode + profile.NewRegistry with pinned hashes profile.TestRegistry
72 Extension registry and registration rules; the encoder decodes its own output before sealing extension.Registry, extension.Set, extension.DataValidator; self-checks in capsule.Encrypt and accesskey.MarshalBody capsule.TestKnownCriticalExtensions, TestUnusableNoncriticalExtensions, TestNestedDataSealsAndOpens, FuzzEncodeImpliesDecode
75 Blocking requirements before v1.0 items 1–9 above; item 10 (external review) pending —
76 Change policy; the v0.8.2 extension change and its reproducible cases extension, codec, fixture time_only_extensions regenerated case 2: extension.TestNew; case 3: capsule.TestNaNKeyedDataHasOneVerdict; case 4: capsule.TestExtensionFixtureData; case 5: capsule.TestNestedDataSealsAndOpens; case 6: capsule.TestHugeExtensionArraysAreRejected, extension.TestCheckDisjointIsLinear

Error mapping

Where the specification does not name the error of a failure, the reference implementation uses the following mapping. Each entry is a reproducible case under the change policy of §76.

Failure Error
Bytes that are not the deterministic encoding of a valid schema instance: malformed CBOR, non-canonical encoding, unknown key, missing key, wrong type, null, wrong type tag (key 0), wrong field length, undefined access_policy, empty optional array or map, extension rules (named by §54 and §57 since v0.8.2) ERR_NON_CANONICAL_CBOR
Schema version (key 1) other than 1 ERR_UNSUPPORTED_VERSION
Truncated framing, length fields beyond the §57 limits, an object above its §57 frame on encode or decode, data after BODY_CBOR, malformed or unauthenticated age data, truncated or modified STREAM, trailing data after PAYLOAD_AGE, a PAYLOAD_AGE that I_PAYLOAD cannot open ERR_INTEGRITY
Stanza count or type violations in OUTER_TIME_AGE, PAYLOAD_AGE or INNER_ACCESS_AGE, including two stanzas for one recipient ERR_POLICY_STRUCTURE_MISMATCH
tlock stanza round argument not exactly the canonical decimal DateKey round ERR_ROUND_MISMATCH
tlock stanza chain hash not exactly the lowercase hex chain hash of the pinned profile; profile whose parameters do not hash to its chain hash ERR_PROFILE_MISMATCH
Instant before the profile genesis or after 9999-12-31T23:59:59Z; round outside the profile range ERR_DATEKEY_INVALID
Unknown access_type, wrong material length, .dkk for another capsule_id, capsule_digest mismatch, no supplied identity is a recipient ERR_ACCESS_INVALID
Round time not reached yet (no request is made), no source delivered the release ERR_RELEASE_UNAVAILABLE

Implementation decisions to confirm in the specification

These are choices the reference implementation had to make where v0.8.2 is silent or provisional (§74). None changes the protocol semantics; each is a candidate clarification under §76.

  1. Pre-genesis instants. §15 defines the candidate formula relative to genesis_time; instants before it are rejected with ERR_DATEKEY_INVALID instead of resolving to round 1.
  2. Round bounds. dk1_ accepts rounds in 1..2^53−1 so that JSON parsers based on IEEE 754 doubles read the same integer; a profile further limits rounds to round times up to 9999-12-31T23:59:59Z (Quicknet: 83 903 165 811).
  3. profile_id alphabet. [a-z0-9][a-z0-9:._-]{0,127}, which keeps the canonical dk1_ JSON free of escapes and makes its re-emission trivial.
  4. Number spellings in dk1_. JSON numbers are compared by value, so 1e3 or 1000.0 for 1000 are ERR_DATEKEY_NON_CANONICAL, while non-integers, negatives and out-of-range values are ERR_DATEKEY_INVALID. Padded or standard-alphabet Base64 and non-zero trailing bits are non-canonical.
  5. Closed maps. Unknown keys in core maps are rejected; applications use extensions (§1, §54).
  6. Extension data. v0.8.2 settles the format (§54, §76): key 2 is omitted when absent and otherwise a non-empty byte string that the base protocol never decodes. What remains an implementation choice: extension_id is 1 to 256 bytes of UTF-8; extension.MaxDataLen is 64 MiB, the largest frame, the container frame being the effective bound; an application validates the data of the extensions it knows through the optional extension.DataValidator of its Registry, and an unknown critical extension is reported before a known one with invalid data.
  7. One stanza per recipient. Enforced as far as a recipient can observe it: no repeated X25519 ephemeral share, and no identity that unwraps more than one stanza.
  8. Strict tlock stanza arguments. Exact string comparison, as the tlock library itself does for the chain hash; a round with leading zeros is a mismatch.
  9. capsule_digest. Written by Encrypt for every portable key and checked before any request when the capsule reader is seekable; it remains a UX shortcut (§43).
  10. Creation in the past. Encrypt requires the unlock time to be strictly after the injected clock.
  11. Clock injection. No library package reads the wall clock; Encrypt and Open require a Now function, and Open never requests a release for a round whose time has not been reached.

Powered by TurnKey Linux.