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
// Command genfixtures generates the official DateKeys vectors and fixtures
// (spec §65-§68) into testdata/.
//
// Fixtures are generated once, over rounds that are already published, and
// then committed: age randomness cannot be injected through its public API,
// so they are decryption and validation fixtures, not byte-reproducible
// encryption outputs (spec §67). Existing fixtures are never overwritten
// unless -force is given; vectors are always regenerated, and the tests fail
// if the implementation stops reproducing the committed ones.
//
// go run ./internal/testkit/genfixtures -out testdata
package main
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"flag"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"
"filippo.io/age"
"g.activething.com/go/DateKeys/accesskey"
"g.activething.com/go/DateKeys/agewrap"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/datekey"
"g.activething.com/go/DateKeys/extension"
"g.activething.com/go/DateKeys/internal/testkit"
"g.activething.com/go/DateKeys/profile"
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 main ( ) {
out := flag . String ( "out" , "testdata" , "output directory" )
force := flag . Bool ( "force" , false , "overwrite existing fixtures" )
flag . Parse ( )
if err := vectors ( filepath . Join ( * out , "vectors" ) ) ; err != nil {
log . Fatal ( err )
}
if err := fixtures ( filepath . Join ( * out , "fixtures" ) , * force ) ; err != nil {
log . Fatal ( err )
}
}
func vectors ( dir string ) error {
pv , err := testkit . QuicknetProfileVector ( )
if err != nil {
return err
}
if err := testkit . WriteJSON ( filepath . Join ( dir , "profile_quicknet.json" ) , pv ) ; err != nil {
return err
}
if err := testkit . WriteJSON ( filepath . Join ( dir , "quicknet_rounds.json" ) , testkit . RoundVectors ( ) ) ; err != nil {
return err
}
return testkit . WriteJSON ( filepath . Join ( dir , "dk1.json" ) , testkit . DK1Vectors ( ) )
}
type spec struct {
name , description string
round uint64
policy capsule . Policy
recipients int
portable bool
plaintext [ ] byte
headerExt [ ] extension . Extension
controlExt [ ] extension . Extension
}
func fixtures ( dir string , force bool ) error {
if err := os . MkdirAll ( dir , 0 o755 ) ; err != nil {
return err
}
large := [ ] byte ( strings . Repeat ( "DateKeys fixture: this plaintext spans more than one 64 KiB age STREAM chunk.\n" , 1000 ) )
hExt , err := extension . New ( "org.example.label" , 1 , "public label" )
if err != nil {
return err
}
cExt , err := extension . New ( "org.example.note" , 2 , map [ string ] any { "sealed" : true , "n" : 7 } )
if err != nil {
return err
}
specs := [ ] spec {
{ name : "time_only" , description : "time_only capsule, two STREAM chunks, no extensions" , round : 1000 , policy : capsule . TimeOnly , plaintext : large } ,
{ name : "time_only_extensions" , description : "time_only capsule with a noncritical PUBLIC_HEADER extension and a noncritical CONTROL_CBOR extension" , round : 2000 , policy : capsule . TimeOnly , plaintext : [ ] byte ( "DateKeys fixture with extensions.\n" ) , headerExt : [ ] extension . Extension { hExt } , controlExt : [ ] extension . Extension { cExt } } ,
{ name : "time_and_key_portable" , description : "time_and_key capsule whose only recipient is a portable .dkk" , round : 1000 , policy : capsule . TimeAndKey , portable : true , plaintext : [ ] byte ( "DateKeys fixture opened with a portable .dkk.\n" ) } ,
{ name : "time_and_key_recipients" , description : "time_and_key capsule for two known X25519 recipients and a portable .dkk" , round : 1001 , policy : capsule . TimeAndKey , recipients : 2 , portable : true , plaintext : [ ] byte ( "DateKeys fixture for several recipients.\n" ) } ,
{ name : "empty_payload" , description : "time_only capsule with an empty payload" , round : 1001 , policy : capsule . TimeOnly , plaintext : [ ] byte { } } ,
}
for _ , s := range specs {
path := filepath . Join ( dir , s . name + ".dkc" )
if _ , err := os . Stat ( path ) ; err == nil && ! force {
log . Printf ( "keeping existing %s" , path )
continue
}
if err := generate ( dir , s ) ; err != nil {
return fmt . Errorf ( "%s: %w" , s . name , err )
}
log . Printf ( "generated %s" , path )
}
return nil
}
func generate ( dir string , s spec ) error {
p := profile . Quicknet ( )
reg := testkit . Registry ( )
unlock , err := datekey . RoundTime ( p , s . round )
if err != nil {
return err
}
opts := capsule . EncryptOptions {
Profile : p , UnlockAt : unlock , Policy : s . policy , NewPortableKey : s . portable ,
Noncritical : s . headerExt , ControlNoncritical : s . controlExt , Now : testkit . Fixed ( testkit . Genesis ( ) ) ,
}
var ids [ ] * age . X25519Identity
for range s . recipients {
id , err := age . GenerateX25519Identity ( )
if err != nil {
return err
}
ids = append ( ids , id )
opts . Recipients = append ( opts . Recipients , id . Recipient ( ) )
}
var dkc bytes . Buffer
res , err := capsule . Encrypt ( & dkc , bytes . NewReader ( s . plaintext ) , opts )
if err != nil {
return err
}
release := testkit . Release ( s . round )
// Recover every intermediate value by opening the fixture step by step.
parts , err := testkit . Split ( dkc . Bytes ( ) )
if err != nil {
return err
}
timeID , err := agewrap . NewTimeIdentity ( p , s . round , release )
if err != nil {
return err
}
inner , err := decrypt ( parts . Sealed , timeID )
if err != nil {
return err
}
control := inner
var innerStanzas [ ] testkit . FixtureStanza
var identities [ ] string
var dkkFile string
var dkkBytes [ ] byte
if s . policy == capsule . TimeAndKey {
st , err := agewrap . Stanzas ( bytes . NewReader ( inner ) )
if err != nil {
return err
}
innerStanzas = stanzas ( st )
var tryIDs [ ] age . Identity
for _ , id := range ids {
identities = append ( identities , id . String ( ) )
tryIDs = append ( tryIDs , id )
}
if res . PortableKey != nil {
var kb bytes . Buffer
if err := accesskey . Encode ( & kb , res . PortableKey ) ; err != nil {
return err
}
dkkBytes = kb . Bytes ( )
dkkFile = s . name + ".dkk"
kid , err := res . PortableKey . Identity ( )
if err != nil {
return err
}
tryIDs = append ( tryIDs , kid )
}
accessID , err := agewrap . NewAccessIdentity ( tryIDs ... )
if err != nil {
return err
}
if control , err = decrypt ( inner , accessID ) ; err != nil {
return err
}
}
ctrl , err := capsule . DecodeControl ( control )
if err != nil {
return err
}
outer , err := agewrap . Stanzas ( bytes . NewReader ( parts . Sealed ) )
if err != nil {
return err
}
payload , err := agewrap . Stanzas ( bytes . NewReader ( parts . Payload ) )
if err != nil {
return err
}
// The fixture must open through the public API with the embedded release.
oo := capsule . OpenOptions { Registry : reg , Source : testkit . NewSource ( release ) , Now : testkit . Fixed ( unlock ) }
for _ , id := range ids {
oo . Identities = append ( oo . Identities , id )
}
if s . policy == capsule . TimeAndKey && len ( ids ) == 0 {
oo . AccessKey = res . PortableKey
}
var plain bytes . Buffer
opened , err := capsule . Open ( context . Background ( ) , & plain , bytes . NewReader ( dkc . Bytes ( ) ) , oo )
if err != nil {
return fmt . Errorf ( "fixture does not open: %w" , err )
}
if ! bytes . Equal ( plain . Bytes ( ) , s . plaintext ) {
return fmt . Errorf ( "fixture plaintext mismatch" )
}
sum := sha256 . Sum256 ( dkc . Bytes ( ) )
psum := sha256 . Sum256 ( s . plaintext )
f := testkit . DKCFixture {
Description : s . description ,
Spec : testkit . SpecVersion ,
File : s . name + ".dkc" ,
SHA256 : hex . EncodeToString ( sum [ : ] ) ,
Release : testkit . FixtureRelease { Round : release . Round , Signature : hex . EncodeToString ( release . Signature ) } ,
Prelude : hex . EncodeToString ( parts . Prelude ) ,
PublicHeader : hex . EncodeToString ( parts . Header ) ,
DateKey : res . DateKey . Compact ( ) ,
CapsuleID : hex . EncodeToString ( res . CapsuleID [ : ] ) ,
AccessPolicy : s . policy . String ( ) ,
Structure : s . policy . String ( ) ,
UnlockAt : unlock . Format ( time . RFC3339 ) ,
HeaderBinding : hex . EncodeToString ( ctrl . HeaderBinding [ : ] ) ,
OuterStanzas : stanzas ( outer ) ,
PayloadStanzas : stanzas ( payload ) ,
InnerStanzas : innerStanzas ,
AccessKeyFile : dkkFile ,
Identities : identities ,
ControlCBOR : hex . EncodeToString ( control ) ,
PayloadIdentity : hex . EncodeToString ( ctrl . PayloadIdentity [ : ] ) ,
PlaintextFile : s . name + ".plaintext" ,
PlaintextSHA256 : hex . EncodeToString ( psum [ : ] ) ,
HeaderExtensions : exts ( false , s . headerExt ) ,
ControlExt : exts ( false , s . controlExt ) ,
}
for _ , c := range opened . Inspection . Checks {
f . Stages = append ( f . Stages , testkit . FixtureStage { Step : c . Step , Name : c . Name , OK : c . OK , Error : c . Error } )
}
if err := os . WriteFile ( filepath . Join ( dir , f . File ) , dkc . Bytes ( ) , 0 o644 ) ; err != nil {
return err
}
if err := os . WriteFile ( filepath . Join ( dir , f . PlaintextFile ) , s . plaintext , 0 o644 ) ; err != nil {
return err
}
if err := testkit . WriteJSON ( filepath . Join ( dir , s . name + ".json" ) , f ) ; err != nil {
return err
}
if dkkBytes == nil {
return nil
}
k := res . PortableKey
ksum := sha256 . Sum256 ( dkkBytes )
kf := testkit . DKKFixture {
Description : "portable X25519 .dkk of " + f . File ,
Spec : testkit . SpecVersion ,
File : dkkFile ,
SHA256 : hex . EncodeToString ( ksum [ : ] ) ,
CredentialID : hex . EncodeToString ( k . CredentialID [ : ] ) ,
CapsuleID : hex . EncodeToString ( k . CapsuleID [ : ] ) ,
AccessType : k . Type ,
Material : hex . EncodeToString ( k . Material ) ,
CapsuleDigest : hex . EncodeToString ( k . Verification . CapsuleDigest ) ,
Capsule : f . File ,
ExpectedResult : "opens INNER_ACCESS_AGE of " + f . File + " and yields its CONTROL_CBOR" ,
}
if err := os . WriteFile ( filepath . Join ( dir , dkkFile ) , dkkBytes , 0 o644 ) ; err != nil {
return err
}
return testkit . WriteJSON ( filepath . Join ( dir , s . name + ".dkk.json" ) , kf )
}
func decrypt ( file [ ] byte , id age . Identity ) ( [ ] byte , error ) {
r , err := age . Decrypt ( bytes . NewReader ( file ) , id )
if err != nil {
return nil , err
}
var b bytes . Buffer
if _ , err := b . ReadFrom ( r ) ; err != nil {
return nil , err
}
return b . Bytes ( ) , nil
}
func stanzas ( in [ ] * age . Stanza ) [ ] 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
}
func exts ( critical bool , in [ ] extension . Extension ) [ ] testkit . FixtureExt {
var out [ ] testkit . FixtureExt
for _ , e := range in {
out = append ( out , testkit . FixtureExt { Critical : critical , ID : e . ID , Version : e . Version , Data : hex . EncodeToString ( e . Data ) } )
}
return out
}