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

230 lines
14 KiB

# 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.8.2
Moves the module to the DateKeys Protocol Specification v0.8.2, whose one
normative change closes the extension format (spec §76). 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.
### 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
- `ErrExtensionDataInvalid` (`ERR_EXTENSION_DATA_INVALID`, §69).
- `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, 55
cases (the 23 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 16
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` also states the rules of the reference that the spec
leaves open and the corpora depend on: the order of codes within an
object, the checks of steps 1 to 8 (frame lengths of at least 1, the age
header grammar and parser limits, the round-time ceiling of
9999-12-31T23:59:59Z, the exact tlock stanza arguments), the access
pre-checks and the implementation limits.
- `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
- `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.