Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
package capsule_test
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"context"
|
|
|
|
|
"crypto/sha256"
|
|
|
|
|
"encoding/hex"
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
"errors"
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
"io"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"reflect"
|
|
|
|
|
"slices"
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"filippo.io/age"
|
|
|
|
|
|
|
|
|
|
"g.activething.com/go/DateKeys/accesskey"
|
|
|
|
|
"g.activething.com/go/DateKeys/agewrap"
|
Signature plan, step 6: the fixture format3_signed and the vectors of its signature
format3_signed is written by EncryptFiles with a test key. Its record gives
the seed of the key, control_commit, head_digest, signers_digest,
AUTHOR_MESSAGE with its code, the signature and the content of key 2, and
verdicts carries the dkauthor1 key. The conformance test recomputes all of
it from the control, the head and the security area, and signs again from
the seed. A second test changes the context, a bit of the signature or of
the key, and the key itself, and removes the signature.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
"g.activething.com/go/DateKeys/authorkey"
|
|
|
|
|
"g.activething.com/go/DateKeys/capsule"
|
|
|
|
|
"g.activething.com/go/DateKeys/extension"
|
|
|
|
|
"g.activething.com/go/DateKeys/internal/testkit"
|
|
|
|
|
"g.activething.com/go/DateKeys/profile"
|
|
|
|
|
"g.activething.com/go/DateKeys/provider"
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const fixtureDir = "../testdata/fixtures"
|
|
|
|
|
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
// fixtureNames are the official fixtures: the format 1 ones of spec v0.8.2,
|
|
|
|
|
// kept for compatibility, and the format 2 ones (spec §67).
|
|
|
|
|
var fixtureNames = []string{
|
|
|
|
|
"time_only", "time_only_extensions", "time_and_key_portable", "time_and_key_recipients", "empty_payload",
|
|
|
|
|
"format2_time_only", "format2_time_only_bloque256", "format2_empty_payload", "format2_time_only_extensions",
|
|
|
|
|
"format2_time_and_key_portable", "format2_time_and_key_recipients", "format2_time_and_key_sixteen",
|
Format 3, step 6b: the nine fixtures of format 3
The fixtures of spec 67 for format 3, each with its record, its BODY,
its inspect output and, for time_and_key, its .dkk:
- format3_single, format3_tree (five files in three folders, one over
two STREAM chunks, one without mtime, a comment and a declared
author), format3_comment_only (no files; a TAB in the comment),
format3_bloque256 and format3_time_and_key_portable, written with
EncryptFiles;
- format3_area_1024, format3_security_v2 (verdict X),
format3_signature_unsupported (an author-signature of alg 1 with a
random key of 32 bytes and a random signature of 64: F1) and
format3_seal_unsupported (that and a seal of seal_type 1: F1 and S1),
which only a generator of test vectors writes (62.1 rule 13), built
with testkit.Build.
The record of a format 3 fixture adds the area, SECURITY_CBOR,
HEAD_CBOR, the salt, the comment, the declared author, the head
extensions, the offset of CONTENT in BODY, each file with its layout,
SHA-256 and mtime, and the verdicts with their lines; its plaintext
file is BODY. The generator writes, then recovers every value by
opening layer by layer for the three formats alike, and refreshes the
records of format 3 through a Sink.
Tests: the conformance test checks BODY, the head, security and every
file, and opens through a MemorySink; the .dkk tests take the .dkk of
formats 2 and 3 too (spec 68); the CLI decrypts five of the fixtures
into folders; the control fuzz target decodes with the three schema
versions. The differential corpus gains two bases, format3_single and
format3_time_and_key_portable, one per policy: 5110 cases, the earlier
ones unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
"format3_single", "format3_tree", "format3_comment_only", "format3_bloque256", "format3_time_and_key_portable",
|
|
|
|
|
"format3_area_1024", "format3_security_v2", "format3_signature_unsupported", "format3_seal_unsupported",
|
|
|
|
|
"format3_unsigned", "format3_signed", "format3_signed_cms", "format3_sealed", "format3_note",
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
}
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
|
|
|
|
|
type fixture struct {
|
|
|
|
|
testkit.DKCFixture
|
|
|
|
|
dkc []byte
|
|
|
|
|
plaintext []byte
|
|
|
|
|
release provider.Release
|
|
|
|
|
dkk *accesskey.AccessKey
|
|
|
|
|
ids []age.Identity
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func loadFixture(t testing.TB, name string) *fixture {
|
|
|
|
|
t.Helper()
|
|
|
|
|
f := &fixture{}
|
|
|
|
|
if err := testkit.ReadJSON(filepath.Join(fixtureDir, name+".json"), &f.DKCFixture); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
var err error
|
|
|
|
|
if f.dkc, err = os.ReadFile(filepath.Join(fixtureDir, f.File)); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if f.plaintext, err = os.ReadFile(filepath.Join(fixtureDir, f.PlaintextFile)); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
sig, err := hex.DecodeString(f.Release.Signature)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
f.release = provider.Release{Round: f.Release.Round, Signature: sig}
|
|
|
|
|
if f.AccessKeyFile != "" {
|
|
|
|
|
b, err := os.ReadFile(filepath.Join(fixtureDir, f.AccessKeyFile))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if f.dkk, err = accesskey.Decode(bytes.NewReader(b)); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, s := range f.Identities {
|
|
|
|
|
id, err := age.ParseX25519Identity(s)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
f.ids = append(f.ids, id)
|
|
|
|
|
}
|
|
|
|
|
return f
|
|
|
|
|
}
|
|
|
|
|
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
func (f *fixture) format() capsule.Format { return capsule.Format(f.Format) }
|
|
|
|
|
|
|
|
|
|
// slots is the number of INNER_ACCESS_AGE stanzas of the format of f, 0
|
|
|
|
|
// meaning one or more.
|
|
|
|
|
func (f *fixture) slots() int {
|
Format 3, step 6b: the nine fixtures of format 3
The fixtures of spec 67 for format 3, each with its record, its BODY,
its inspect output and, for time_and_key, its .dkk:
- format3_single, format3_tree (five files in three folders, one over
two STREAM chunks, one without mtime, a comment and a declared
author), format3_comment_only (no files; a TAB in the comment),
format3_bloque256 and format3_time_and_key_portable, written with
EncryptFiles;
- format3_area_1024, format3_security_v2 (verdict X),
format3_signature_unsupported (an author-signature of alg 1 with a
random key of 32 bytes and a random signature of 64: F1) and
format3_seal_unsupported (that and a seal of seal_type 1: F1 and S1),
which only a generator of test vectors writes (62.1 rule 13), built
with testkit.Build.
The record of a format 3 fixture adds the area, SECURITY_CBOR,
HEAD_CBOR, the salt, the comment, the declared author, the head
extensions, the offset of CONTENT in BODY, each file with its layout,
SHA-256 and mtime, and the verdicts with their lines; its plaintext
file is BODY. The generator writes, then recovers every value by
opening layer by layer for the three formats alike, and refreshes the
records of format 3 through a Sink.
Tests: the conformance test checks BODY, the head, security and every
file, and opens through a MemorySink; the .dkk tests take the .dkk of
formats 2 and 3 too (spec 68); the CLI decrypts five of the fixtures
into folders; the control fuzz target decodes with the three schema
versions. The differential corpus gains two bases, format3_single and
format3_time_and_key_portable, one per policy: 5110 cases, the earlier
ones unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if f.format() != capsule.Format1 {
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
return agewrap.AccessSlots
|
|
|
|
|
}
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
func (f *fixture) unlock(t testing.TB) time.Time {
|
|
|
|
|
u, err := time.Parse(time.RFC3339, f.UnlockAt)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
return u
|
|
|
|
|
}
|
|
|
|
|
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
// openOptions returns the options that open f: its release, its unlock time
|
|
|
|
|
// and, for time_and_key, its .dkk or else its identities.
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
func (f *fixture) openOptions(t testing.TB) capsule.OpenOptions {
|
Format 3, step 6b: the nine fixtures of format 3
The fixtures of spec 67 for format 3, each with its record, its BODY,
its inspect output and, for time_and_key, its .dkk:
- format3_single, format3_tree (five files in three folders, one over
two STREAM chunks, one without mtime, a comment and a declared
author), format3_comment_only (no files; a TAB in the comment),
format3_bloque256 and format3_time_and_key_portable, written with
EncryptFiles;
- format3_area_1024, format3_security_v2 (verdict X),
format3_signature_unsupported (an author-signature of alg 1 with a
random key of 32 bytes and a random signature of 64: F1) and
format3_seal_unsupported (that and a seal of seal_type 1: F1 and S1),
which only a generator of test vectors writes (62.1 rule 13), built
with testkit.Build.
The record of a format 3 fixture adds the area, SECURITY_CBOR,
HEAD_CBOR, the salt, the comment, the declared author, the head
extensions, the offset of CONTENT in BODY, each file with its layout,
SHA-256 and mtime, and the verdicts with their lines; its plaintext
file is BODY. The generator writes, then recovers every value by
opening layer by layer for the three formats alike, and refreshes the
records of format 3 through a Sink.
Tests: the conformance test checks BODY, the head, security and every
file, and opens through a MemorySink; the .dkk tests take the .dkk of
formats 2 and 3 too (spec 68); the CLI decrypts five of the fixtures
into folders; the control fuzz target decodes with the three schema
versions. The differential corpus gains two bases, format3_single and
format3_time_and_key_portable, one per policy: 5110 cases, the earlier
ones unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
o := capsule.OpenOptions{Registry: testkit.Registry(), Source: testkit.NewSource(f.release), Now: testkit.Fixed(f.unlock(t)), Sink: testkit.DiscardSink{}}
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
if f.AccessPolicy == "time_and_key" {
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if f.dkk != nil {
|
|
|
|
|
o.AccessKey = f.dkk
|
|
|
|
|
} else {
|
|
|
|
|
o.Identities = f.ids
|
|
|
|
|
}
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
}
|
|
|
|
|
return o
|
|
|
|
|
}
|
|
|
|
|
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
// credentials returns the identities that open INNER_ACCESS_AGE: the .dkk
|
|
|
|
|
// first, when there is one, then the identities of the record.
|
|
|
|
|
func (f *fixture) credentials(t testing.TB) []age.Identity {
|
|
|
|
|
var ids []age.Identity
|
|
|
|
|
if f.dkk != nil {
|
|
|
|
|
id, err := f.dkk.Identity()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
ids = append(ids, id)
|
|
|
|
|
}
|
|
|
|
|
return append(ids, f.ids...)
|
|
|
|
|
}
|
|
|
|
|
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
func decryptAge(t *testing.T, file []byte, id age.Identity) []byte {
|
|
|
|
|
t.Helper()
|
|
|
|
|
r, err := age.Decrypt(bytes.NewReader(file), id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
b, err := io.ReadAll(r)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
return b
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func stanzaList(in []capsule.StanzaInfo) []testkit.FixtureStanza {
|
|
|
|
|
out := make([]testkit.FixtureStanza, len(in))
|
|
|
|
|
for i, s := range in {
|
|
|
|
|
out[i] = testkit.FixtureStanza{Type: s.Type, Args: s.Args}
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Spec §67: conformance is shown by decrypting and verifying each official
|
|
|
|
|
// fixture and comparing every intermediate value and the plaintext.
|
|
|
|
|
func TestConformanceFixtures(t *testing.T) {
|
|
|
|
|
p := profile.Quicknet()
|
|
|
|
|
for _, name := range fixtureNames {
|
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
|
f := loadFixture(t, name)
|
|
|
|
|
if sum := sha256.Sum256(f.dkc); hex.EncodeToString(sum[:]) != f.SHA256 {
|
|
|
|
|
t.Fatal("fixture bytes changed")
|
|
|
|
|
}
|
|
|
|
|
if sum := sha256.Sum256(f.plaintext); hex.EncodeToString(sum[:]) != f.PlaintextSHA256 {
|
|
|
|
|
t.Fatal("plaintext file changed")
|
|
|
|
|
}
|
|
|
|
|
if err := provider.Verify(p, provider.Condition{Round: f.release.Round}, f.release); err != nil {
|
|
|
|
|
t.Fatalf("embedded release: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PRELUDE, PUBLIC_HEADER and the pre-unlock view (steps 1 to 8).
|
|
|
|
|
parts, err := testkit.Split(f.dkc)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if hex.EncodeToString(parts.Prelude) != f.Prelude || hex.EncodeToString(parts.Header) != f.PublicHeader {
|
|
|
|
|
t.Fatal("PRELUDE or PUBLIC_HEADER differ")
|
|
|
|
|
}
|
|
|
|
|
in, err := capsule.Inspect(bytes.NewReader(f.dkc), capsule.InspectOptions{Registry: testkit.Registry()})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if in.Prelude.Format != f.format() || in.Header.DateKey.Compact() != f.DateKey || in.Header.CapsuleIDHex() != f.CapsuleID ||
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
in.Header.Policy.String() != f.AccessPolicy || in.UnlockAt.Format(time.RFC3339) != f.UnlockAt {
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
t.Fatalf("inspection differs: format %d, %+v", in.Prelude.Format, in.Header)
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
}
|
|
|
|
|
if !reflect.DeepEqual(stanzaList(in.OuterStanzas), f.OuterStanzas) || !reflect.DeepEqual(stanzaList(in.PayloadStanzas), f.PayloadStanzas) {
|
|
|
|
|
t.Fatal("stanzas differ")
|
|
|
|
|
}
|
|
|
|
|
for _, c := range in.Checks {
|
|
|
|
|
if !c.OK || c.Step > 8 {
|
|
|
|
|
t.Fatalf("unexpected inspection check %+v", c)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var pre [capsule.PreludeSize]byte
|
|
|
|
|
copy(pre[:], parts.Prelude)
|
|
|
|
|
if b := capsule.HeaderBinding(pre, parts.Header); hex.EncodeToString(b[:]) != f.HeaderBinding {
|
|
|
|
|
t.Fatal("header_binding differs")
|
|
|
|
|
}
|
|
|
|
|
h, err := capsule.DecodeHeader(parts.Header)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if re, _ := capsule.EncodeHeader(h); !bytes.Equal(re, parts.Header) {
|
|
|
|
|
t.Fatal("EncodeHeader(DecodeHeader(x)) != x")
|
|
|
|
|
}
|
|
|
|
|
if got := fixtureExts(h.Critical, h.Noncritical); !reflect.DeepEqual(got, f.HeaderExtensions) {
|
|
|
|
|
t.Fatalf("header extensions differ:\n got %+v\nwant %+v", got, f.HeaderExtensions)
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Opening layer by layer: OUTER_TIME_AGE, INNER_ACCESS_AGE, CONTROL_CBOR.
|
|
|
|
|
timeID, _ := agewrap.NewTimeIdentity(p, f.release.Round, f.release)
|
|
|
|
|
inner := decryptAge(t, parts.Sealed, timeID)
|
|
|
|
|
control := inner
|
|
|
|
|
if f.Structure == "time_and_key" {
|
|
|
|
|
st, err := agewrap.Stanzas(bytes.NewReader(inner))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
got := make([]testkit.FixtureStanza, len(st))
|
|
|
|
|
for i, s := range st {
|
|
|
|
|
got[i] = testkit.FixtureStanza{Type: s.Type, Args: s.Args}
|
|
|
|
|
}
|
|
|
|
|
if !reflect.DeepEqual(got, f.InnerStanzas) {
|
|
|
|
|
t.Fatal("INNER_ACCESS_AGE stanzas differ")
|
|
|
|
|
}
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if err := agewrap.CheckAccessStanzas(st, f.slots()); err != nil {
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
Format 3, step 6b: the nine fixtures of format 3
The fixtures of spec 67 for format 3, each with its record, its BODY,
its inspect output and, for time_and_key, its .dkk:
- format3_single, format3_tree (five files in three folders, one over
two STREAM chunks, one without mtime, a comment and a declared
author), format3_comment_only (no files; a TAB in the comment),
format3_bloque256 and format3_time_and_key_portable, written with
EncryptFiles;
- format3_area_1024, format3_security_v2 (verdict X),
format3_signature_unsupported (an author-signature of alg 1 with a
random key of 32 bytes and a random signature of 64: F1) and
format3_seal_unsupported (that and a seal of seal_type 1: F1 and S1),
which only a generator of test vectors writes (62.1 rule 13), built
with testkit.Build.
The record of a format 3 fixture adds the area, SECURITY_CBOR,
HEAD_CBOR, the salt, the comment, the declared author, the head
extensions, the offset of CONTENT in BODY, each file with its layout,
SHA-256 and mtime, and the verdicts with their lines; its plaintext
file is BODY. The generator writes, then recovers every value by
opening layer by layer for the three formats alike, and refreshes the
records of format 3 through a Sink.
Tests: the conformance test checks BODY, the head, security and every
file, and opens through a MemorySink; the .dkk tests take the .dkk of
formats 2 and 3 too (spec 68); the CLI decrypts five of the fixtures
into folders; the control fuzz target decodes with the three schema
versions. The differential corpus gains two bases, format3_single and
format3_time_and_key_portable, one per policy: 5110 cases, the earlier
ones unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if f.format() != capsule.Format1 {
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
checkCredentialStanzas(t, f, st)
|
|
|
|
|
}
|
|
|
|
|
acc, _ := agewrap.NewAccessIdentity(f.slots(), f.credentials(t)[0])
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
control = decryptAge(t, inner, acc)
|
|
|
|
|
}
|
|
|
|
|
if hex.EncodeToString(control) != f.ControlCBOR {
|
|
|
|
|
t.Fatal("CONTROL_CBOR differs")
|
|
|
|
|
}
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
ctrl, err := capsule.DecodeControl(control, f.format())
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if hex.EncodeToString(ctrl.PayloadIdentity[:]) != f.PayloadIdentity || hex.EncodeToString(ctrl.HeaderBinding[:]) != f.HeaderBinding {
|
|
|
|
|
t.Fatal("I_PAYLOAD or header_binding in CONTROL_CBOR differ")
|
|
|
|
|
}
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if re, _ := capsule.EncodeControl(ctrl, f.format()); !bytes.Equal(re, control) {
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
t.Fatal("EncodeControl(DecodeControl(x)) != x")
|
|
|
|
|
}
|
|
|
|
|
if got := fixtureExts(ctrl.Critical, ctrl.Noncritical); !reflect.DeepEqual(got, f.ControlExt) {
|
|
|
|
|
t.Fatalf("control extensions differ:\n got %+v\nwant %+v", got, f.ControlExt)
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
}
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
|
Format 3, step 6b: the nine fixtures of format 3
The fixtures of spec 67 for format 3, each with its record, its BODY,
its inspect output and, for time_and_key, its .dkk:
- format3_single, format3_tree (five files in three folders, one over
two STREAM chunks, one without mtime, a comment and a declared
author), format3_comment_only (no files; a TAB in the comment),
format3_bloque256 and format3_time_and_key_portable, written with
EncryptFiles;
- format3_area_1024, format3_security_v2 (verdict X),
format3_signature_unsupported (an author-signature of alg 1 with a
random key of 32 bytes and a random signature of 64: F1) and
format3_seal_unsupported (that and a seal of seal_type 1: F1 and S1),
which only a generator of test vectors writes (62.1 rule 13), built
with testkit.Build.
The record of a format 3 fixture adds the area, SECURITY_CBOR,
HEAD_CBOR, the salt, the comment, the declared author, the head
extensions, the offset of CONTENT in BODY, each file with its layout,
SHA-256 and mtime, and the verdicts with their lines; its plaintext
file is BODY. The generator writes, then recovers every value by
opening layer by layer for the three formats alike, and refreshes the
records of format 3 through a Sink.
Tests: the conformance test checks BODY, the head, security and every
file, and opens through a MemorySink; the .dkk tests take the .dkk of
formats 2 and 3 too (spec 68); the CLI decrypts five of the fixtures
into folders; the control fuzz target decodes with the three schema
versions. The differential corpus gains two bases, format3_single and
format3_time_and_key_portable, one per policy: 5110 cases, the earlier
ones unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
// PAYLOAD_AGE: the content, BODY in format 3, and in formats 2 and 3
|
|
|
|
|
// its padding (spec §29.1, §29.2).
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
payloadID, _ := agewrap.NewPayloadIdentity(ctrl.PayloadIdentity[:])
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
plaintext := decryptAge(t, parts.Payload, payloadID)
|
|
|
|
|
if f.PayloadLength != uint64(len(f.plaintext)) {
|
|
|
|
|
t.Fatalf("payload_length %d, the content is %d bytes", f.PayloadLength, len(f.plaintext))
|
|
|
|
|
}
|
|
|
|
|
switch f.format() {
|
|
|
|
|
case capsule.Format1:
|
|
|
|
|
if !bytes.Equal(plaintext, f.plaintext) || ctrl.PayloadLength != 0 || ctrl.Padding != 0 || f.Padding != 0 || f.PaddedLength != 0 {
|
|
|
|
|
t.Fatal("PAYLOAD_AGE plaintext differs")
|
|
|
|
|
}
|
Format 3, step 6b: the nine fixtures of format 3
The fixtures of spec 67 for format 3, each with its record, its BODY,
its inspect output and, for time_and_key, its .dkk:
- format3_single, format3_tree (five files in three folders, one over
two STREAM chunks, one without mtime, a comment and a declared
author), format3_comment_only (no files; a TAB in the comment),
format3_bloque256 and format3_time_and_key_portable, written with
EncryptFiles;
- format3_area_1024, format3_security_v2 (verdict X),
format3_signature_unsupported (an author-signature of alg 1 with a
random key of 32 bytes and a random signature of 64: F1) and
format3_seal_unsupported (that and a seal of seal_type 1: F1 and S1),
which only a generator of test vectors writes (62.1 rule 13), built
with testkit.Build.
The record of a format 3 fixture adds the area, SECURITY_CBOR,
HEAD_CBOR, the salt, the comment, the declared author, the head
extensions, the offset of CONTENT in BODY, each file with its layout,
SHA-256 and mtime, and the verdicts with their lines; its plaintext
file is BODY. The generator writes, then recovers every value by
opening layer by layer for the three formats alike, and refreshes the
records of format 3 through a Sink.
Tests: the conformance test checks BODY, the head, security and every
file, and opens through a MemorySink; the .dkk tests take the .dkk of
formats 2 and 3 too (spec 68); the CLI decrypts five of the fixtures
into folders; the control fuzz target decodes with the three schema
versions. The differential corpus gains two bases, format3_single and
format3_time_and_key_portable, one per policy: 5110 cases, the earlier
ones unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
case capsule.Format2, capsule.Format3:
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
padded, err := capsule.PaddedLength(f.PayloadLength, capsule.Padding(f.Padding))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if ctrl.PayloadLength != f.PayloadLength || ctrl.Padding != capsule.Padding(f.Padding) || padded != f.PaddedLength ||
|
|
|
|
|
uint64(len(plaintext)) != padded || !bytes.Equal(plaintext[:f.PayloadLength], f.plaintext) ||
|
|
|
|
|
len(bytes.Trim(plaintext[f.PayloadLength:], "\x00")) != 0 {
|
|
|
|
|
t.Fatal("PAYLOAD_AGE plaintext is not the content and its padding")
|
|
|
|
|
}
|
|
|
|
|
if n := uint64(len(parts.Payload)); n != capsule.PayloadAgeLength(padded) {
|
|
|
|
|
t.Fatalf("PAYLOAD_AGE of %d bytes, P = %d gives %d", n, padded, capsule.PayloadAgeLength(padded))
|
|
|
|
|
}
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
}
|
Format 3, step 6b: the nine fixtures of format 3
The fixtures of spec 67 for format 3, each with its record, its BODY,
its inspect output and, for time_and_key, its .dkk:
- format3_single, format3_tree (five files in three folders, one over
two STREAM chunks, one without mtime, a comment and a declared
author), format3_comment_only (no files; a TAB in the comment),
format3_bloque256 and format3_time_and_key_portable, written with
EncryptFiles;
- format3_area_1024, format3_security_v2 (verdict X),
format3_signature_unsupported (an author-signature of alg 1 with a
random key of 32 bytes and a random signature of 64: F1) and
format3_seal_unsupported (that and a seal of seal_type 1: F1 and S1),
which only a generator of test vectors writes (62.1 rule 13), built
with testkit.Build.
The record of a format 3 fixture adds the area, SECURITY_CBOR,
HEAD_CBOR, the salt, the comment, the declared author, the head
extensions, the offset of CONTENT in BODY, each file with its layout,
SHA-256 and mtime, and the verdicts with their lines; its plaintext
file is BODY. The generator writes, then recovers every value by
opening layer by layer for the three formats alike, and refreshes the
records of format 3 through a Sink.
Tests: the conformance test checks BODY, the head, security and every
file, and opens through a MemorySink; the .dkk tests take the .dkk of
formats 2 and 3 too (spec 68); the CLI decrypts five of the fixtures
into folders; the control fuzz target decodes with the three schema
versions. The differential corpus gains two bases, format3_single and
format3_time_and_key_portable, one per policy: 5110 cases, the earlier
ones unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if f.format() == capsule.Format3 {
|
|
|
|
|
checkBody3(t, f)
|
|
|
|
|
}
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
|
Format 3, step 6b: the nine fixtures of format 3
The fixtures of spec 67 for format 3, each with its record, its BODY,
its inspect output and, for time_and_key, its .dkk:
- format3_single, format3_tree (five files in three folders, one over
two STREAM chunks, one without mtime, a comment and a declared
author), format3_comment_only (no files; a TAB in the comment),
format3_bloque256 and format3_time_and_key_portable, written with
EncryptFiles;
- format3_area_1024, format3_security_v2 (verdict X),
format3_signature_unsupported (an author-signature of alg 1 with a
random key of 32 bytes and a random signature of 64: F1) and
format3_seal_unsupported (that and a seal of seal_type 1: F1 and S1),
which only a generator of test vectors writes (62.1 rule 13), built
with testkit.Build.
The record of a format 3 fixture adds the area, SECURITY_CBOR,
HEAD_CBOR, the salt, the comment, the declared author, the head
extensions, the offset of CONTENT in BODY, each file with its layout,
SHA-256 and mtime, and the verdicts with their lines; its plaintext
file is BODY. The generator writes, then recovers every value by
opening layer by layer for the three formats alike, and refreshes the
records of format 3 through a Sink.
Tests: the conformance test checks BODY, the head, security and every
file, and opens through a MemorySink; the .dkk tests take the .dkk of
formats 2 and 3 too (spec 68); the CLI decrypts five of the fixtures
into folders; the control fuzz target decodes with the three schema
versions. The differential corpus gains two bases, format3_single and
format3_time_and_key_portable, one per policy: 5110 cases, the earlier
ones unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
// The complete flow through the public API, stage by stage: the
|
|
|
|
|
// content, or in format 3 the files, the head and the verdicts.
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
var out bytes.Buffer
|
Format 3, step 6b: the nine fixtures of format 3
The fixtures of spec 67 for format 3, each with its record, its BODY,
its inspect output and, for time_and_key, its .dkk:
- format3_single, format3_tree (five files in three folders, one over
two STREAM chunks, one without mtime, a comment and a declared
author), format3_comment_only (no files; a TAB in the comment),
format3_bloque256 and format3_time_and_key_portable, written with
EncryptFiles;
- format3_area_1024, format3_security_v2 (verdict X),
format3_signature_unsupported (an author-signature of alg 1 with a
random key of 32 bytes and a random signature of 64: F1) and
format3_seal_unsupported (that and a seal of seal_type 1: F1 and S1),
which only a generator of test vectors writes (62.1 rule 13), built
with testkit.Build.
The record of a format 3 fixture adds the area, SECURITY_CBOR,
HEAD_CBOR, the salt, the comment, the declared author, the head
extensions, the offset of CONTENT in BODY, each file with its layout,
SHA-256 and mtime, and the verdicts with their lines; its plaintext
file is BODY. The generator writes, then recovers every value by
opening layer by layer for the three formats alike, and refreshes the
records of format 3 through a Sink.
Tests: the conformance test checks BODY, the head, security and every
file, and opens through a MemorySink; the .dkk tests take the .dkk of
formats 2 and 3 too (spec 68); the CLI decrypts five of the fixtures
into folders; the control fuzz target decodes with the three schema
versions. The differential corpus gains two bases, format3_single and
format3_time_and_key_portable, one per policy: 5110 cases, the earlier
ones unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
o := f.openOptions(t)
|
|
|
|
|
sink := &testkit.MemorySink{}
|
|
|
|
|
o.Sink = sink
|
|
|
|
|
opened, err := capsule.Open(context.Background(), &out, bytes.NewReader(f.dkc), o)
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
Format 3, step 6b: the nine fixtures of format 3
The fixtures of spec 67 for format 3, each with its record, its BODY,
its inspect output and, for time_and_key, its .dkk:
- format3_single, format3_tree (five files in three folders, one over
two STREAM chunks, one without mtime, a comment and a declared
author), format3_comment_only (no files; a TAB in the comment),
format3_bloque256 and format3_time_and_key_portable, written with
EncryptFiles;
- format3_area_1024, format3_security_v2 (verdict X),
format3_signature_unsupported (an author-signature of alg 1 with a
random key of 32 bytes and a random signature of 64: F1) and
format3_seal_unsupported (that and a seal of seal_type 1: F1 and S1),
which only a generator of test vectors writes (62.1 rule 13), built
with testkit.Build.
The record of a format 3 fixture adds the area, SECURITY_CBOR,
HEAD_CBOR, the salt, the comment, the declared author, the head
extensions, the offset of CONTENT in BODY, each file with its layout,
SHA-256 and mtime, and the verdicts with their lines; its plaintext
file is BODY. The generator writes, then recovers every value by
opening layer by layer for the three formats alike, and refreshes the
records of format 3 through a Sink.
Tests: the conformance test checks BODY, the head, security and every
file, and opens through a MemorySink; the .dkk tests take the .dkk of
formats 2 and 3 too (spec 68); the CLI decrypts five of the fixtures
into folders; the control fuzz target decodes with the three schema
versions. The differential corpus gains two bases, format3_single and
format3_time_and_key_portable, one per policy: 5110 cases, the earlier
ones unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if f.format() == capsule.Format3 {
|
|
|
|
|
if out.Len() != 0 || !sink.Committed || len(sink.Files) != len(f.Files) {
|
|
|
|
|
t.Fatal("Open did not deliver the files to the Sink")
|
|
|
|
|
}
|
|
|
|
|
for i, r := range f.Files {
|
|
|
|
|
if !bytes.Equal(sink.Files[i], f.plaintext[f.ContentOffset+r.Start:f.ContentOffset+r.End]) {
|
|
|
|
|
t.Fatalf("file %q differs", r.Path)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
v := opened.Verdicts
|
|
|
|
|
if string(v.Signature) != f.Verdicts.Signature || string(v.Seal) != f.Verdicts.Seal || opened.AreaLen != f.AreaLen ||
|
Signature plan, step 6: the fixture format3_signed and the vectors of its signature
format3_signed is written by EncryptFiles with a test key. Its record gives
the seed of the key, control_commit, head_digest, signers_digest,
AUTHOR_MESSAGE with its code, the signature and the content of key 2, and
verdicts carries the dkauthor1 key. The conformance test recomputes all of
it from the control, the head and the security area, and signs again from
the seed. A second test changes the context, a bit of the signature or of
the key, and the key itself, and removes the signature.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
opened.Head.Comment != f.Comment || opened.Head.Author != f.Author || hex.EncodeToString(opened.Head.Salt[:]) != f.Salt ||
|
|
|
|
|
(f.Verdicts.AuthorKey != "") != (v.AuthorKey != [32]byte{}) {
|
Format 3, step 6b: the nine fixtures of format 3
The fixtures of spec 67 for format 3, each with its record, its BODY,
its inspect output and, for time_and_key, its .dkk:
- format3_single, format3_tree (five files in three folders, one over
two STREAM chunks, one without mtime, a comment and a declared
author), format3_comment_only (no files; a TAB in the comment),
format3_bloque256 and format3_time_and_key_portable, written with
EncryptFiles;
- format3_area_1024, format3_security_v2 (verdict X),
format3_signature_unsupported (an author-signature of alg 1 with a
random key of 32 bytes and a random signature of 64: F1) and
format3_seal_unsupported (that and a seal of seal_type 1: F1 and S1),
which only a generator of test vectors writes (62.1 rule 13), built
with testkit.Build.
The record of a format 3 fixture adds the area, SECURITY_CBOR,
HEAD_CBOR, the salt, the comment, the declared author, the head
extensions, the offset of CONTENT in BODY, each file with its layout,
SHA-256 and mtime, and the verdicts with their lines; its plaintext
file is BODY. The generator writes, then recovers every value by
opening layer by layer for the three formats alike, and refreshes the
records of format 3 through a Sink.
Tests: the conformance test checks BODY, the head, security and every
file, and opens through a MemorySink; the .dkk tests take the .dkk of
formats 2 and 3 too (spec 68); the CLI decrypts five of the fixtures
into folders; the control fuzz target decodes with the three schema
versions. The differential corpus gains two bases, format3_single and
format3_time_and_key_portable, one per policy: 5110 cases, the earlier
ones unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
t.Fatalf("Open reports another head or verdicts: %+v", v)
|
|
|
|
|
}
|
|
|
|
|
} else if !bytes.Equal(out.Bytes(), f.plaintext) {
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
t.Fatal("plaintext differs")
|
|
|
|
|
}
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if opened.Format != f.format() || opened.PayloadLength != f.PayloadLength ||
|
|
|
|
|
opened.Padding != capsule.Padding(f.Padding) || opened.PaddedLength != f.PaddedLength {
|
|
|
|
|
t.Fatalf("Open reports format %d, L = %d, padding %d, P = %d", opened.Format, opened.PayloadLength, opened.Padding, opened.PaddedLength)
|
|
|
|
|
}
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
var stages []testkit.FixtureStage
|
|
|
|
|
for _, c := range opened.Inspection.Checks {
|
|
|
|
|
stages = append(stages, testkit.FixtureStage{Step: c.Step, Name: c.Name, OK: c.OK, Error: c.Error})
|
|
|
|
|
}
|
|
|
|
|
if !reflect.DeepEqual(stages, f.Stages) {
|
|
|
|
|
t.Fatalf("stages differ:\n got %+v\nwant %+v", stages, f.Stages)
|
|
|
|
|
}
|
|
|
|
|
if got := fixtureExts(opened.ControlCritical, opened.ControlNoncritical); !reflect.DeepEqual(got, f.ControlExt) {
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
t.Fatal("Open does not report the control extensions")
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Test data for the second implementation: alg 2, the seal and the locator
Fixtures format3_signed_cms (alg 2, two certificates, each sealed, F6) and
format3_sealed (alg 1 and a seal of seal_type 2, F4 and S4), with the
certificates, SIGNERS, the commitments, SEAL_SUBJECT, the token and the
result of each signer in their records. vectors/security_cms.json has 22
frozen areas with their context and verdicts (F1, F2, F5, F6, S1 to S5), and
vectors/locator.json the extension datekeys.capsule with its envelope hidden
in a host, its locator sealed with tlock, the padding at the boundaries and
the rules of the addresses. The README of testdata describes all of it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
// checkSignature3 checks the record of the signature and of the seal of a
|
|
|
|
|
// format 3 fixture against what this implementation computes from the
|
|
|
|
|
// control, the head and the security of the fixture (spec v0.11, §29.8 to
|
|
|
|
|
// §29.11): the commitments, AUTHOR_MESSAGE and its code, the signature, which
|
|
|
|
|
// Ed25519 makes again from the seed of the record with alg 1, the key, and
|
|
|
|
|
// with alg 2 SIGNERS, the certificates and the result of each signer;
|
|
|
|
|
// SEAL_SUBJECT and the token of a seal. It returns the verdicts in the
|
Signature plan, step 6: the fixture format3_signed and the vectors of its signature
format3_signed is written by EncryptFiles with a test key. Its record gives
the seed of the key, control_commit, head_digest, signers_digest,
AUTHOR_MESSAGE with its code, the signature and the content of key 2, and
verdicts carries the dkauthor1 key. The conformance test recomputes all of
it from the control, the head and the security area, and signs again from
the seed. A second test changes the context, a bit of the signature or of
the key, and the key itself, and removes the signature.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
// context of the capsule.
|
|
|
|
|
func checkSignature3(t *testing.T, f *fixture, security, head []byte) capsule.Verdicts {
|
|
|
|
|
t.Helper()
|
|
|
|
|
cb, err := hex.DecodeString(f.ControlCBOR)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
c, err := capsule.DecodeControl(cb, f.format())
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
cc, err := capsule.ControlCommit(c, f.format())
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
Test data for the second implementation: alg 2, the seal and the locator
Fixtures format3_signed_cms (alg 2, two certificates, each sealed, F6) and
format3_sealed (alg 1 and a seal of seal_type 2, F4 and S4), with the
certificates, SIGNERS, the commitments, SEAL_SUBJECT, the token and the
result of each signer in their records. vectors/security_cms.json has 22
frozen areas with their context and verdicts (F1, F2, F5, F6, S1 to S5), and
vectors/locator.json the extension datekeys.capsule with its envelope hidden
in a host, its locator sealed with tlock, the padding at the boundaries and
the rules of the addresses. The README of testdata describes all of it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
hd := capsule.HeadDigest(head)
|
|
|
|
|
unlock, err := time.Parse(time.RFC3339, f.UnlockAt)
|
Signature plan, step 6: the fixture format3_signed and the vectors of its signature
format3_signed is written by EncryptFiles with a test key. Its record gives
the seed of the key, control_commit, head_digest, signers_digest,
AUTHOR_MESSAGE with its code, the signature and the content of key 2, and
verdicts carries the dkauthor1 key. The conformance test recomputes all of
it from the control, the head and the security area, and signs again from
the seed. A second test changes the context, a bit of the signature or of
the key, and the key itself, and removes the signature.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
Test data for the second implementation: alg 2, the seal and the locator
Fixtures format3_signed_cms (alg 2, two certificates, each sealed, F6) and
format3_sealed (alg 1 and a seal of seal_type 2, F4 and S4), with the
certificates, SIGNERS, the commitments, SEAL_SUBJECT, the token and the
result of each signer in their records. vectors/security_cms.json has 22
frozen areas with their context and verdicts (F1, F2, F5, F6, S1 to S5), and
vectors/locator.json the extension datekeys.capsule with its envelope hidden
in a host, its locator sealed with tlock, the padding at the boundaries and
the rules of the addresses. The README of testdata describes all of it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
v := capsule.EvaluateSecurityIn(security, &capsule.SecurityContext{ControlCommit: cc, HeadDigest: hd, RoundTime: unlock})
|
|
|
|
|
if s := f.Signature; s != nil {
|
|
|
|
|
checkSignatureRecord(t, s, security, cc, hd, v)
|
|
|
|
|
}
|
|
|
|
|
if s := f.Seal; s != nil {
|
|
|
|
|
var part []byte
|
|
|
|
|
if content, _, err := capsule.SecurityKey2(security); err == nil {
|
|
|
|
|
part = content
|
|
|
|
|
}
|
|
|
|
|
subject := capsule.SealSubject(cc, hd, capsule.SigPart(part))
|
|
|
|
|
typ, token, err := capsule.SecurityKey3(security)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if s.SealType != capsule.SealTypeRFC3161 || uint64(s.SealType) != typ || s.SealSubject != hex.EncodeToString(subject[:]) || s.Token != hex.EncodeToString(token) ||
|
|
|
|
|
v.Detail == nil || s.Holder != v.Detail.SealHolder || s.Time != v.Detail.SealTime.UTC().Format(time.RFC3339) {
|
|
|
|
|
t.Fatalf("the record of the seal differs from what is computed: %+v", s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return v
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func checkSignatureRecord(t *testing.T, s *testkit.FixtureSignature, security []byte, cc, hd [32]byte, v capsule.Verdicts) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
content, value, err := capsule.SecurityKey2(security)
|
Signature plan, step 6: the fixture format3_signed and the vectors of its signature
format3_signed is written by EncryptFiles with a test key. Its record gives
the seed of the key, control_commit, head_digest, signers_digest,
AUTHOR_MESSAGE with its code, the signature and the content of key 2, and
verdicts carries the dkauthor1 key. The conformance test recomputes all of
it from the control, the head and the security area, and signs again from
the seed. A second test changes the context, a bit of the signature or of
the key, and the key itself, and removes the signature.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
Test data for the second implementation: alg 2, the seal and the locator
Fixtures format3_signed_cms (alg 2, two certificates, each sealed, F6) and
format3_sealed (alg 1 and a seal of seal_type 2, F4 and S4), with the
certificates, SIGNERS, the commitments, SEAL_SUBJECT, the token and the
result of each signer in their records. vectors/security_cms.json has 22
frozen areas with their context and verdicts (F1, F2, F5, F6, S1 to S5), and
vectors/locator.json the extension datekeys.capsule with its envelope hidden
in a host, its locator sealed with tlock, the padding at the boundaries and
the rules of the addresses. The README of testdata describes all of it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
alg, key, _, err := capsule.DecodeAuthorSignature(content)
|
Signature plan, step 6: the fixture format3_signed and the vectors of its signature
format3_signed is written by EncryptFiles with a test key. Its record gives
the seed of the key, control_commit, head_digest, signers_digest,
AUTHOR_MESSAGE with its code, the signature and the content of key 2, and
verdicts carries the dkauthor1 key. The conformance test recomputes all of
it from the control, the head and the security area, and signs again from
the seed. A second test changes the context, a bit of the signature or of
the key, and the key itself, and removes the signature.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
Test data for the second implementation: alg 2, the seal and the locator
Fixtures format3_signed_cms (alg 2, two certificates, each sealed, F6) and
format3_sealed (alg 1 and a seal of seal_type 2, F4 and S4), with the
certificates, SIGNERS, the commitments, SEAL_SUBJECT, the token and the
result of each signer in their records. vectors/security_cms.json has 22
frozen areas with their context and verdicts (F1, F2, F5, F6, S1 to S5), and
vectors/locator.json the extension datekeys.capsule with its envelope hidden
in a host, its locator sealed with tlock, the padding at the boundaries and
the rules of the addresses. The README of testdata describes all of it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if uint64(s.Alg) != alg || s.SignatureValue != hex.EncodeToString(value) || s.SecurityKey2 != hex.EncodeToString(content) ||
|
|
|
|
|
s.ControlCommit != hex.EncodeToString(cc[:]) || s.HeadDigest != hex.EncodeToString(hd[:]) {
|
|
|
|
|
t.Fatalf("the record of the signature differs from the security area: %+v", s)
|
Signature plan, step 6: the fixture format3_signed and the vectors of its signature
format3_signed is written by EncryptFiles with a test key. Its record gives
the seed of the key, control_commit, head_digest, signers_digest,
AUTHOR_MESSAGE with its code, the signature and the content of key 2, and
verdicts carries the dkauthor1 key. The conformance test recomputes all of
it from the control, the head and the security area, and signs again from
the seed. A second test changes the context, a bit of the signature or of
the key, and the key itself, and removes the signature.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
}
|
Test data for the second implementation: alg 2, the seal and the locator
Fixtures format3_signed_cms (alg 2, two certificates, each sealed, F6) and
format3_sealed (alg 1 and a seal of seal_type 2, F4 and S4), with the
certificates, SIGNERS, the commitments, SEAL_SUBJECT, the token and the
result of each signer in their records. vectors/security_cms.json has 22
frozen areas with their context and verdicts (F1, F2, F5, F6, S1 to S5), and
vectors/locator.json the extension datekeys.capsule with its envelope hidden
in a host, its locator sealed with tlock, the padding at the boundaries and
the rules of the addresses. The README of testdata describes all of it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
var sd [32]byte
|
|
|
|
|
switch alg {
|
|
|
|
|
case capsule.AlgEd25519:
|
|
|
|
|
sd = capsule.SignersDigest(capsule.AlgEd25519, nil)
|
|
|
|
|
seed, err := hex.DecodeString(s.SecretSeed)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
k, err := authorkey.NewFromSeed(seed)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
pub, _ := authorkey.PublicString(k.Public())
|
|
|
|
|
msg := capsule.AuthorMessage(cc, hd, sd)
|
|
|
|
|
if s.AuthorKey != pub || hex.EncodeToString(k.Sign(msg)) != s.SignatureValue {
|
|
|
|
|
t.Fatalf("the key or the signature of the record: %+v", s)
|
|
|
|
|
}
|
|
|
|
|
case capsule.AlgCMS:
|
|
|
|
|
sd = capsule.SignersDigest(capsule.AlgCMS, key)
|
|
|
|
|
if s.Signers != hex.EncodeToString(key) || v.Detail == nil ||
|
|
|
|
|
!reflect.DeepEqual(s.Results, signerResults(v.Detail.Signers)) || !reflect.DeepEqual(s.Foreign, signerResults(v.Detail.Foreign)) {
|
|
|
|
|
t.Fatalf("SIGNERS or the results of the record: %+v", s)
|
|
|
|
|
}
|
|
|
|
|
if len(s.Certificates) == 0 {
|
|
|
|
|
t.Fatal("no certificates in the record")
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
t.Fatalf("alg %d", alg)
|
|
|
|
|
}
|
|
|
|
|
msg := capsule.AuthorMessage(cc, hd, sd)
|
|
|
|
|
if s.SignersDigest != hex.EncodeToString(sd[:]) || s.AuthorMessage != string(msg) || s.AuthorCode != capsule.AuthorCode(msg) {
|
|
|
|
|
t.Fatalf("SIGNERS digest or AUTHOR_MESSAGE of the record: %+v", s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func signerResults(lines []capsule.SignerLine) []testkit.FixtureSignerResult {
|
|
|
|
|
var out []testkit.FixtureSignerResult
|
|
|
|
|
for _, l := range lines {
|
|
|
|
|
r := testkit.FixtureSignerResult{Holder: l.Holder, Issuer: l.Issuer, Result: l.Result, Before: l.Before}
|
|
|
|
|
if !l.SealTime.IsZero() {
|
|
|
|
|
r.SealTime = l.SealTime.UTC().Format(time.RFC3339)
|
|
|
|
|
}
|
|
|
|
|
out = append(out, r)
|
Signature plan, step 6: the fixture format3_signed and the vectors of its signature
format3_signed is written by EncryptFiles with a test key. Its record gives
the seed of the key, control_commit, head_digest, signers_digest,
AUTHOR_MESSAGE with its code, the signature and the content of key 2, and
verdicts carries the dkauthor1 key. The conformance test recomputes all of
it from the control, the head and the security area, and signs again from
the seed. A second test changes the context, a bit of the signature or of
the key, and the key itself, and removes the signature.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
}
|
Test data for the second implementation: alg 2, the seal and the locator
Fixtures format3_signed_cms (alg 2, two certificates, each sealed, F6) and
format3_sealed (alg 1 and a seal of seal_type 2, F4 and S4), with the
certificates, SIGNERS, the commitments, SEAL_SUBJECT, the token and the
result of each signer in their records. vectors/security_cms.json has 22
frozen areas with their context and verdicts (F1, F2, F5, F6, S1 to S5), and
vectors/locator.json the extension datekeys.capsule with its envelope hidden
in a host, its locator sealed with tlock, the padding at the boundaries and
the rules of the addresses. The README of testdata describes all of it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
return out
|
Signature plan, step 6: the fixture format3_signed and the vectors of its signature
format3_signed is written by EncryptFiles with a test key. Its record gives
the seed of the key, control_commit, head_digest, signers_digest,
AUTHOR_MESSAGE with its code, the signature and the content of key 2, and
verdicts carries the dkauthor1 key. The conformance test recomputes all of
it from the control, the head and the security area, and signs again from
the seed. A second test changes the context, a bit of the signature or of
the key, and the key itself, and removes the signature.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
}
|
|
|
|
|
|
Format 3, step 6b: the nine fixtures of format 3
The fixtures of spec 67 for format 3, each with its record, its BODY,
its inspect output and, for time_and_key, its .dkk:
- format3_single, format3_tree (five files in three folders, one over
two STREAM chunks, one without mtime, a comment and a declared
author), format3_comment_only (no files; a TAB in the comment),
format3_bloque256 and format3_time_and_key_portable, written with
EncryptFiles;
- format3_area_1024, format3_security_v2 (verdict X),
format3_signature_unsupported (an author-signature of alg 1 with a
random key of 32 bytes and a random signature of 64: F1) and
format3_seal_unsupported (that and a seal of seal_type 1: F1 and S1),
which only a generator of test vectors writes (62.1 rule 13), built
with testkit.Build.
The record of a format 3 fixture adds the area, SECURITY_CBOR,
HEAD_CBOR, the salt, the comment, the declared author, the head
extensions, the offset of CONTENT in BODY, each file with its layout,
SHA-256 and mtime, and the verdicts with their lines; its plaintext
file is BODY. The generator writes, then recovers every value by
opening layer by layer for the three formats alike, and refreshes the
records of format 3 through a Sink.
Tests: the conformance test checks BODY, the head, security and every
file, and opens through a MemorySink; the .dkk tests take the .dkk of
formats 2 and 3 too (spec 68); the CLI decrypts five of the fixtures
into folders; the control fuzz target decodes with the three schema
versions. The differential corpus gains two bases, format3_single and
format3_time_and_key_portable, one per policy: 5110 cases, the earlier
ones unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
// checkBody3 checks BODY, the plaintext file of a format 3 fixture, against
|
|
|
|
|
// its record: the frame, the area, security and its verdicts, the head and
|
|
|
|
|
// each file with its SHA-256 (spec §29.2 to §29.7).
|
|
|
|
|
func checkBody3(t *testing.T, f *fixture) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
body := f.plaintext
|
|
|
|
|
l := uint64(len(body))
|
|
|
|
|
frame, err := capsule.ParseBodyFrame(body[:capsule.BodyFrameSize], l)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
area := body[capsule.BodyFrameSize : capsule.BodyFrameSize+uint64(frame.AreaLen)]
|
|
|
|
|
if err := capsule.CheckArea(area, frame.SecurityLen); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
security := area[:frame.SecurityLen]
|
|
|
|
|
offset := capsule.BodyFrameSize + uint64(frame.AreaLen) + uint64(frame.HeadLen)
|
|
|
|
|
hb := body[capsule.BodyFrameSize+uint64(frame.AreaLen) : offset]
|
|
|
|
|
if frame.AreaLen != f.AreaLen || hex.EncodeToString(security) != f.Security || hex.EncodeToString(hb) != f.Head || offset != f.ContentOffset {
|
|
|
|
|
t.Fatal("the area, security, the head or the offset of CONTENT differ")
|
|
|
|
|
}
|
|
|
|
|
v := capsule.EvaluateSecurity(security)
|
Test data for the second implementation: alg 2, the seal and the locator
Fixtures format3_signed_cms (alg 2, two certificates, each sealed, F6) and
format3_sealed (alg 1 and a seal of seal_type 2, F4 and S4), with the
certificates, SIGNERS, the commitments, SEAL_SUBJECT, the token and the
result of each signer in their records. vectors/security_cms.json has 22
frozen areas with their context and verdicts (F1, F2, F5, F6, S1 to S5), and
vectors/locator.json the extension datekeys.capsule with its envelope hidden
in a host, its locator sealed with tlock, the padding at the boundaries and
the rules of the addresses. The README of testdata describes all of it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if f.Signature != nil || f.Seal != nil {
|
Signature plan, step 6: the fixture format3_signed and the vectors of its signature
format3_signed is written by EncryptFiles with a test key. Its record gives
the seed of the key, control_commit, head_digest, signers_digest,
AUTHOR_MESSAGE with its code, the signature and the content of key 2, and
verdicts carries the dkauthor1 key. The conformance test recomputes all of
it from the control, the head and the security area, and signs again from
the seed. A second test changes the context, a bit of the signature or of
the key, and the key itself, and removes the signature.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
v = checkSignature3(t, f, security, hb)
|
|
|
|
|
}
|
Format 3, step 6b: the nine fixtures of format 3
The fixtures of spec 67 for format 3, each with its record, its BODY,
its inspect output and, for time_and_key, its .dkk:
- format3_single, format3_tree (five files in three folders, one over
two STREAM chunks, one without mtime, a comment and a declared
author), format3_comment_only (no files; a TAB in the comment),
format3_bloque256 and format3_time_and_key_portable, written with
EncryptFiles;
- format3_area_1024, format3_security_v2 (verdict X),
format3_signature_unsupported (an author-signature of alg 1 with a
random key of 32 bytes and a random signature of 64: F1) and
format3_seal_unsupported (that and a seal of seal_type 1: F1 and S1),
which only a generator of test vectors writes (62.1 rule 13), built
with testkit.Build.
The record of a format 3 fixture adds the area, SECURITY_CBOR,
HEAD_CBOR, the salt, the comment, the declared author, the head
extensions, the offset of CONTENT in BODY, each file with its layout,
SHA-256 and mtime, and the verdicts with their lines; its plaintext
file is BODY. The generator writes, then recovers every value by
opening layer by layer for the three formats alike, and refreshes the
records of format 3 through a Sink.
Tests: the conformance test checks BODY, the head, security and every
file, and opens through a MemorySink; the .dkk tests take the .dkk of
formats 2 and 3 too (spec 68); the CLI decrypts five of the fixtures
into folders; the control fuzz target decodes with the three schema
versions. The differential corpus gains two bases, format3_single and
format3_time_and_key_portable, one per policy: 5110 cases, the earlier
ones unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if f.Verdicts == nil || string(v.Signature) != f.Verdicts.Signature || string(v.Seal) != f.Verdicts.Seal || !reflect.DeepEqual(v.Lines(), f.Verdicts.Lines) {
|
|
|
|
|
t.Fatalf("verdicts %+v, recorded %+v", v, f.Verdicts)
|
|
|
|
|
}
|
|
|
|
|
h, err := capsule.DecodeHead(hb, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if re, _ := capsule.EncodeHead(h); !bytes.Equal(re, hb) {
|
|
|
|
|
t.Fatal("EncodeHead(DecodeHead(x)) != x")
|
|
|
|
|
}
|
|
|
|
|
if err := capsule.CheckHeadEnd(h, frame.ContentLength(l)); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if hex.EncodeToString(h.Salt[:]) != f.Salt || h.Comment != f.Comment || h.Author != f.Author || len(h.Files) != len(f.Files) {
|
|
|
|
|
t.Fatal("the head differs from the record")
|
|
|
|
|
}
|
|
|
|
|
if got := fixtureExts(h.Critical, h.Noncritical); !reflect.DeepEqual(got, f.HeadExtensions) {
|
|
|
|
|
t.Fatalf("head extensions differ:\n got %+v\nwant %+v", got, f.HeadExtensions)
|
|
|
|
|
}
|
|
|
|
|
for i, e := range h.Files {
|
|
|
|
|
r := f.Files[i]
|
|
|
|
|
recorded := r.MTime != nil
|
|
|
|
|
if e.Path != r.Path || e.Size != r.Size || e.Start != r.Start || e.End != r.End || hex.EncodeToString(e.SHA256[:]) != r.SHA256 ||
|
|
|
|
|
e.HasMTime != recorded || recorded && e.MTime != *r.MTime {
|
|
|
|
|
t.Fatalf("file %d: %+v, recorded %+v", i+1, e, r)
|
|
|
|
|
}
|
|
|
|
|
if sha256.Sum256(body[offset+e.Start:offset+e.End]) != e.SHA256 {
|
|
|
|
|
t.Fatalf("file %q: its bytes do not have its SHA-256", e.Path)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// checkCredentialStanzas checks the record of a time_and_key fixture of
|
|
|
|
|
// format 2 or 3: each credential opens exactly the stanza the record gives, and no
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
// credential opens any other one, which are the dummies (spec §39, §67).
|
|
|
|
|
func checkCredentialStanzas(t *testing.T, f *fixture, st []*age.Stanza) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
want := slices.Clone(f.IdentityStanzas)
|
|
|
|
|
if f.dkk != nil {
|
|
|
|
|
if f.AccessKeyStanza == nil {
|
|
|
|
|
t.Fatal("no access_key_stanza for the .dkk")
|
|
|
|
|
}
|
|
|
|
|
want = append([]int{*f.AccessKeyStanza}, want...)
|
|
|
|
|
}
|
|
|
|
|
creds := f.credentials(t)
|
|
|
|
|
if len(creds) != len(want) {
|
|
|
|
|
t.Fatalf("%d credentials, %d recorded stanzas", len(creds), len(want))
|
|
|
|
|
}
|
|
|
|
|
opened := map[int]bool{}
|
|
|
|
|
for c, id := range creds {
|
|
|
|
|
for i, s := range st {
|
|
|
|
|
fk, err := id.Unwrap([]*age.Stanza{s})
|
|
|
|
|
if errors.Is(err, age.ErrIncorrectIdentity) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if err != nil || i != want[c] || opened[i] {
|
|
|
|
|
t.Fatalf("credential %d opens stanza %d, recorded %d: %v", c, i, want[c], err)
|
|
|
|
|
}
|
|
|
|
|
opened[i] = true
|
|
|
|
|
clear(fk)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if len(opened) != len(creds) {
|
|
|
|
|
t.Fatalf("%d credentials open %d stanzas", len(creds), len(opened))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// fixtureExts lists extensions as the fixtures record them, with the exact
|
|
|
|
|
// data bytes (spec §67, §68).
|
|
|
|
|
func fixtureExts(critical, noncritical []extension.Extension) []testkit.FixtureExt {
|
|
|
|
|
var out []testkit.FixtureExt
|
|
|
|
|
for i, e := range append(slices.Clone(critical), noncritical...) {
|
|
|
|
|
out = append(out, testkit.FixtureExt{Critical: i < len(critical), ID: e.ID, Version: e.Version, Data: hex.EncodeToString(e.Data)})
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
// The extension data of time_only_extensions and format2_time_only_extensions,
|
|
|
|
|
// restated independently of their JSON: raw UTF-8 bytes in PUBLIC_HEADER, and
|
|
|
|
|
// CBOR in the profile of spec §58 in CONTROL_CBOR. The base protocol decodes
|
|
|
|
|
// neither.
|
|
|
|
|
func TestExtensionFixtureData(t *testing.T) {
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
for _, name := range []string{"time_only_extensions", "format2_time_only_extensions"} {
|
|
|
|
|
f := loadFixture(t, name)
|
|
|
|
|
var out bytes.Buffer
|
|
|
|
|
opened, err := capsule.Open(context.Background(), &out, bytes.NewReader(f.dkc), f.openOptions(t))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
h := opened.Inspection.Header
|
|
|
|
|
if len(h.Critical) != 0 || len(h.Noncritical) != 1 || h.Noncritical[0].ID != "org.example.label" || h.Noncritical[0].Version != 1 ||
|
|
|
|
|
string(h.Noncritical[0].Data) != "public label" {
|
|
|
|
|
t.Fatalf("%s: header extension %+v", name, h.Noncritical)
|
|
|
|
|
}
|
|
|
|
|
c := opened.ControlNoncritical
|
|
|
|
|
if len(opened.ControlCritical) != 0 || len(c) != 1 || c[0].ID != "org.example.note" || c[0].Version != 2 ||
|
|
|
|
|
hex.EncodeToString(c[0].Data) != "a2000701667365616c6564" {
|
|
|
|
|
t.Fatalf("%s: control extension %+v", name, c)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func loadAccessKey(t *testing.T, name string) *accesskey.AccessKey {
|
|
|
|
|
t.Helper()
|
|
|
|
|
b, err := os.ReadFile(filepath.Join(fixtureDir, name+".dkk"))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
k, err := accesskey.Decode(bytes.NewReader(b))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
return k
|
|
|
|
|
}
|
|
|
|
|
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
// Every known recipient of a multi-recipient fixture opens it on its own.
|
|
|
|
|
func TestFixtureRecipients(t *testing.T) {
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
for _, c := range []struct {
|
|
|
|
|
name string
|
|
|
|
|
n int
|
|
|
|
|
}{{"time_and_key_recipients", 2}, {"format2_time_and_key_recipients", 3}, {"format2_time_and_key_sixteen", 16}} {
|
|
|
|
|
f := loadFixture(t, c.name)
|
|
|
|
|
if len(f.ids) != c.n {
|
|
|
|
|
t.Fatalf("%s should have %d known recipients", c.name, c.n)
|
|
|
|
|
}
|
|
|
|
|
for i, id := range f.ids {
|
|
|
|
|
o := f.openOptions(t)
|
|
|
|
|
o.AccessKey = nil
|
|
|
|
|
o.Identities = []age.Identity{id}
|
|
|
|
|
var out bytes.Buffer
|
|
|
|
|
if _, err := capsule.Open(context.Background(), &out, bytes.NewReader(f.dkc), o); err != nil || !bytes.Equal(out.Bytes(), f.plaintext) {
|
|
|
|
|
t.Fatalf("%s, recipient %d: %v", c.name, i, err)
|
|
|
|
|
}
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A non-seekable reader works too: only the capsule_digest shortcut is skipped.
|
|
|
|
|
func TestOpenFromPlainReader(t *testing.T) {
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
for _, name := range []string{"time_only", "time_and_key_portable", "format2_time_only", "format2_time_and_key_portable"} {
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
f := loadFixture(t, name)
|
|
|
|
|
var out bytes.Buffer
|
|
|
|
|
r := struct{ io.Reader }{bytes.NewReader(f.dkc)}
|
|
|
|
|
if _, err := capsule.Open(context.Background(), &out, r, f.openOptions(t)); err != nil || !bytes.Equal(out.Bytes(), f.plaintext) {
|
|
|
|
|
t.Fatalf("%s: %v", name, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|