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/CHANGELOG.md

576 lines
35 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Changelog
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.9
Moves the module to the DateKeys Protocol Specification v0.9, which adds
capsule format 2 (spec §76, "Cambios normativos de la v0.9"): until the
unlock date it hides the exact length of the content and the number of
credentials. `Encrypt` writes format 2 only; `Open` and `Inspect` read both
formats, and a format 1 capsule, as v0.8.2 wrote it, keeps its verdict.
### Format 2
- `VERSION` in the PRELUDE is the capsule format, 1 or 2 (spec §22, §23):
`capsule.Format`, `Format1`, `Format2` and `Prelude.Format`. Any other
value is `ERR_UNSUPPORTED_VERSION` at step 2, before any request, so a
v0.8.2 reader rejects a format 2 capsule without a network request.
- CONTROL_CBOR has the schema version of its format (spec §31). Version 2
adds key 6, `payload_length`, L in exactly 8 bytes, big-endian, at most
L_MAX = 2^53 − 2^46, and key 7, `padding`, 1 (bloque256) or 2
(reforzado). A control of the other version is `ERR_UNSUPPORTED_VERSION`
at step 14; a violation of keys 6 and 7 is `ERR_NON_CANONICAL_CBOR`.
Without extensions a version 2 control is 103 bytes, whatever L and the
code. `EncodeControl` and `DecodeControl` take the format.
- The payload is padded (spec §29.1): its plaintext is the content followed
by zeros up to P = rule(L). `capsule.PaddedLength` computes both rules on
64-bit integers, and `capsule.PayloadAgeLength` the length of PAYLOAD_AGE.
Step 17 checks that the plaintext is exactly P bytes and its padding zero,
or `ERR_INTEGRITY`. `Open` writes only the first L bytes to `dst`, never
the padding (spec §56), records step 17, and reports the format, L, the
rule and P in `Opened`.
- In format 2 INNER_ACCESS_AGE holds exactly 16 X25519 stanzas (spec §39),
`agewrap.AccessSlots`: `agewrap.CheckAccessStanzas` and
`agewrap.NewAccessIdentity` take the number of slots, 0 for format 1.
Another number is `ERR_POLICY_STRUCTURE_MISMATCH` at step 12, and again at
step 13.
### Writer rules (spec §62.1)
- `EncryptOptions.Length`, L, is required: the control is sealed before the
content is read, and a source that delivers another number of bytes is an
error. `EncryptOptions.Padding` chooses the rule; zero means reforzado.
- `time_and_key` takes from 1 to 16 credentials, the recipients and the
portable key together. Each free slot gets a dummy, a fresh X25519 public
key whose private key is dropped at once, and the 16 recipients are
shuffled with an unbiased Fisher–Yates over `crypto/rand`.
- A recipient that is not canonical (bit 255 set, or u ≥ p) or of low order
is rejected: `agewrap.CheckX25519Recipient`.
- Self-checks: INNER_ACCESS_AGE has 16 X25519 stanzas with distinct shares
and the portable key opens exactly one; PAYLOAD_AGE has the length P gives
and I_PAYLOAD opens its header.
- `Result` reports the format, L, the rule and P.
### CLI
- `datekeys encrypt` measures its input, copying a file that is not regular
to a temporary file first, and takes `-padding reforzado|bloque256`. Its
report and that of `decrypt` give the format and the lengths, and `decrypt`
warns that format 1 hides neither the number of credentials nor the exact
length. `datekeys inspect` reports the format, `"format"` in `-json`.
### Test data
- Seven format 2 fixtures (spec §67), whose records give L, the rule, P and
the INNER_ACCESS_AGE stanza each credential opens. The five format 1
fixtures of v0.8.2 keep their bytes as compatibility fixtures, and
`genfixtures` never regenerates them; their records name spec 0.9, the
format and stage 17.
- `vectors/padding.json`, new: both rules for the rows of spec §29.1,
checked against a Padmé computed with `math/big`.
- `vectors/cbor.json`: the control vectors carry `format`, absent meaning 1,
and gain the cases of version 2; "unknown key 6" is now "key 6, defined
only in schema version 2".
- `vectors/mutations.json`: "version changed" writes `VERSION` 3; the 33
mutations of the first two lists of spec §64 also run on format 2 fixtures
("format 2: …"), followed by the 22 of its third list and their companions
with a `.dkk`. The new cases derive from fixtures without randomness:
`testkit` seals them again with their known file keys and nonces
(`reseal.go`), and reproduces each fixture byte for byte when nothing is
edited.
- `vectors/inspect_differential.json` keeps its 1 825 cases and adds a block
for each format 2 fixture: 4 380 cases.
## Unreleased — specification v0.8.2
Moves the module to the DateKeys Protocol Specification v0.8.2, whose
normative change closes the extension format (spec §76), refined, amended and
corrected before release (see "Specification refinements", "Specification
amendment: point canonicality" and "Specification corrections: formal
review"). Framing and schema versions do not change.
The CBOR library is replaced by a codec of the module's own, without
reflection or dependencies. Every valid object encodes to the same bytes as
before: the official vectors and fixtures are unchanged, and every error code
and inspection step of the test suite and the mutation corpus is the same.
### Specification refinements
v0.8.2 is unreleased, so these refinements amend it without a version change;
spec §76 records each with its reproducible cases. No valid object changes,
nor the verdict of any existing official vector or fixture; `dk1.json` gains
four vectors for the reading rules of §19.
- Layered error precedence (spec §69.1, with §57 and §63): within one object
the code of the first failing layer is reported: frame, then type tag and
schema version (keys 0 and 1), then the CBOR profile and the CDDL, then the
fields with codes of their own in ascending key order; the rules with
codes of their own belong to the last layer, not to the CDDL layer. Across
objects and steps, the step order of §63 decides; step 9 now spells out its
order (the `.dkk` as an object, then its binding to the capsule, then
credentials, then the round time, then the request), and the errors of a
`.dkk` come at step 9.a even when it is decoded earlier, and never under
`time_only`. The optional inspection of steps 5, 6 and 8 and the
`capsule_digest` check can change the code, and so could, until the
corrections of the formal review, whether a network source verified the
release. Steps 10, 11, 13 and 17 give the codes of the release
verification (round, then signature) and of each identity.
- Trust model (spec §55.1): who can write PUBLIC_HEADER, CONTROL_CBOR,
PAYLOAD_AGE and the `.dkk` body, from which step each is bound and by what,
and what none of them proves. Spec §72: an extension with security-relevant
claims lives in CONTROL_CBOR or is signed by a signature extension; the
data of `.dkk` extensions is advisory for its holder only.
- One ordering rule for extension arrays (spec §31, §54, CDDL): strictly
ascending unsigned bytewise order of the UTF-8 bytes of `extension_id`, a
proper prefix first, never UTF-16 code units or a locale collation.
- Rules that only `testdata/README.md` stated are now normative text: the age
header grammar of C2SP and its codes (§28.1, §36), the round-time bound of
9999-12-31T23:59:59Z and pre-genesis instants (§15), the reading rules of
`dk1_` (§19: either Base64 alphabet but no CR or LF, one JSON object in
valid UTF-8 without a byte order mark, numbers by their exact decimal
value), lengths
of at least 1 (§22, §23, §40, §57), the comparison of the tlock stanza
arguments (§35, §63 step 8), the Provider Profile rules and the chain-hash
formula (§12.1, with `period` at most 2^32 − 1 and the name alphabets
normative; the name lengths stay implementation limits), `extension_id` of
at least one byte (§31), a repeated X25519 ephemeral share in
INNER_ACCESS_AGE (§36), and the implementation limits of the reference
(§74).
These error codes of the reference change, for inputs that no existing
official vector holds:
- `capsule.Open` checks an offered `.dkk` as an object before binding it to
the capsule: `access_type` and `access_material`, then its critical
extensions, then `capsule_id`, then `capsule_digest`. A `.dkk` for another
capsule with an unknown critical extension is now
`ERR_EXTENSION_CRITICAL_UNKNOWN` (was `ERR_ACCESS_INVALID`), and one with
an unsupported `access_type` and an unknown critical extension is
`ERR_ACCESS_INVALID` (was `ERR_EXTENSION_CRITICAL_UNKNOWN`).
- `accesskey.Decode` rejects a `BODY_LEN` of 0 with `ERR_INTEGRITY`, like a
`PUBLIC_HEADER_LEN` of 0; it was `ERR_NON_CANONICAL_CBOR`, the empty body
failing to decode.
- `datekey.Parse` rejects CR and LF in a `dk1_` string with
`ERR_DATEKEY_INVALID`; the Go Base64 decoders skipped them, and the
result was `ERR_DATEKEY_NON_CANONICAL`. It also rejects invalid UTF-8 in
the JSON at step 2 (see "Fixed").
- `datekeys decrypt -dkk` hands the `.dkk` to `capsule.Open` still encoded,
through the new `OpenOptions.AccessKeyFile`, so its decoding errors come at
step 9 of a `time_and_key` capsule, after any failure of steps 1 to 8, and
a `time_only` capsule ignores it; the CLI used to fail on it before
reading the capsule.
- `capsule.Open` ignores nil entries of `OpenOptions.Identities`: with no
other credential, the result is `ERR_ACCESS_REQUIRED` at step 9, before
the clock is consulted, instead of `ERR_RELEASE_UNAVAILABLE` or a release
request.
- `agewrap.AccessIdentity` tries every identity on every stanza: an identity
that unwraps two INNER_ACCESS_AGE stanzas is
`ERR_POLICY_STRUCTURE_MISMATCH` even when another identity unwraps exactly
one, whatever their order; the file used to open when the other came
first.
- `profile.NewRegistry` encodes and decodes each profile before comparing its
`profile_hash`, so a profile gets the code `profile.Decode` reports: a
`period` of 86401 s is `ERR_NON_CANONICAL_CBOR`, as in `Decode`, not
`ERR_UNKNOWN_PROFILE`. `Profile.Validate` checks the schema rules a value
can break (a period that is not a whole number of seconds in 1..86400, a
genesis time outside 0..2^53−1, a name that is not valid UTF-8) first,
with `ERR_NON_CANONICAL_CBOR`.
Tests: `capsule.TestPrecedenceWithinPublicHeader`, `TestPrecedenceAcrossSteps`,
`TestFrameLengthLowerBounds`, `TestMalformedAgeHeaders`,
`TestTlockStanzaArgumentComparison`, `TestAccessKeyCheckOrder`,
`TestAccessKeyFileAtStep9`, `TestControlCriticalBeforeHeaderBinding` and
`TestTrustModel`; `accesskey.TestDecodePrecedence`;
`agewrap.TestAccessIdentityStrictness`; `datekey.TestReadingRules`;
`profile.TestDecodePrecedence` (a G1 point outside the prime-order
subgroup), `TestPinPathMatchesDecode` and `TestChainHashFormula`;
`provider.TestVerifyRejects`; `extension.TestOrderIsUnsignedBytewise`;
`cmd/datekeys.TestDecryptAccessKeyOrder`.
### Specification amendment: point canonicality
An amendment of the unreleased v0.8.2, recorded with its case in spec §76.
The new §12.2 defines the canonical encoding of a BLS12-381 point, the
compressed form of drand: the compression flag set, the infinity flag only for
the point at infinity with every other bit zero, the sort flag for the
lexicographically largest y, big-endian coordinates below p (c1 then c0 in
G2) and a point of the prime-order subgroup. A decoder rejects every other
string, among them x + p and an identity with a payload. The Provider Profile
public key (§12.1), the release signature (§63 step 10) and the U of the
tlock stanza (§63 step 11) are canonical and never the point at infinity, and
step 11 defines the stanza body, `U || V || W` with |V| = |W| = 16 (128 bytes
for Quicknet), and the IBE check r·G == U. The case, from the second
implementation: `tlock-js` on `@noble/curves` 1.9.7 accepted U re-encoded as
c0 + p and a signature re-encoded as x + p and returned the same file key,
where the reference rejects both.
No error code or step of the reference changes: its decoder,
`kilic/bls12-381` through drand, already rejected those encodings. A U at
infinity, which only the IBE check used to reject, is refused before
decryption, with the same `ERR_INTEGRITY`. Spec §64 gains ten mutations,
exported in `mutations.json`: U with c0 + p, U at infinity, U with the
infinity flag and a payload, and tlock stanza bodies of 127 and 129 bytes,
each with a valid header MAC (`ERR_INTEGRITY`, step 11); a release signature
with x + p, at infinity, with the infinity flag and a payload, negated, and
negated together with U with c0 + p (`ERR_RELEASE_INVALID`, step 10). The
x + p case is a capsule for round 1004, whose published signature `testkit`
now knows (`testkit.XPlusPRound`): no fixture round has an x below
2^381 − p. No fixture and no other vector changes.
Tests: `profile.TestDrandPointDecodersAreCanonical`, which fails if a
dependency update makes the decoders of drand lenient, and
`TestPublicKeyEncodingIsCanonical`; new cases in `provider.TestVerifyRejects`,
`agewrap.TestTimeIdentityStrictness` and `TestTimeIdentityRelease`;
`capsule.TestPointMutationsChangeOnlyTheEncoding`, which checks that each
point mutation differs from a capsule that opens only in one encoding;
`internal/testkit.TestPointReencodings`.
### Specification corrections: formal review
Corrections of the unreleased v0.8.2 from the formal review of the
specification, recorded with their cases in spec §76 ("Correcciones de la
revisión formal"). Three change normative rules:
- A source that fetches releases over a network (a relay, the Release API or
a cache) must verify every response with the rules of step 10 and discard
the one that fails; when none passes, the code is `ERR_RELEASE_UNAVAILABLE`
at step 9 (spec §63). The codes of step 10 are those of a release supplied
directly, as in the official vectors. `provider/drand.Client` already did
this; the contract of `provider.ReleaseSource` and the documentation of
`capsule.OpenOptions.Source` now say so.
- Each registered extension declares the objects (PUBLIC_HEADER,
CONTROL_CBOR, `.dkk`) and the arrays where it may appear, and a known
extension elsewhere is treated as unknown there (spec §31, §54, §72): a
critical one is `ERR_EXTENSION_CRITICAL_UNKNOWN`, a noncritical one is
ignored. The new optional interface `extension.Placement` of a `Registry`
tells where each extension is registered; `capsule.Inspect` and
`capsule.Open` check the arrays of PUBLIC_HEADER (step 4), of the `.dkk`
(step 9.a) and of CONTROL_CBOR (step 14) with the new
`extension.CheckCriticalIn` and `extension.CheckNoncriticalIn`. A
`Registry` that does not implement it behaves as before.
- H2, H3 and H4 of step 11 are those of drand/kyber `encrypt/ibe`, and H2
hashes the element of GT in the order of `kilic/bls12-381`: c1 before c0 at
every level of the tower, each coordinate of Fp in 48 bytes big-endian. The
new vector file `testdata/vectors/tlock_ibe.json` freezes H2(e(G1, G2)) =
`cb87319f24560b5231579a09ad79f12e`; the order of `Fp12.toBytes` of noble,
c0 first, gives `0118eea9d5971745f71e3c94926f1717` and another file key.
The other findings are editorial: §27 defers the authenticity of
PUBLIC_HEADER to the trust model (§55.1) and says that the age header MAC
protects only against whoever does not know the file key; step 5 separates
the mandatory read of SEALED_CONTROL from the optional inspection of its age
header; step 15 names `ERR_HEADER_BINDING`; §21 makes the 16 CSPRNG bytes of
`capsule_id` a MUST; §77 gains drand/kyber, RFC 8259, RFC 8610 and RFC 4648;
§76 retitles its section "Cambios normativos de la v0.8.2" and corrects its
record: `dk1.json` gained four vectors, not three, and two cases of the
refinements quoted texts that no earlier version contained.
No error code of the reference changes, nor any fixture or existing vector.
`github.com/drand/kyber-bls12381`, already an indirect dependency through
drand and tlock, becomes a direct requirement: `internal/testkit` and a test
compute the pairing with it.
Tests: `capsule.TestReleaseFromANetworkSource` (a relay whose only answer is
a release of another round, or a negated signature, gives
`ERR_RELEASE_UNAVAILABLE` at step 9 through `provider/drand.Client`, and the
same release supplied directly the code of step 10);
`capsule.TestExtensionPlacement` (an extension registered for CONTROL_CBOR
only is unknown in PUBLIC_HEADER and in a `.dkk`, one registered as
noncritical only is unknown in a critical array, and a noncritical copy
outside its registration is ignored); `extension.TestPlacement`;
`agewrap.TestTlockH2Vector` (the frozen vector, and step 11 recomputed with
H2 and H4 against the file key that tlock unwraps).
A second round of the same review confirmed these corrections and asked for
three more, recorded in spec §76 as corrections 4 to 6:
- An encoder must not write a registered extension in an object or array it
is not registered for, and a reader that ignores a noncritical one for that
reason must not interpret its data (spec §54, §72). A writer could seal in
the critical extensions of CONTROL_CBOR one registered there as
noncritical only, which a reader that knows the registration rejects at
step 14, after the unlock. `capsule.Encrypt` and `accesskey.Encode` take
no `Registry`: they write the extensions they are given, and the
application, which knows the registration, applies the rule.
- §17 and §51 give the codes of step 10 only for a release supplied
directly, as step 10 does; one that a network source fetches and that
breaks those rules is discarded at step 9.
- Whatever the failure of the release source, step 9 reports
`ERR_RELEASE_UNAVAILABLE` and no other code (spec §63).
The other findings are editorial: §28.1 says that steps 5 and 6 parse only
the age header, since step 5 reads all of SEALED_CONTROL; step 9 has a single
arrow to `ERR_RELEASE_UNAVAILABLE`; §76 rewords two introductions.
`testdata/README.md` says where the extensions of the mutation corpus are
registered: in both arrays of every object.
These error results of the reference change, for inputs that no official
vector holds:
- `capsule.Open` reports any error of `OpenOptions.Source` at step 9 with
`ERR_RELEASE_UNAVAILABLE` as its only code. An error that carries another
normative code, or none, keeps only its text: a caller's source that
failed with `ERR_RELEASE_INVALID` gave `ERR_RELEASE_INVALID` at step 9. An
error that wraps `ERR_RELEASE_UNAVAILABLE` alone is returned as it is, with
its other causes, such as a context error.
- The error of `provider/drand.Client.Fetch` wraps `ERR_RELEASE_UNAVAILABLE`
and no other normative error, as `errors.go` promises for every error of
the module. The failure of each relay is kept in its text only, where
`errors.Join` wrapped it: a relay that answered with a release of another
round also made the error match `ERR_ROUND_MISMATCH`. A context that ended
is still wrapped, whether `Fetch` sees it end before or after the relays
fail because of it.
Tests: `capsule.TestReleaseSourceErrorsAtStep9` (a source error with another
code, two codes or none gives `ERR_RELEASE_UNAVAILABLE` alone at step 9 and
keeps its text); `capsule.TestExtensionPlacement`, which now also covers the
noncritical array of a `.dkk`, so that checking it with the object-blind
`extension.CheckNoncritical` fails; `provider/drand.TestFetchErrorHasOneCode`
and `TestUnavailabilityAndCancellation` (a canceled context and a deadline
during the request); `datekeys.TestCode`.
### Breaking changes
- `extension.New(id, version, data []byte)` takes the opaque data bytes instead
of a value that it encoded as CBOR, and rejects nil or empty data. An
extension without data is the literal `extension.Extension{ID, Version}`,
which omits key 2.
- Extension data (key 2) must be a byte string of at least one byte. Any other
CBOR type, `null` or `h''` at key 2 is now `ERR_NON_CANONICAL_CBOR`, so a
v0.8.1 object with such data no longer decodes. The base protocol never
decodes the content (§54).
- `codec.Valid` and its fuzz target `codec.FuzzValid` are removed: nothing
decodes extension data any more.
- Package `codec` is rewritten without reflection, struct tags or dependencies
(§58). Removed: `Marshal`, the reflection-based `Unmarshal(data, v)` and
`Peek(data, v)`, and `MaxNestedLevels`, `MaxArrayElements` and
`MaxMapPairs`. Each schema now writes its encoding with a `codec.Encoder`
(`Map`, `Array`, `Uint`, `Bstr`, `Text`, `Out`, with a sticky first error,
and `Fail`, which records an error of the schema so that `Out` never returns
bytes its decoder rejects) and reads it with a strict `codec.Decoder`
(`NewDecoder`, `Map`, `Key`, `EndMap`, `Array`, `Uint`, `Bstr`, `Text`,
`Done`). `codec.Unmarshal(in, decode, encode)` runs the decoder of a schema
and requires that its re-encoding reproduces the input; `codec.Peek(in)`
returns the type tag, of at most `codec.MaxTypeTagLen` (64) bytes, and the
schema version; `codec.Walk(in, maxDepth, maxLen)` checks that bytes are one
item of the §58 profile, for vectors, fuzzing and diagnostics.
`CheckSchema` and `MaxSafeUint` keep their names.
- `extension.Wire` and its `UnmarshalCBOR` are removed. `extension.Encode`
becomes `extension.Canonical`, which returns the validated array in
canonical order as `[]Extension`; `extension.Decode([]Wire)` becomes
`extension.DecodeArray(*codec.Decoder)`, which reads and validates one array
and rejects more than 64 entries from the array head, before reading any;
`extension.EncodeArray(*codec.Encoder, []Extension)` writes one, and
records in the Encoder, instead of writing it, an array that `DecodeArray`
would reject.
- `codec.CheckSchema` reads keys 0 and 1 only, and nothing after them (§70):
the map head, key 0, a type tag of at most 64 bytes, key 1 and the version
must be in the profile, each head in its shortest form, and the version at
most 2^53−1. A schema version other than the expected one read that way is
`ERR_UNSUPPORTED_VERSION` whatever follows it; it was
`ERR_NON_CANONICAL_CBOR` when the rest of the object was malformed. Every
other form of the version is now `ERR_NON_CANONICAL_CBOR`, where it was
`ERR_UNSUPPORTED_VERSION` whenever the value read was not the expected one:
a missing version, `null` or `undefined`, a version not in its shortest
form, a version above 2^53−1 (up to 2^64−1), a version that is not the
second key (placed before key 0 or after another key), and a version
behind a map head or a type tag head not in its shortest form. `true` and
`false` were already `ERR_NON_CANONICAL_CBOR`.
- `capsule.DecodeHeader` checks every CDDL rule of PUBLIC_HEADER, including
`access_policy`, the extension arrays and the cross-array rule, before it
parses the DateKey (§57, §63 step 4). A header that breaks both reports
`ERR_NON_CANONICAL_CBOR` where it reported `ERR_DATEKEY_INVALID` or
`ERR_DATEKEY_NON_CANONICAL`; a header with one fault keeps its code.
- `profile.Profile.CanonicalCBOR` and `Hash` refuse a `profile_id`,
`provider`, `network` or `scheme` that is not valid UTF-8, with
`ERR_NON_CANONICAL_CBOR`: they wrote it as an invalid text string. Every
encoder refuses such text.
- At most 64 extensions per array and `extension_version` at most 2^32−1, on
encode and decode (`ERR_NON_CANONICAL_CBOR`). An `extension_id` appears at
most once per object, and arrays are ordered by `extension_id` only.
- Provider Profile: `genesis_time` is an unsigned integer, and `period` and
`genesis_time` are at most 2^53−1; a negative or larger value is
`ERR_NON_CANONICAL_CBOR`.
- `null` in a byte-string field is `ERR_NON_CANONICAL_CBOR` (for example a
`null` `access_material` was `ERR_ACCESS_INVALID`): nil byte strings, arrays
and maps now encode as empty ones, never as `null`.
- `capsule.EncodeHeader` and `capsule.DecodeHeader` enforce the 1 MiB
PUBLIC_HEADER limit and `accesskey.DecodeBody` the 16 MiB BODY limit. Every
frame-limit refusal, including those of `accesskey.MarshalBody` and of
`capsule.Encrypt` for SEALED_CONTROL, now wraps `ERR_INTEGRITY` (§57).
- `profile.Profile.CanonicalCBOR` refuses a `period` or `genesis_time` outside
the schema with `ERR_NON_CANONICAL_CBOR`, as `profile.Decode` does.
- The official fixture `time_only_extensions` is regenerated: its header data
is the raw UTF-8 bytes of "public label" and its control data is
`{0: 7, 1: "sealed"}` (`a2000701667365616c6564`). Every other `.dkc` and
`.dkk` keeps its bytes; the fixture and vector metadata name spec 0.8.2.
### Added
- `datekeys.SpecVersion` (`0.8.2`), the specification the module implements,
and `datekeys.Version()`, the version of the module as the go command
recorded it: a tag, the pseudo-version of the commit of a checkout build,
or `(devel)`. `datekeys version` prints both and the Go toolchain.
- `capsule.OpenOptions.AccessKeyFile`, a `.dkk` still encoded, which `Open`
decodes at step 9.a and only for a `time_and_key` capsule (§63, §69.1).
- `ErrExtensionDataInvalid` (`ERR_EXTENSION_DATA_INVALID`, §69).
- `extension.Placement`, an optional interface of a `Registry` that tells in
which objects and arrays each extension is registered, with
`extension.Object` (`PublicHeader`, `Control`, `AccessKey`),
`extension.Array` (`Critical`, `Noncritical`), `extension.KnownIn`,
`extension.CheckCriticalIn` and `extension.CheckNoncriticalIn` (§54, §72).
`CheckCritical` and `CheckNoncritical`, which do not know the object, keep
their behaviour and consult no `Placement`.
- `testdata/vectors/tlock_ibe.json`, the H2 vector of §63 step 11, generated
by `internal/testkit.IBEVectors` and documented in `testdata/README.md`.
- `extension.DataValidator`, an optional interface of a `Registry` that
validates the data of the extensions it knows: a known critical extension
with invalid data fails with `ErrExtensionDataInvalid` (§63 steps 4 and 14,
and the `.dkk` check); a known noncritical one is reported in
`capsule.Inspection.UnusableExtensions`, `capsule.Opened.UnusableControlExtensions`
or `capsule.Opened.UnusableAccessKeyExtensions` (`extension.CheckNoncritical`,
`extension.Unusable`) and does not fail.
- Encoder self-checks: `capsule.Encrypt` decodes its PUBLIC_HEADER and
CONTROL_CBOR, and `accesskey.MarshalBody` its body, with the readers'
decoders before sealing or writing (§72).
- `extension.MaxExtensions`, `MaxVersion`, `MaxDataLen`, `codec.MaxSafeUint`
and `codec.MaxTypeTagLen`.
- The `.dkk` fixture `time_and_key_portable_extension`, which carries a
noncritical extension with data (§68).
- `genfixtures -only NAME[,NAME...]` regenerates the named fixtures only.
- Tests: the three new §64 mutations (data that is not a byte string, `h''`
data, 65 extensions), regression tests for the cases of §76, conformance
checks on the exact data bytes, and the fuzz target
`capsule.FuzzEncodeImpliesDecode` (header, control and `.dkk`).
- The mutation corpus moves from `capsule/mutation_test.go` to
`internal/testkit.Mutations`, shared by the test and by `genfixtures`. Its
third-party X25519 identity is now fixed (`testkit.Stranger`), and every
release source answers with one recorded release, as the exported corpus
describes it. The CLI's inspect view moves to `internal/inspectview`, which
`genfixtures` uses to freeze the outputs.
- `internal/cbortest`, an encoder and decoder of generic CBOR values written
independently of `codec`: tests build with it inputs outside the profile
and check `codec` against it.
- Tests of the map structure of every schema (key order, required and unknown
keys, entry counts) and of the codec, whose statement coverage is 100 %.
- `access_policy` values whose low byte is 0 or 1 (256, 257, 65536, 2^32,
2^53−256…) are tested as undefined, as `FuzzDecodeHeader` seeds and as two
mutations with a consistent `header_binding` (`testkit.Build.RawPolicy`).
- Tests `extension.TestEncodeArrayRejects`,
`accesskey.TestEncodeAndDecodeLeaveNoStaleMaterial`,
`accesskey.TestDecodeShortBodyAllocatesLittle` and `capsule.TestDecryptAll`.
- Shared test data for a second implementation, generated by `genfixtures`,
regenerated by the gate and documented in `testdata/README.md`:
- `testdata/vectors/cbor.json`: 36 accepted and 67 rejected items of the
§58 profile, walked with `codec.Walk` (integers above 2^53−1 as decimal
strings), and 135 schema vectors: minimal valid object, unknown key,
missing key, wrong type, size and range for the Provider Profile,
PUBLIC_HEADER, CONTROL_CBOR, the `.dkk` body, `verification_metadata` and
extensions (data `40` and `5801xx`, data of every other type, 64 and 65
extensions, a leading BOM, the U+FF61/U+10000 order, `extension_version`
2^32−1 and 2^32); schema versions 2^53 and 2^64−1 and the order of type
tag and version; and the Provider Profile validation (names, public key,
`genesis_time`, drand scheme, the chain-hash self-check with its formula,
the `period` limit), each vector keeping the chain hash consistent unless
it tests the self-check.
- `testdata/vectors/mutations.json`: the mutation corpus as frozen data, 65
cases (the 33 of §64 first), each a `.dkc` given as edits of a fixture and
what the reader is given (`.dkk`, identities, the recorded release, clock,
registry, known extensions), with the expected error and step. The 17
capsules built with age randomness are kept from the committed file;
`genfixtures -only mutations` rebuilds them.
- `testdata/vectors/inspect_differential.json`: 1825 deterministic mutations
of the five `.dkc` fixtures (bit flips, byte changes, truncations,
insertions, deletions, length fields, CBOR-aware header edits, DateKey
edits, age header edits) with the verdict of steps 1 to 8.
- `testdata/fixtures/<name>.inspect.json`: the exact output of
`datekeys inspect -json -in <name>.dkc` for each official capsule.
- `testdata/README.md` points to the sections of the specification that
decide each verdict (§12.1, §15, §19, §22, §23, §28.1, §63, §69.1, §74),
which the refinements above moved into normative text.
- `spec/datekeys.cddl` marks the one-day `period` limit of the Provider
Profile, which `profile.Decode` already applied, as an implementation limit
of the reference (§57, §74); spec §11 allows up to 2^53−1.
- Tests that replay them: `codec.TestSharedVectors`,
`internal/testkit.TestSchemaVectors`, `capsule.TestExportedMutationCorpus`,
`capsule.TestInspectDifferentialCorpus` and
`cmd/datekeys.TestInspectJSONGoldens`.
- Fuzz targets `codec.FuzzDecoder` (the Decoder primitives),
`codec.FuzzWalk` (against the independent decoder), `codec.FuzzPeek`,
`codec.FuzzEncodeImpliesWalk` and `extension.FuzzDecodeArray`, run by
`scripts/fuzz.sh`; `codec.FuzzUnmarshal` now fuzzes a hand-written schema.
### Removed
- The dependencies `github.com/fxamacker/cbor/v2` and
`github.com/x448/float16`. `go.mod` requires nothing new.
### Fixed
- Error messages no longer copy the text of an error of age, tlock, kyber,
drand or kyber-bls12381. When the IBE check of a tlock stanza failed,
kyber's error carried the candidate plaintext and r, and
`agewrap.TimeIdentity` copied it into its error, and so into the error of
`capsule.Open` and the details of `Inspection.Checks`: a third party who
edited W learned FK_TIME from the message. Each such failure now has a
fixed message with its normative error and a reason of its own: the length
of the tlock stanza body, the encoding of U, U at infinity or the IBE check;
the header or the STREAM of an age file; a malformed X25519 stanza. A
failure of the writer of the plaintext at step 17 keeps its own text.
`capsule.TestTlockFailureDiagnosticsCarryNoSecrets`,
`TestPlaintextWriterFailureKeepsItsText`.
- `datekey.Parse` rejects invalid UTF-8 in the `dk1_` JSON at step 2, as
§19 requires. `encoding/json` replaced it with U+FFFD, so a member that a
repeated name overwrites passed steps 2 and 3 and ended as
`ERR_DATEKEY_NON_CANONICAL` instead of `ERR_DATEKEY_INVALID`. Found by the
second implementation's differential; new `dk1.json` vector.
- `extension.CheckDisjoint` is a linear merge of the two sorted arrays; a
PUBLIC_HEADER with 40 000 + 40 000 extensions took 8.3 s in the pairwise
check (§76, case 6).
- Control data made of 14 or 15 nested arrays was sealed by `Encrypt` and
rejected by `Open` at step 14, after the unlock (§76, case 5).
- Header data `{NaN: 0, NaN: 1}` gave a nondeterministic verdict (§76, case 3).
- `extension.New(id, v, nil)` wrote `null` as data (§76, case 2).
- `codec.Unmarshal` wipes its re-encoding, which after the new self-checks
held a copy of I_PAYLOAD or `access_material`, and `accesskey.DecodeBody`
wipes the material on its error paths. The `codec.Encoder` also wipes every
buffer it outgrows, and `codec.Unmarshal` sizes its re-encoding for the
input; the former library's internal buffers could keep a copy.
- `accesskey.Encode` wipes the body it wrote, and `accesskey.Decode` reads the
body into a buffer that grows with the data read, wiping every buffer it
outgrows, and wipes the body once decoded or on error: both left copies of
`access_material` behind. The in-memory age decryption of SEALED_CONTROL and
INNER_ACCESS_AGE in `capsule.Open` reads the plaintext into one buffer of
the ciphertext's size instead of a growing one, so that no outgrown buffer
keeps a copy of I_PAYLOAD. Buffers internal to `filippo.io/age` and copies
made by the Go runtime stay out of reach (SECURITY.md).
## Unreleased — v0.1.0
First implementation of the DateKeys Protocol Specification v0.8.1.
### Added
- `datekey`: local date → round resolution at full precision (§15), canonical
`dk1_` encoding and strict parsing (§18, §19).
- `profile`: Provider Profile Deterministic CBOR and `profile_hash` (§11), the
pinned Quicknet profile with its chain-hash self-check (§12), and pinned
registries (§13).
- `provider`: release sources and local BLS verification (§51);
`provider/drand`: racing public relays, verifying every answer (§48, §49, §52).
- `codec`: Deterministic CBOR with a re-encoding canonicality check (§58, §58.1).
- `extension`: the generic extension mechanism (§54).
- `agewrap`: strict tlock and X25519 age identities that enforce the stanza
rules (§27, §29, §32, §33, §35), and a secret-free header probe.
- `capsule`: `.dkc` framing, `Encrypt` for `time_only` and `time_and_key`
(§61, §62), `Inspect` (§63 steps 1–8) and `Open` (§63 steps 9–18).
- `accesskey`: `.dkk` encoding and decoding (§40–§44).
- `cmd/datekeys`: `encrypt`, `decrypt`, `inspect`, `datekey resolve`,
`profile hash`, with atomic, non-overwriting outputs.
- Official vectors (§65, §66), `.dkc`/`.dkk` fixtures (§67, §68), the mutation
corpus (§64), fuzz targets for every parser, interoperability tests with the
official `age` and `tle` CLIs, and live Quicknet integration tests.
- `spec/datekeys.cddl` and `docs/traceability.md`.

Powered by TurnKey Linux.