|
|
# 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.16
|
|
|
|
|
|
Implements the draft of the DateKeys Protocol Specification v0.16, of 7
|
|
|
October 2026, on the branch `v0.16`: the review of v0.15 by Astra, with the
|
|
|
recommendation of each decision. It changes no format; it changes the
|
|
|
verdict of a seal without accuracy and the reading of drand's JSON.
|
|
|
`SpecVersion` is 0.16, and so is the `spec` field of every file of
|
|
|
`testdata`, also of the frozen `security_cms.json` and `locator.json`.
|
|
|
|
|
|
- **A seal without accuracy** (§29.7, §29.11). `cms.Token` gains
|
|
|
`HasAccuracy`, `Policy` and `BTSP`. A valid seal is S4 only with accuracy
|
|
|
and t plus the accuracy before the round time; otherwise S5, whose text
|
|
|
gives its reason, `capsule.SealReason`: sealed after or too close, no
|
|
|
accuracy under the BTSP policy of ETSI EN 319 421, or no accuracy.
|
|
|
`Detail.SealReason` and `SignerLine.Reason` carry it, and the line of a
|
|
|
signer of F6 whose seal does not prove it says «sin acreditar que fuera
|
|
|
antes de la fecha de apertura» and the reason. `EncryptFiles` returns the
|
|
|
verdicts of the area it wrote in `Result.Security`, so that a writer warns
|
|
|
of a seal without accuracy (§62.1 rule 19). `security_cms.json` is made
|
|
|
again: 143 cases with `seal_reason`.
|
|
|
- **drand's JSON, strict** (§47.1). `provider.ParseDrandJSON`, the one
|
|
|
reader of it, for a release in hand and for the answers of the relays:
|
|
|
no object repeats a name, names compared exactly once their escapes are
|
|
|
decoded, no lone surrogate, and round a number without sign, fraction or
|
|
|
exponent from 1 to 2^53 - 1. `encoding/json` kept the last of two repeated
|
|
|
names and matched `ROUND` to `round`. `release.json` gains 25 cases.
|
|
|
- **The key of words in the annex** (§79, 79.7). `scripts/recovery` opens
|
|
|
with `-words FILE`, with the normalization without tables of the annex or,
|
|
|
with `-unicodedata FILE`, the full one from `UnicodeData.txt` of Unicode
|
|
|
18.0.0, checked by its SHA-256. The fixture `format3_time_and_key_words`
|
|
|
opens with the text of the vector of the annex, kept in `words_text`.
|
|
|
- **A full last chunk** (79.5). The fixture `format3_full_chunk`, whose
|
|
|
`PAYLOAD_AGE` is one full STREAM chunk; `scripts/recovery_check.sh` opens
|
|
|
it, and the one with words.
|
|
|
- **The annex.** `annex/recovery.md` is §79 of the draft v0.16, with the
|
|
|
key of words in 79.7.
|
|
|
|
|
|
## Unreleased — specification v0.15
|
|
|
|
|
|
Implements the DateKeys Protocol Specification v0.15, which its author
|
|
|
approved on 7 October 2026 with the recommendation of each of its decisions,
|
|
|
tagged `spec-v0.15`: the long-term recovery of capsules. It changes no format
|
|
|
of `.dkc` or `.dkk`, and one verdict: a valid release in the caller's hand
|
|
|
opens a capsule even when the clock is before the round time.
|
|
|
|
|
|
- **Random words.** `wordkey.Generate` draws a key of words uniformly from
|
|
|
a built-in list, and `encrypt -new-words FILE [-dic LIST] [-word-count N]`
|
|
|
writes them to a new file: 7 words by default, of the Spanish list
|
|
|
`wordkey/lists/es.txt`, 7776 words, a draft not yet reviewed (spec §38.1,
|
|
|
the SHOULD to offer random words). `wordkey.List` and `CheckList(lang,
|
|
|
words)` refuse a list of fewer than 2048 words, with two words that are
|
|
|
one once normalized, or with a character that is not a letter of the
|
|
|
alphabet of its language, which only the code gives (for `es`, `a` to `z`,
|
|
|
`á`, `é`, `í`, `ó`, `ú`, `ü` and `ñ`): a Cyrillic letter that looks like a
|
|
|
Latin one would be typed again with the Latin one, and the capsule would
|
|
|
not open. `wordkey.Bits` is the strength of the words drawn, which
|
|
|
`encrypt` prints. The list is CC BY-SA 4.0, an adaptation of
|
|
|
FrequencyWords; `wordkey/lists/README.md` records its source, method and
|
|
|
SHA-256. It changes no format and no derivation.
|
|
|
|
|
|
- **The English list.** `wordkey/lists/en.txt` is the large wordlist of the
|
|
|
EFF, 7776 words, CC BY 4.0, without its dice numbers and in its order, so
|
|
|
that the position of a word still gives them; `-dic` takes it by default.
|
|
|
The alphabet of `en` is `a` to `z` and the ASCII hyphen of its four
|
|
|
compound words, such as `t-shirt`.
|
|
|
|
|
|
- **What the official SDK says when it seals** (spec §7.6, §62.1 rules 26
|
|
|
and 27, §71). `encrypt` writes next to the `.dkc` the recovery annex,
|
|
|
`FILE.dkc.recuperacion.txt` (`datekeys.RecoveryAnnex`, §79 of the
|
|
|
specification under a title with its version and SHA-256, the same for
|
|
|
every capsule; `-no-recovery` leaves it out), and says what opening the
|
|
|
capsule years later will take: the `.dkc`, a credential of a
|
|
|
`time_and_key` capsule, and the release of its round, which an archive
|
|
|
of releases or a cache service must keep if drand no longer serves it.
|
|
|
Beyond one year, `time_only` gets the recommendation of `time_and_key`.
|
|
|
`profile.Status` and `StatusOf` give the state of a pinned profile in
|
|
|
the registry of §71, which DateKeys does not publish yet (Quicknet is
|
|
|
active); `encrypt` writes no capsule with a profile that is not active,
|
|
|
and `decrypt` and `inspect` warn when the profile of a capsule is
|
|
|
compromised.
|
|
|
|
|
|
- **The license of the specification** is CC-BY-ND-4.0, as its author
|
|
|
decided: it may be copied and shared unchanged, with credit, and a
|
|
|
modified version or a translation needs the written permission of its
|
|
|
author. The
|
|
|
title of the recovery annex says so, since the annex travels alone next
|
|
|
to every capsule. The code stays Apache-2.0, and the word lists keep
|
|
|
their own licenses.
|
|
|
|
|
|
- **Dice.** For whoever does not trust the random numbers of a computer,
|
|
|
as the author decided: five dice for each word give a number from 11111
|
|
|
to 66666, its position in a list of 7776 words. `wordkey.DiceNumber`,
|
|
|
`DiceWord`, `DiceWords` (at least 6 numbers, never the same word twice)
|
|
|
and `DiceList`, the list numbered for dice as the EFF publishes its own:
|
|
|
for `en` it is the file of the EFF, byte for byte. `encrypt -dice TEXT`
|
|
|
and `-dice-file FILE` take the words from dice and show them, and
|
|
|
`datekeys wordlist [-dic LIST]` writes the numbered list, to print it,
|
|
|
and its SHA-256, which `wordkey/lists/README.md` records.
|
|
|
|
|
|
- **Approval.** `SpecVersion` is 0.15, and so is the `spec` field of every
|
|
|
file of `testdata`: the records and the vectors, regenerated, and the
|
|
|
frozen `security_cms.json` and `locator.json`, whose `spec` field alone
|
|
|
changes. The text approved is the draft with its date; `spec/README.md`
|
|
|
records its SHA-256.
|
|
|
|
|
|
- **Specification.** `spec/DateKeys_Protocol_Specification_v0.15.md`, whose
|
|
|
§76 lists six changes with their cases: the release object (§47.1, with
|
|
|
§45, §47 and §57), its chain hash at step 10 (§63, §69.1), step 9.c
|
|
|
only before a network request (§49, §63, §70), the long-term recovery
|
|
|
(§50, on archives and cache services that keep the releases of all
|
|
|
rounds; §53; §62.1 rules 26 and 27; and the informative annex §79), the
|
|
|
Release API (§45) and §74. `datekeys.cddl` adds the rule `release`.
|
|
|
- **No `.dkr` file.** The first draft kept the release of a capsule in a
|
|
|
`.dkr` file next to it; the author removed it on 7 October 2026: the
|
|
|
release does not exist when the capsule is made, and once the date comes
|
|
|
the capsule can be opened, so a saved release only opens it again and does
|
|
|
not help whoever opens it decades later. Gone with it: the extension, rule
|
|
|
28 of §62.1, `decrypt -save-release` and the command `datekeys release`.
|
|
|
- **Release object.** `provider.EncodeRelease`, `DecodeRelease` and
|
|
|
`ParseRelease`, which also reads drand's JSON; `provider.Verify` checks the
|
|
|
chain hash a release names, `ERR_PROFILE_MISMATCH`, before its round and its
|
|
|
signature. `provider.Archive` reads a local release archive, the
|
|
|
informative format of §50.
|
|
|
- **A release in hand.** `capsule.OpenOptions.Release`, a
|
|
|
`provider.Supplier` exclusive with `Source`, is not compared with the clock
|
|
|
(step 9.c, option B); `Opened.ClockBehind` says when the clock was before
|
|
|
the round time, and `Opened.Release` carries the chain hash, ready for
|
|
|
`provider.EncodeRelease`. A network source is still never asked before the
|
|
|
round time.
|
|
|
- **CLI.** `datekeys decrypt -release FILE` takes a release object,
|
|
|
drand's JSON or a local archive, with no network request.
|
|
|
- **Test data.** `vectors/release.json`, new, from
|
|
|
`internal/testkit.ReleaseVectors`; `releases/<round>.cbor` for rounds 1000,
|
|
|
1001, 1004 and 2000, and `releases/archive_1000_1004.bin`. In
|
|
|
`mutations.json` every case has the field `source`, `supplied` or
|
|
|
`network`; "round not reached yet", a release in hand with a clock 1 ns
|
|
|
before the round time, was `ERR_RELEASE_UNAVAILABLE` at step 9 and now
|
|
|
opens; four cases are new: the same with a network source, a release of
|
|
|
another round from a network source, and two release objects of another
|
|
|
chain. No other case changes its result.
|
|
|
- **Recovery check.** `scripts/recovery`, a program that opens a capsule by
|
|
|
following the annex, with no code of this module, tlock or drand, and
|
|
|
`scripts/recovery_check.sh`, which `scripts/check.sh` runs on a
|
|
|
`time_only` and a `time_and_key` fixture of format 3.
|
|
|
|
|
|
## Unreleased — specification v0.14
|
|
|
|
|
|
Implements the DateKeys Protocol Specification v0.14, which its author
|
|
|
approved on 6 October 2026 with the recommendation of each of its ten
|
|
|
decisions, tagged `spec-v0.14`. It changes no format and no verdict of a
|
|
|
Quicknet capsule.
|
|
|
|
|
|
- **Approval.** `SpecVersion` is 0.14, and so is the `spec` field of every
|
|
|
file of `testdata`: the records and the vectors, regenerated, and the
|
|
|
frozen `security_cms.json` and `locator.json`, whose `spec` field alone
|
|
|
changes. The text approved is the draft with decision 8 applied, and its
|
|
|
date; `spec/README.md` records its SHA-256.
|
|
|
|
|
|
- **Specification.** `spec/DateKeys_Protocol_Specification_v0.14.md`, whose
|
|
|
§76 lists seven changes with their cases: what the protocol does not
|
|
|
guarantee (§5), the provider and the states of a profile (§7.6, §36.1,
|
|
|
§71), signatures and seals against a quantum adversary (§7.7, §53), the web
|
|
|
client (§7.10, §59), the entropy of a key of words (§38.1), the root of
|
|
|
trust byte for byte (§12, §35, §51, §63 steps 10 and 11), and errata.
|
|
|
- **One scheme of drand** (§12.1, §76 change 7, decision 8 of the author,
|
|
|
6 October 2026). `profile.Validate` admits only `bls-unchained-g1-rfc9380`:
|
|
|
a profile of `pedersen-bls-unchained` or `bls-unchained-on-g1` is
|
|
|
`ERR_UNKNOWN_PROFILE`. No pinned profile changes, and no vector of
|
|
|
`testdata`.
|
|
|
- **Test data.** `vectors/tlock_steps.json`, new, from
|
|
|
`internal/testkit.TlockStepVectors`: for the published rounds 1000, 1001,
|
|
|
1004 and 2000, the message a round signs, its hash to G1 and the pairing
|
|
|
equation of step 10; and the decryption of five tlock stanzas in step 11,
|
|
|
with e(signature, U), H2, sigma, H4, the file key, every try of H3 and r.
|
|
|
The generator computes each value with its own H2, H3 and H4 and checks it
|
|
|
against drand, kyber and tlock.
|
|
|
|
|
|
## Unreleased — specification v0.13
|
|
|
|
|
|
Implements the DateKeys Protocol Specification v0.13, which its author
|
|
|
approved on 6 October 2026, tagged `spec-v0.13`. It changes no format and no
|
|
|
verdict.
|
|
|
|
|
|
- **Approval.** `SpecVersion` is 0.13, and so is the `spec` field of every
|
|
|
file of `testdata`: the records and the vectors, regenerated, and the
|
|
|
frozen `security_cms.json` and `locator.json`, whose `spec` field alone
|
|
|
changes. The text approved is the draft as it stood, with only its date
|
|
|
changed; `spec/README.md` records its SHA-256.
|
|
|
|
|
|
- **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.
|
|
|
- **`ParseInfo` checks the sealed locator as far as it can before the date**
|
|
|
(§44.1, which already asked for it, at the author's request of 6 October
|
|
|
2026): the tlock stanza carries the round of the DateKey and a chain hash
|
|
|
in lower-case hexadecimal, the chain of the profile of the DateKey when
|
|
|
this module pins it, and the body after the age header holds a plaintext of
|
|
|
4096 bytes or a multiple, so that a header without a body is refused. Each
|
|
|
is `ERR_EXTENSION_DATA_INVALID` with its own text, and `Info.Extension`,
|
|
|
which reads what it writes, refuses them too, and a DateKey of a profile
|
|
|
that this module does not pin. `locator.Open` still reads at most 1 MiB, by
|
|
|
the author's decision. `TestInfoSealedForm`.
|
|
|
- **Two addresses that the text of v0.12 already refused**, and the
|
|
|
reference accepted (§44.1): a CID with a character more, of zero bits,
|
|
|
which decodes to the same bytes and is not its canonical form; and
|
|
|
`https://[[2000::]/`, whose brackets `checkHost` trimmed all at once.
|
|
|
`isCIDv1` refuses 5 or more bits left over, and `checkHost` takes one pair
|
|
|
of brackets. No normative change: `TestAddressCanonicalForms`.
|
|
|
- **Fuzzing.** `FuzzCheckResolvedIP`, the 26th target of `scripts/fuzz.sh`:
|
|
|
an address that `CheckResolvedIP` accepts is public, or holds a public
|
|
|
IPv4 address at the positions of RFC 6052 in the NAT64 prefix that
|
|
|
contains it, whatever the prefix given.
|
|
|
- **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
|
|
|
approved on 6 October 2026, tagged `spec-v0.12`. It fixes what the review of
|
|
|
the implementation of v0.11 found on 2 October 2026, and changes no format: a
|
|
|
reader of v0.11 opens these capsules, and this one opens those of v0.11.
|
|
|
|
|
|
- **Approval.** `SpecVersion` is 0.12, and so is the `spec` field of every
|
|
|
file of `testdata`: the records of the fixtures and the vectors, regenerated,
|
|
|
and the frozen `security_cms.json` and `locator.json`, whose `spec` field
|
|
|
alone changes. The text approved is the draft as it stood, with only its
|
|
|
date changed; `spec/README.md` records its SHA-256.
|
|
|
|
|
|
- **Review of 2 October.** Six adversarial reviews of the implementation of
|
|
|
v0.11 found nothing blocking: what is signed, the strict Ed25519, the CMS
|
|
|
and RFC 3161 checks and the envelope hold, and the failures were at the
|
|
|
edges. What the text of v0.11 already asked for is fixed in the code, as
|
|
|
this entry says; what it left open or got wrong goes to the draft.
|
|
|
- A typed nil in `EncryptOptions.AuthorKey`, `CMSSigner` or `Sealer` is an
|
|
|
error, never a capsule without the signature or the seal that was asked
|
|
|
for.
|
|
|
- A panic while evaluating the signature or the seal fails only that part,
|
|
|
F1 or S2, not both.
|
|
|
- `OpenOptions.Accept` sees the verdicts after step 17 and before step 18,
|
|
|
and can refuse to publish the files.
|
|
|
- `extension.CheckWrite`, the rule of encoders of spec §72: the writers of
|
|
|
capsules and `.dkk` files refuse `datekeys.note` and `datekeys.capsule`
|
|
|
outside the arrays where they are registered, or with invalid data.
|
|
|
- `authorkey.Key.String` and `GoString` hide the secret key, which only
|
|
|
`Key.Secret` returns. `ParsePublic` refuses a key that is not a point of
|
|
|
the curve (`ed25519strict.OnCurve`).
|
|
|
- `Header.UnusableNote` tells a public note that breaks the rules of text
|
|
|
from no note.
|
|
|
- **Specification.** `spec/DateKeys_Protocol_Specification_v0.12.md`, a draft
|
|
|
whose §76 lists each change with its case: the names of certificates in the
|
|
|
verdicts, between « and », shown only with at most 64 code points and no two
|
|
|
spaces in a row; the authority of each seal in the lines of F6, with the
|
|
|
warning that nobody checks who issued it; the holder by `givenName` and
|
|
|
`surname` before the `commonName`, which in the certificates of the FNMT
|
|
|
carries the NIF, when both have text that is not empty, and the issuer by
|
|
|
its `commonName` or, without one that has text, its `organizationName`;
|
|
|
the profile of the certificate field by field; object
|
|
|
identifiers by their bytes, repeated elements of a SET OF and the edge cases
|
|
|
of the token; the addresses and the padding of the locator; and the errata
|
|
|
of §44.1, §55.2, §64, §67 and §76. The CDDL fixes the sizes of the locator.
|
|
|
`Verdicts.Lines` follows it, and writes the result of a foreign signer in
|
|
|
Spanish.
|
|
|
- **CMS reader with a profile of its own.** `internal/cms` reads certificates
|
|
|
field by field, as the profile of §29.10 says, instead of with
|
|
|
`encoding/asn1` and `crypto/x509`, so that a second implementation reads
|
|
|
them the same; a certificate that breaks it decides nothing unless a
|
|
|
`SignerInfo` names it. The text of a name comes only from UTF8String,
|
|
|
PrintableString, IA5String, TeletexString in ASCII and BMPString without
|
|
|
surrogates, with nothing removed. The key is RSA with NULL parameters and
|
|
|
an odd modulus, or EC uncompressed on P-256, P-384 and P-521. Object
|
|
|
identifiers are compared by the bytes of their DER, so an arc of 2^31 or
|
|
|
more no longer makes an attribute that decides nothing fail the signature;
|
|
|
a SET OF may repeat an element, and two copies of a certificate are one; a
|
|
|
key of another scheme than its algorithm is F2; a `messageImprint` of
|
|
|
another length is S3; the `crls` of a token decide nothing. `internal/der`
|
|
|
checks UTCTime and GeneralizedTime in their forms of X.690, a date that
|
|
|
exists, and millis and micros as minimal INTEGERs.
|
|
|
- **Addresses of the locator.** A reader rejects an address that breaks §44.1
|
|
|
and uses the others (`Locator.Usable`); a writer never writes one. Addresses
|
|
|
refuse the special-purpose blocks of IANA, IPv6 outside 2000::/3,
|
|
|
`localhost` and local names, characters outside RFC 3986, dot segments, and
|
|
|
a CID that does not decode to version 1 and a multihash. A port with a
|
|
|
leading zero is refused too, as the draft asks, which also writes the rules
|
|
|
that the reference applied without a text: segments of 1 to 63 characters
|
|
|
that neither start nor end with a hyphen, the scheme in lower case, `0x`
|
|
|
and local names in either case, an IPv4 without leading zeros and a CID in
|
|
|
canonical form. `ParseInfo` checks
|
|
|
that the locator is an age file with one tlock stanza for the round of its
|
|
|
DateKey, `Info.Extension` reads what it writes, and `Info.OpenLocator`
|
|
|
refuses a nil registry. The errors of `Open` and `Unmarshal` carry no
|
|
|
normative code.
|
|
|
- **CLI.** `encrypt -sign` shows the author key and the code of
|
|
|
`AUTHOR_MESSAGE` before it signs (spec §62.1 rule 20).
|
|
|
`decrypt -expect-author` compares the key of an F4, and writes nothing
|
|
|
unless it matches. `decrypt` and `inspect` say when a public note is not
|
|
|
shown because it breaks the rules of text; `inspect -json` gives
|
|
|
`public_note_unusable`. The lines of the verdicts break at the last space
|
|
|
that fits, each row after the first behind ` ↳ `, so that the terminal
|
|
|
never breaks them. `encrypt` reports L as the length of the payload, not of
|
|
|
the content.
|
|
|
- **Tables for Dart.** `internal/pathrule/gen -dart` writes the Unicode and
|
|
|
best-fit tables as a Dart library for `datekeys-dart`, the third
|
|
|
implementation, as `-ts` does for `datekeys-ts`: the same lists and the same
|
|
|
`TablesDigest`. The tables and the TypeScript module do not change.
|
|
|
- **Test data.**
|
|
|
- `genfixtures -force` writes the fixtures of v0.10 again with their area
|
|
|
of 512 bytes, through `EncryptOptions.TestAreaLen`, which needs
|
|
|
`TestVectors`; it used to rewrite them with 32 KiB and fail halfway.
|
|
|
- `format3_seal_unsupported` uses `seal_type` 4294967295, reserved for
|
|
|
tests, instead of `seal_type` 1, which a later version may define;
|
|
|
`capsule.AlgTest` and `SealTypeTest` name the two values.
|
|
|
- `security.json` carries the context of a capsule, its commitments and the
|
|
|
time of its round, and each case the verdicts and the lines of a reader of
|
|
|
this version: a signature of `alg` 1 that does not verify is F2, a token
|
|
|
of `seal_type` 2 that is not DER is S2, and new cases give a valid
|
|
|
signature of `alg` 1 (F4), and `alg` and `seal_type` 4294967295 (F1, S1).
|
|
|
24 cases.
|
|
|
- `note.json`, new: 16 public notes, those that pass and those that a
|
|
|
writer refuses and a reader does not show, each with the text of the rule
|
|
|
it breaks.
|
|
|
- New fixtures `format3_unsigned`, the capsule of `format3_signed` without
|
|
|
its signature, with the same P, and `format3_note`, with a public note:
|
|
|
26 capsules, 14 of format 3. The records of `format3_signed_cms` and
|
|
|
`format3_sealed` give the lines of v0.12.
|
|
|
- `mutations.json` gains 8 mutations of the list of v0.11 of §64: the
|
|
|
signature of `alg` 1 altered (F2), removed (F0), made again with another
|
|
|
key (F4 of that key) and transplanted to another capsule (F2); a key of 31
|
|
|
bytes and a signature of 65 (F1); the area widened to 64 KiB after signing
|
|
|
(F4, the same `AUTHOR_MESSAGE`); and the public note changed
|
|
|
(`ERR_HEADER_BINDING`, step 15). The signature of `alg` 1 that does not
|
|
|
verify (F2) is now a case of §64. 218 cases, 178 of the spec.
|
|
|
- `security_cms.json`, made again with the profile of v0.12: 135 cases,
|
|
|
each with the lines of `Verdicts.Lines`, for each row of §29.7 and each
|
|
|
item of the lists of §64 of v0.11 and v0.12: the names of the holder and
|
|
|
of the issuer, the profile of the certificate, identifiers, repetitions,
|
|
|
every hash and curve of the table, and the edges of the token. The two
|
|
|
cases without a context, which gave the verdicts of a reader of v0.10,
|
|
|
are gone.
|
|
|
- `locator.json`, made again with the cases of §64 of v0.11 and v0.12: the
|
|
|
first and the last address of each IPv4 block with their public
|
|
|
neighbours, the IPv6 blocks, local names, characters outside RFC 3986, dot
|
|
|
segments and base32 that is not a CID v1, and what the draft fixes about
|
|
|
segments, case, leading zeros and the form of a CID; a locator whose
|
|
|
`http` and NAT64 addresses are rejected and whose third is used; resources
|
|
|
of the rest, with bytes after it, changed, cut or at another offset; data
|
|
|
of the extension that a reader cannot use, a locator for another round
|
|
|
among them; and plaintexts with one defect each, the bases 4094, 4070 and
|
|
|
3837 completed to 4096 by a key 6 that is empty or not in its shortest
|
|
|
form (change 7) among them. The bases 3837, 4070, 4094 and 4095 and those
|
|
|
of the next multiple join `padding_cases`.
|
|
|
- `wordkey.json`, new: the key of words of §38.1, which §64 of v0.11
|
|
|
asks for and was only in the tests of `wordkey`. 45 texts and their
|
|
|
words, among them each space of the list and three that are not; 20
|
|
|
texts and what a writer does with them, the zero width space that it
|
|
|
refuses among them; and 6 identities with their recipients, the vector
|
|
|
of §38.1 and those of the next round, another `capsule_id`, another
|
|
|
chain hash, round 2^53 − 1 and words that are not ASCII. For
|
|
|
`datekeys-ts` and `datekeys-dart`.
|
|
|
- `testdata/README.md` describes the files of v0.11 and of the draft.
|
|
|
|
|
|
## Unreleased — specification v0.11
|
|
|
|
|
|
Moves the module to the DateKeys Protocol Specification v0.11, approved by its
|
|
|
author on 1 October 2026 and tagged `spec-v0.11`, which adds to format 3 what
|
|
|
the v0.10 reserved, without changing any format: a reader of v0.10 opens these
|
|
|
capsules. `SpecVersion` is 0.11.
|
|
|
|
|
|
- **Area of 32 KiB.** `capsule.AreaLen` is 32768, and `LargeAreaLen`, 65536,
|
|
|
which `EncryptOptions.LargeArea` lets the writer use, after the signatures
|
|
|
are made, only when what they produced does not fit in 32 KiB. The fixtures of v0.10 keep their 512 bytes
|
|
|
(`AreaUnit`), which a reader accepts.
|
|
|
- **Key of words.** Package `wordkey` derives a credential of `time_and_key`
|
|
|
from six or more words that the person chooses, with PBKDF2-HMAC-SHA256 of
|
|
|
600 000 iterations salted with the chain, the round and `capsule_id`
|
|
|
(§38.1); the words are normalised with the tables of Unicode 18.0.0.
|
|
|
`EncryptOptions.Words` adds it as one more credential, and the CLI takes
|
|
|
`-words` and `-words-file` in `encrypt` and `decrypt`.
|
|
|
- **Author signature, `alg` 1.** `internal/ed25519strict` verifies with the
|
|
|
strict profile of §29.9 and `authorkey` keeps the keys `dkauthor1…`.
|
|
|
`EncryptOptions.AuthorKey` signs, inside the writer and after its checks;
|
|
|
`OpenOptions.AuthorKeys` are the keys the person saved; `Verdicts` gives F2,
|
|
|
F3 and F4. `capsule.PayloadCommit`, `ControlCommit`, `HeadDigest`,
|
|
|
`AuthorMessage` and `SealSubject` compute what is signed.
|
|
|
- **Signature with certificates, `alg` 2.** `internal/cms` and `internal/der`
|
|
|
read the CMS signature and the RFC 3161 token of §29.10 and §29.11 with the
|
|
|
standard library only, a closed table of algorithms, and DER checked byte by
|
|
|
byte. `EncryptOptions.CMSSigner` gets `AUTHOR_MESSAGE` and returns what the
|
|
|
person signed outside; the writer checks it and writes nothing unless it is
|
|
|
F6. The reader gives F1, F2, F5 and F6 and names the signers.
|
|
|
- **Time seal, `seal_type` 2.** `EncryptOptions.Sealer` asks for the token
|
|
|
over `SEAL_SUBJECT`; the reader gives S1 to S5 and the authority of a valid
|
|
|
seal.
|
|
|
- **Public note.** `EncryptOptions.PublicNote` writes `datekeys.note`;
|
|
|
`Header.PublicNote` reads it; `extension.Standard` registers it.
|
|
|
- **Locator and envelope.** Package `locator`: the data of `datekeys.capsule`,
|
|
|
the locator sealed with tlock, and the envelope split into a header and a
|
|
|
rest that can hide inside another file.
|
|
|
- **Test data.** `format3_signed` and the vectors of its signature, and the
|
|
|
fixtures `format3_signature_unsupported` and `format3_seal_unsupported`
|
|
|
remade with `alg` 4294967295.
|
|
|
- **Review.** Three independent reviews, of the CMS reader, of the signing
|
|
|
logic and writer, and of the locator and the CLI, fixed the form of the
|
|
|
signature and of the TSTInfo field by field, the area decided after the
|
|
|
signatures, the text of an issuer on screen and the rules of the addresses;
|
|
|
spec §76 item 8 records them. Under a valid seal, `decrypt` shows an mtime
|
|
|
later than the seal as an inconsistency (§29.7).
|
|
|
- **CLI.** `author keygen` and `author public`, `encrypt -sign`, `-note` and
|
|
|
`-large-area`, and `decrypt -expect-author`. Passphrases come from a file or
|
|
|
from the standard input.
|
|
|
|
|
|
## Unreleased — specification v0.10
|
|
|
|
|
|
Moves the module to the DateKeys Protocol Specification v0.10, which adds
|
|
|
capsule format 3, delivery 1 of its design: a capsule holds files, with
|
|
|
their paths, sizes, SHA-256 and modification times, a comment and a
|
|
|
declared author, and a security area that later versions fill with an
|
|
|
author signature and a time seal. `EncryptFiles` writes format 3; `Open`
|
|
|
and `Inspect` read the three formats, and capsules of formats 1 and 2 keep
|
|
|
their verdicts.
|
|
|
|
|
|
### Format 3
|
|
|
|
|
|
- `VERSION` 3 in the PRELUDE and CONTROL_CBOR of schema version 3, with the
|
|
|
keys of version 2: L is the length of BODY (spec §22, §31).
|
|
|
- BODY (spec §29.2): a frame of three uint32, `AREA_LEN`, `SECURITY_LEN` and
|
|
|
`HEAD_LEN`, the security area, 512 bytes when written and from 512 to
|
|
|
65536 when read, the head and the files. A violation of the frame, L
|
|
|
under 12 included, or a byte of the area after SECURITY_CBOR that is not
|
|
|
zero, is `ERR_INTEGRITY` at step 17. `capsule.BodyFrame`,
|
|
|
`ParseBodyFrame`, `CheckArea`.
|
|
|
- Security (spec §29.3, §29.7): a map of version 1 whose keys 2 and 3 are
|
|
|
byte strings holding the author signature and the seal, encoded apart.
|
|
|
It never fails and never decides the opening: `capsule.EvaluateSecurity`
|
|
|
gives the verdicts X, F0, F1, S0, S1 and S2, and `Verdicts.Lines` the
|
|
|
texts of the table, in Spanish. This version implements no `alg` and no
|
|
|
`seal_type`.
|
|
|
- The head (spec §29.4): its type tag and version 1, then the CDDL with R1
|
|
|
and R8, then, in key order, the comment and the declared author, each
|
|
|
file with R2 to R6c, R10 and its layout, R7 and R9 over the tree, and the
|
|
|
critical extensions. A violation of a rule of layer 4 is the new
|
|
|
`ERR_HEAD_INVALID`. `capsule.Head`, `File`, `EncodeHead`, `DecodeHead`,
|
|
|
and `extension.Head` for its extensions.
|
|
|
- Paths and texts (spec §29.5, §29.6, §29.5.1): `internal/pathrule` applies
|
|
|
the rules with tables generated from 19 pinned data files, Unicode 18.0.0
|
|
|
and the 15 WindowsBestFit tables, never with the Unicode functions of the
|
|
|
platform; `TablesDigest` pins them. Its errors name the rule and the
|
|
|
character, never echo the text, and read the same in every
|
|
|
implementation. R4b and the invisibles rule of §29.6 keep ZWJ, ZWNJ,
|
|
|
VS15 and VS16 to their emoji and script uses.
|
|
|
|
|
|
### Reader (spec §63 step 17, §56, §57)
|
|
|
|
|
|
- `capsule.Sink` receives the files: `Begin` with the validated head,
|
|
|
`Create` for each file, `Commit` at step 18 only, and `Abort` once after
|
|
|
any failure that follows `Begin`. A format 3 capsule without
|
|
|
`OpenOptions.Sink` fails right after step 2 with `capsule.ErrSinkRequired`,
|
|
|
a caller error without a code, before any request; a capsule of format 1
|
|
|
or 2 without `dst` fails there too.
|
|
|
- Step 17 in its substeps: the frame and the area, security, the head, the
|
|
|
files filling CONTENT, the SHA-256 of each file and the padding. A failure
|
|
|
of age, or a plaintext whose length is not P, prevails; otherwise the
|
|
|
first substep that fails decides, and a code other than `ERR_INTEGRITY`
|
|
|
is reported only after reading PAYLOAD_AGE to its end.
|
|
|
- Reads of BODY grow with the bytes received, never with `AREA_LEN`,
|
|
|
`HEAD_LEN` or a declared size (spec §57).
|
|
|
- `Opened` gains `Head`, `Verdicts`, `AreaLen` and `UnusableHeadExtensions`.
|
|
|
|
|
|
### Writer (spec §61, §62, §62.1)
|
|
|
|
|
|
- `capsule.EncryptFiles` writes format 3 from a list of `capsule.Source`,
|
|
|
each read twice: first to check the paths and the texts with the rules
|
|
|
of the reader, measure L with a head whose salt and SHA-256 are zero, and
|
|
|
hash each file; then to write it, failing if its size or SHA-256 changed
|
|
|
(rules 14 to 18). Files go in the byte order of their paths; the comment
|
|
|
has its CR LF turned into LF; the mtime is kept from 1970 to 9999 and
|
|
|
omitted otherwise. The head, the control and security are decoded with
|
|
|
the rules of the reader before anything is written (rule 17), and the
|
|
|
area is always 512 bytes with the empty security (rule 13).
|
|
|
- `EncryptOptions` gains `Comment`, `Author`, `HeadCritical`,
|
|
|
`HeadNoncritical` and `TestVectors`. `Encrypt` writes format 2 only with
|
|
|
`TestVectors`, for generators of test vectors (rule 1). `Result.Head` is
|
|
|
the head written.
|
|
|
|
|
|
### CLI
|
|
|
|
|
|
- `datekeys encrypt` takes `-in` several times, files and folders; a folder
|
|
|
gives its name as the first segment and is walked with `Lstat`, following
|
|
|
no link, taking regular files only and leaving out `.DS_Store`,
|
|
|
`Thumbs.db`, `desktop.ini`, `._*` and `__MACOSX`, which it reports. New
|
|
|
`-comment`, `-author` and `-no-mtime`. A pipe is no longer accepted.
|
|
|
- `datekeys decrypt` writes the files of a format 3 capsule to the new
|
|
|
folder `-out`: `os.Mkdir` claims it, only when there are files; the tree
|
|
|
is staged in `-out/.datekeys-*` through an `os.Root`, with `O_EXCL` and
|
|
|
mode 0600; the mtimes are set and each entry of the first level is moved
|
|
|
into place at step 18, and any failure removes the folder. Formats 1 and
|
|
|
2 still write a file.
|
|
|
- The presentation of spec §29.7 goes to stdout: the verdicts, the declared
|
|
|
author and the comment box with their labels, the paths, and the
|
|
|
verdicts again. Each line of the creator goes in pieces of at most W − 3
|
|
|
columns behind `│ `, counting 2 for anything but printable ASCII, with
|
|
|
its TABs expanded; W is the width of the terminal, asked through
|
|
|
`syscall` on Unix and Windows, or 80. Shortcuts, `desktop.ini`, `.git`,
|
|
|
programs and a leading dash get a warning.
|
|
|
|
|
|
### Errors
|
|
|
|
|
|
- `ERR_HEAD_INVALID` joins the catalogue, last (spec §69): 18 codes.
|
|
|
|
|
|
### Test data
|
|
|
|
|
|
- `datekeys.SpecVersion` is `0.10`, and every test data file says so.
|
|
|
- Nine fixtures of format 3 (spec §67): `format3_single`, `format3_tree`,
|
|
|
`format3_comment_only`, `format3_bloque256` and
|
|
|
`format3_time_and_key_portable`, written with `EncryptFiles`, and
|
|
|
`format3_area_1024`, `format3_security_v2`,
|
|
|
`format3_signature_unsupported` and `format3_seal_unsupported`, which only
|
|
|
a generator of test vectors writes. Their records add the area, security,
|
|
|
the head, the salt, the comment, the declared author, each file and the
|
|
|
verdicts, and their plaintext file is BODY. The .dkk of formats 2 and 3
|
|
|
join the .dkk tests (spec §68).
|
|
|
- New vectors: `paths.json`, `path_fold.json`, `head_schema.json` and
|
|
|
`security.json`; `cbor.json` gains the control of version 3.
|
|
|
- The mutation corpus gains the 33 mutations of the first two lists of
|
|
|
spec §64 on format 3 and the 47 of the list of format 3, three of which
|
|
|
open with their verdicts; format 2 gains "format 2 time_only relabeled
|
|
|
format 3", and "version changed" sets `VERSION` 4. 209 cases, 169 of the
|
|
|
spec. `testkit.Splice` gives an edit for each run of changed bytes.
|
|
|
- The differential corpus gains two bases of format 3, one per policy:
|
|
|
5110 cases, the earlier ones unchanged.
|
|
|
- The tests of the paths and of format 3 hold their invisible and combining
|
|
|
characters as Go escapes.
|
|
|
|
|
|
## 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`.
|