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 datekeys encrypts, inspects and opens DateKeyCap (.dkc) files.
|
|
|
|
|
//
|
|
|
|
|
// datekeys encrypt -at 2030-01-01T00:00:00Z -in secret.txt -out secret.dkc
|
|
|
|
|
// datekeys encrypt -at 2030-01-01T00:00:00Z -policy time_and_key -dkk key.dkk -in secret.txt -out secret.dkc
|
|
|
|
|
// datekeys inspect -in secret.dkc
|
|
|
|
|
// datekeys decrypt -in secret.dkc -out secret.txt [-dkk key.dkk] [-identity key.txt]
|
|
|
|
|
// datekeys datekey resolve -at 2030-01-01T00:00:00Z
|
|
|
|
|
// datekeys profile hash
|
Version constants: the specification and the module
- datekeys.SpecVersion ("0.8.2") names the specification the module
implements. A test ties it to the spec file, its title and
spec/README.md, and TestCatalogueMatchesSpec and the vector files use
it (testkit.SpecVersion now aliases it), so the vectors regenerate
unchanged.
- datekeys.Version() is the version of the module as the go command
recorded it. That is a tag, or for a binary built in a checkout the
pseudo-version of its commit (for example
v0.0.0-20260928105528-9ac9cd952f04), or (devel) when it is unknown, as
in tests or under a replace directive to a directory. It works as the
main module and as a dependency, whatever the module path, which it
reads from the root package.
- `datekeys version` (also -version and --version) prints both and the
Go toolchain.
- README.md and README.es.md explain the three versions (format,
specification, module) and what the code on main covers.
traceability §70 and CHANGELOG follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
// datekeys version
|
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
|
|
|
//
|
|
|
|
|
// Encryption never touches the network. Decryption fetches the release from
|
|
|
|
|
// public drand relays and verifies it locally. Outputs are written to a
|
|
|
|
|
// temporary file in the destination directory and published only when
|
|
|
|
|
// complete; existing files are never overwritten.
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/hex"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"errors"
|
|
|
|
|
"flag"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"os"
|
Version constants: the specification and the module
- datekeys.SpecVersion ("0.8.2") names the specification the module
implements. A test ties it to the spec file, its title and
spec/README.md, and TestCatalogueMatchesSpec and the vector files use
it (testkit.SpecVersion now aliases it), so the vectors regenerate
unchanged.
- datekeys.Version() is the version of the module as the go command
recorded it. That is a tag, or for a binary built in a checkout the
pseudo-version of its commit (for example
v0.0.0-20260928105528-9ac9cd952f04), or (devel) when it is unknown, as
in tests or under a replace directive to a directory. It works as the
main module and as a dependency, whatever the module path, which it
reads from the root package.
- `datekeys version` (also -version and --version) prints both and the
Go toolchain.
- README.md and README.es.md explain the three versions (format,
specification, module) and what the code on main covers.
traceability §70 and CHANGELOG follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
"runtime"
|
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
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"filippo.io/age"
|
|
|
|
|
|
|
|
|
|
datekeys "g.activething.com/go/DateKeys"
|
|
|
|
|
"g.activething.com/go/DateKeys/accesskey"
|
|
|
|
|
"g.activething.com/go/DateKeys/capsule"
|
|
|
|
|
"g.activething.com/go/DateKeys/datekey"
|
|
|
|
|
"g.activething.com/go/DateKeys/internal/inspectview"
|
|
|
|
|
"g.activething.com/go/DateKeys/profile"
|
|
|
|
|
"g.activething.com/go/DateKeys/provider/drand"
|
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 usage = `usage:
|
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
|
|
|
datekeys encrypt -at TIME -in FILE -out FILE.dkc [-policy time_only|time_and_key] [-recipient age1...]... [-dkk FILE.dkk] [-padding reforzado|bloque256]
|
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
|
|
|
datekeys decrypt -in FILE.dkc -out FILE [-dkk FILE.dkk] [-identity FILE]... [-relay URL]...
|
|
|
|
|
datekeys inspect -in FILE.dkc [-json]
|
|
|
|
|
datekeys datekey resolve -at TIME
|
|
|
|
|
datekeys profile hash [-in PROFILE.cbor]
|
Version constants: the specification and the module
- datekeys.SpecVersion ("0.8.2") names the specification the module
implements. A test ties it to the spec file, its title and
spec/README.md, and TestCatalogueMatchesSpec and the vector files use
it (testkit.SpecVersion now aliases it), so the vectors regenerate
unchanged.
- datekeys.Version() is the version of the module as the go command
recorded it. That is a tag, or for a binary built in a checkout the
pseudo-version of its commit (for example
v0.0.0-20260928105528-9ac9cd952f04), or (devel) when it is unknown, as
in tests or under a replace directive to a directory. It works as the
main module and as a dependency, whatever the module path, which it
reads from the root package.
- `datekeys version` (also -version and --version) prints both and the
Go toolchain.
- README.md and README.es.md explain the three versions (format,
specification, module) and what the code on main covers.
traceability §70 and CHANGELOG follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
datekeys version
|
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
|
|
|
TIME is RFC 3339 with a time zone, for example 2030-01-01T00:00:00Z.
|
|
|
|
|
encrypt writes capsule format 2: the content is padded, by default with the
|
|
|
|
|
rule reforzado, and a time_and_key capsule holds 16 slots, from 1 to 16
|
|
|
|
|
credentials and a dummy in each slot left (spec §29.1, §39).`
|
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
|
|
|
|
|
|
|
|
// errUsage reports a malformed command line; main prints the usage text.
|
|
|
|
|
var errUsage = errors.New("invalid command line; run 'datekeys help'")
|
|
|
|
|
|
|
|
|
|
// longHorizon is the product policy threshold for the harvest-now,
|
|
|
|
|
// decrypt-later warning (spec §53).
|
|
|
|
|
const longHorizon = 365 * 24 * time.Hour
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
if err := run(os.Args[1:], os.Stdout, os.Stderr, time.Now); err != nil {
|
|
|
|
|
if errors.Is(err, errUsage) {
|
|
|
|
|
fmt.Fprintln(os.Stderr, usage)
|
|
|
|
|
os.Exit(2)
|
|
|
|
|
}
|
|
|
|
|
fmt.Fprintln(os.Stderr, "datekeys:", err)
|
|
|
|
|
if code := datekeys.Code(err); code != "" {
|
|
|
|
|
fmt.Fprintln(os.Stderr, "datekeys: error code", code)
|
|
|
|
|
}
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type multi []string
|
|
|
|
|
|
|
|
|
|
func (m *multi) String() string { return strings.Join(*m, ",") }
|
|
|
|
|
func (m *multi) Set(v string) error { *m = append(*m, v); return nil }
|
|
|
|
|
|
|
|
|
|
// run is the CLI; the clock is injected for tests (only the CLI reads the
|
|
|
|
|
// wall clock).
|
|
|
|
|
func run(args []string, stdout, stderr io.Writer, now func() time.Time) error {
|
|
|
|
|
if len(args) == 0 {
|
|
|
|
|
return errUsage
|
|
|
|
|
}
|
|
|
|
|
switch args[0] {
|
|
|
|
|
case "encrypt":
|
|
|
|
|
return encrypt(args[1:], stderr, now)
|
|
|
|
|
case "decrypt":
|
|
|
|
|
return decrypt(args[1:], stderr, now)
|
|
|
|
|
case "inspect":
|
|
|
|
|
return inspect(args[1:], stdout)
|
|
|
|
|
case "datekey":
|
|
|
|
|
if len(args) < 2 || args[1] != "resolve" {
|
|
|
|
|
return errUsage
|
|
|
|
|
}
|
|
|
|
|
return resolve(args[2:], stdout)
|
|
|
|
|
case "profile":
|
|
|
|
|
if len(args) < 2 || args[1] != "hash" {
|
|
|
|
|
return errUsage
|
|
|
|
|
}
|
|
|
|
|
return profileHash(args[2:], stdout)
|
Version constants: the specification and the module
- datekeys.SpecVersion ("0.8.2") names the specification the module
implements. A test ties it to the spec file, its title and
spec/README.md, and TestCatalogueMatchesSpec and the vector files use
it (testkit.SpecVersion now aliases it), so the vectors regenerate
unchanged.
- datekeys.Version() is the version of the module as the go command
recorded it. That is a tag, or for a binary built in a checkout the
pseudo-version of its commit (for example
v0.0.0-20260928105528-9ac9cd952f04), or (devel) when it is unknown, as
in tests or under a replace directive to a directory. It works as the
main module and as a dependency, whatever the module path, which it
reads from the root package.
- `datekeys version` (also -version and --version) prints both and the
Go toolchain.
- README.md and README.es.md explain the three versions (format,
specification, module) and what the code on main covers.
traceability §70 and CHANGELOG follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
case "version", "-version", "--version":
|
|
|
|
|
if len(args) != 1 {
|
|
|
|
|
return errUsage
|
|
|
|
|
}
|
|
|
|
|
// The module version (a tag, or the pseudo-version of the commit a
|
|
|
|
|
// checkout was built from), the specification it implements and the
|
|
|
|
|
// toolchain.
|
|
|
|
|
fmt.Fprintf(stdout, "datekeys %s\nspecification %s\n%s %s/%s\n", datekeys.Version(), datekeys.SpecVersion, runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
|
|
|
|
return 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
|
|
|
case "-h", "-help", "--help", "help":
|
|
|
|
|
fmt.Fprintln(stdout, usage)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return errUsage
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newFlags(name string) *flag.FlagSet {
|
|
|
|
|
fs := flag.NewFlagSet(name, flag.ContinueOnError)
|
|
|
|
|
fs.SetOutput(io.Discard)
|
|
|
|
|
return fs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func parse(fs *flag.FlagSet, args []string) error {
|
|
|
|
|
if err := fs.Parse(args); err != nil {
|
|
|
|
|
return fmt.Errorf("%s: %w", fs.Name(), err)
|
|
|
|
|
}
|
|
|
|
|
if fs.NArg() != 0 {
|
|
|
|
|
return fmt.Errorf("%s: unexpected arguments %q", fs.Name(), fs.Args())
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func parseTime(s string) (time.Time, error) {
|
|
|
|
|
t, err := time.Parse(time.RFC3339Nano, s)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return time.Time{}, fmt.Errorf("invalid -at %q: RFC 3339 with a time zone is required", s)
|
|
|
|
|
}
|
|
|
|
|
return t, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func encrypt(args []string, stderr io.Writer, now func() time.Time) error {
|
|
|
|
|
fs := newFlags("encrypt")
|
|
|
|
|
at := fs.String("at", "", "unlock time, RFC 3339")
|
|
|
|
|
in := fs.String("in", "", "plaintext file")
|
|
|
|
|
out := fs.String("out", "", "new .dkc file; never overwritten")
|
|
|
|
|
policy := fs.String("policy", "time_only", "time_only or time_and_key")
|
|
|
|
|
dkk := fs.String("dkk", "", "time_and_key: new .dkk file for a portable access 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
|
|
|
padding := fs.String("padding", "reforzado", "padding rule of the content: reforzado or bloque256")
|
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 recipients multi
|
|
|
|
|
fs.Var(&recipients, "recipient", "time_and_key: X25519 recipient age1... (repeatable)")
|
|
|
|
|
if err := parse(fs, args); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
unlock, err := parseTime(*at)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
pol, err := capsule.ParsePolicy(*policy)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 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
|
|
|
var code capsule.Padding
|
|
|
|
|
switch *padding {
|
|
|
|
|
case capsule.Reforzado.String():
|
|
|
|
|
code = capsule.Reforzado
|
|
|
|
|
case capsule.Bloque256.String():
|
|
|
|
|
code = capsule.Bloque256
|
|
|
|
|
default:
|
|
|
|
|
return fmt.Errorf("encrypt: unknown padding rule %q: reforzado or bloque256", *padding)
|
|
|
|
|
}
|
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 *in == "" || *out == "" {
|
|
|
|
|
return errors.New("encrypt: -in and -out are required")
|
|
|
|
|
}
|
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
|
|
|
opts := capsule.EncryptOptions{Profile: profile.Quicknet(), UnlockAt: unlock, Policy: pol, NewPortableKey: *dkk != "", Padding: code, Now: now}
|
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
|
|
|
for _, r := range recipients {
|
|
|
|
|
x, err := age.ParseX25519Recipient(r)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("encrypt: %w", err)
|
|
|
|
|
}
|
|
|
|
|
opts.Recipients = append(opts.Recipients, x)
|
|
|
|
|
}
|
|
|
|
|
if *dkk != "" {
|
|
|
|
|
if err := checkNew(*dkk); err != nil {
|
|
|
|
|
return 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
|
|
|
src, length, err := openMeasured(*in)
|
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 {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer src.Close()
|
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
|
|
|
opts.Length = length
|
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 res *capsule.Result
|
|
|
|
|
err = writeAtomic(*out, func(w io.Writer) error {
|
|
|
|
|
res, err = capsule.Encrypt(w, src, opts)
|
|
|
|
|
return err
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if res.PortableKey != nil {
|
|
|
|
|
defer res.PortableKey.Wipe()
|
|
|
|
|
if err := writeAtomic(*dkk, func(w io.Writer) error { return accesskey.Encode(w, res.PortableKey) }); err != nil {
|
|
|
|
|
return fmt.Errorf("the capsule was written to %s but its .dkk could not be: %w", *out, 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
|
|
|
fmt.Fprintf(stderr, "Encrypted locally for %s (round %d)\n datekey %s\n capsule_id %x\n format %d: %d bytes of content, padded to %d (%s)\n",
|
|
|
|
|
res.UnlockAt.Format(time.RFC3339), res.DateKey.Round, res.DateKey.Compact(), res.CapsuleID, res.Format, res.Length, res.PaddedLength, res.Padding)
|
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 res.PortableKey != nil {
|
|
|
|
|
fmt.Fprintf(stderr, " access key %s: keep it secret; it is valid for this capsule only\n", *dkk)
|
|
|
|
|
}
|
|
|
|
|
if res.UnlockAt.Sub(now()) > longHorizon {
|
|
|
|
|
fmt.Fprintln(stderr, "warning: Quicknet V1 timelock is not post-quantum. The ciphertext may stay available for years,\n"+
|
|
|
|
|
" and its future confidentiality depends on the provider and on the underlying cryptography (spec §53).")
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func decrypt(args []string, stderr io.Writer, now func() time.Time) error {
|
|
|
|
|
fs := newFlags("decrypt")
|
|
|
|
|
in := fs.String("in", "", ".dkc file")
|
|
|
|
|
out := fs.String("out", "", "new plaintext file; never overwritten")
|
|
|
|
|
dkk := fs.String("dkk", "", "portable access key (.dkk)")
|
|
|
|
|
timeout := fs.Duration("timeout", 30*time.Second, "release request timeout")
|
|
|
|
|
var identities, relays multi
|
|
|
|
|
fs.Var(&identities, "identity", "age identity file with X25519 keys (repeatable)")
|
|
|
|
|
fs.Var(&relays, "relay", "drand relay base URL (repeatable); default: public relays")
|
|
|
|
|
if err := parse(fs, args); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if *in == "" || *out == "" {
|
|
|
|
|
return errors.New("decrypt: -in and -out are required")
|
|
|
|
|
}
|
|
|
|
|
reg, err := profile.Default()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
opts := capsule.OpenOptions{Registry: reg, Source: drand.New(relays...), Now: now}
|
|
|
|
|
for _, path := range identities {
|
|
|
|
|
ids, err := readIdentities(path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
opts.Identities = append(opts.Identities, ids...)
|
|
|
|
|
}
|
|
|
|
|
if *dkk != "" {
|
Spec v0.8.2 refinements: error precedence, trust model, strict order
Approved refinements, each recorded with its reproducible case in the
§76 v0.8.2 subsection:
- §69.1: layered error model with normative precedence (frame, type tag
and version, CBOR profile and CDDL, then fields with their own code in
ascending key order; across steps the §63 order decides), with a scope
paragraph for the optional steps 5, 6 and 8.
- §55.1: normative trust table per section (who can write it, from which
step it is bound, what it never proves); §72: security-relevant claims
go in CONTROL_CBOR or under a signature, .dkk data is advisory.
- §31/§54: extension arrays in strictly ascending unsigned byte order of
extension_id (one rule for order and uniqueness).
- Gaps a second implementation needed: §28.1 malformed age headers,
§15/§19 latest unlock time and dk1_ reading rules, §22/§23/§57 length
lower bounds, §63 step 8 tlock argument comparison and step 9 order,
§12.1 profile validation with the drand chain-hash formula, §74 table
of implementation limits.
Reference alignment: .dkk errors only at step 9.a (new
OpenOptions.AccessKeyFile, used by the CLI), CR/LF in dk1_ is
ERR_DATEKEY_INVALID, BODY_LEN 0 is ERR_INTEGRITY, nil identities are not
credentials, and AccessIdentity tries every identity on every stanza so
its verdict does not depend on their order. dk1.json gains three
vectors; every other testdata file is byte-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
// Open decodes the .dkk at step 9.a, and only for a time_and_key
|
|
|
|
|
// capsule, so that its errors come in the order of spec §63.
|
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, err := os.Open(*dkk)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
Spec v0.8.2 refinements: error precedence, trust model, strict order
Approved refinements, each recorded with its reproducible case in the
§76 v0.8.2 subsection:
- §69.1: layered error model with normative precedence (frame, type tag
and version, CBOR profile and CDDL, then fields with their own code in
ascending key order; across steps the §63 order decides), with a scope
paragraph for the optional steps 5, 6 and 8.
- §55.1: normative trust table per section (who can write it, from which
step it is bound, what it never proves); §72: security-relevant claims
go in CONTROL_CBOR or under a signature, .dkk data is advisory.
- §31/§54: extension arrays in strictly ascending unsigned byte order of
extension_id (one rule for order and uniqueness).
- Gaps a second implementation needed: §28.1 malformed age headers,
§15/§19 latest unlock time and dk1_ reading rules, §22/§23/§57 length
lower bounds, §63 step 8 tlock argument comparison and step 9 order,
§12.1 profile validation with the drand chain-hash formula, §74 table
of implementation limits.
Reference alignment: .dkk errors only at step 9.a (new
OpenOptions.AccessKeyFile, used by the CLI), CR/LF in dk1_ is
ERR_DATEKEY_INVALID, BODY_LEN 0 is ERR_INTEGRITY, nil identities are not
credentials, and AccessIdentity tries every identity on every stanza so
its verdict does not depend on their order. dk1.json gains three
vectors; every other testdata file is byte-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
defer f.Close()
|
|
|
|
|
opts.AccessKeyFile = 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
|
|
|
}
|
|
|
|
|
src, err := os.Open(*in)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer src.Close()
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
|
|
|
|
|
defer cancel()
|
|
|
|
|
var opened *capsule.Opened
|
|
|
|
|
err = writeAtomic(*out, func(w io.Writer) error {
|
|
|
|
|
opened, err = capsule.Open(ctx, w, src, opts)
|
|
|
|
|
return err
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 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
|
|
|
fmt.Fprintf(stderr, "Decrypted capsule %s (round %d, unlocked at %s); release verified locally\n format %d, %d bytes of content\n",
|
|
|
|
|
opened.Inspection.Header.CapsuleIDHex(), opened.Release.Round, opened.Inspection.UnlockAt.Format(time.RFC3339), opened.Format, opened.PayloadLength)
|
|
|
|
|
if opened.Format == capsule.Format1 {
|
|
|
|
|
// Spec §55.2, §70: format 1 hides neither the number of credentials
|
|
|
|
|
// nor the exact length of the content.
|
|
|
|
|
fmt.Fprintln(stderr, " format 1 does not hide the number of credentials or the exact length of the content")
|
|
|
|
|
}
|
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 nil
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
// openMeasured opens the content to encrypt and returns its exact length,
|
|
|
|
|
// which format 2 seals before the content (spec §62.1 rule 6). A file that is
|
|
|
|
|
// not regular, such as a pipe, has no known length: it is copied first to a
|
|
|
|
|
// temporary file, removed when the returned file is closed.
|
|
|
|
|
func openMeasured(path string) (io.ReadCloser, int64, error) {
|
|
|
|
|
f, err := os.Open(path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
info, err := f.Stat()
|
|
|
|
|
if err != nil {
|
|
|
|
|
f.Close()
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
if info.Mode().IsRegular() {
|
|
|
|
|
return f, info.Size(), nil
|
|
|
|
|
}
|
|
|
|
|
defer f.Close()
|
|
|
|
|
tmp, err := os.CreateTemp("", "datekeys-content-*")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
n, err := io.Copy(tmp, f)
|
|
|
|
|
if err == nil {
|
|
|
|
|
_, err = tmp.Seek(0, io.SeekStart)
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
tmp.Close()
|
|
|
|
|
os.Remove(tmp.Name())
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
return removeOnClose{tmp}, n, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// removeOnClose removes its temporary file when it is closed.
|
|
|
|
|
type removeOnClose struct{ *os.File }
|
|
|
|
|
|
|
|
|
|
func (r removeOnClose) Close() error {
|
|
|
|
|
return errors.Join(r.File.Close(), os.Remove(r.File.Name()))
|
|
|
|
|
}
|
|
|
|
|
|
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 readIdentities(path string) ([]age.Identity, error) {
|
|
|
|
|
f, err := os.Open(path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
defer f.Close()
|
|
|
|
|
ids, err := age.ParseIdentities(f)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("%s: %w", path, err)
|
|
|
|
|
}
|
|
|
|
|
return ids, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// inspect runs steps 1 to 8 only: it never requests a release and never uses
|
|
|
|
|
// a secret.
|
|
|
|
|
func inspect(args []string, stdout io.Writer) error {
|
|
|
|
|
fs := newFlags("inspect")
|
|
|
|
|
in := fs.String("in", "", ".dkc file")
|
|
|
|
|
asJSON := fs.Bool("json", false, "JSON output")
|
|
|
|
|
if err := parse(fs, args); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if *in == "" {
|
|
|
|
|
return errors.New("inspect: -in is required")
|
|
|
|
|
}
|
|
|
|
|
reg, err := profile.Default()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
f, err := os.Open(*in)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer f.Close()
|
|
|
|
|
result, inspectErr := capsule.Inspect(f, capsule.InspectOptions{Registry: reg})
|
|
|
|
|
v := inspectview.New(*in, result, inspectErr)
|
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 *asJSON {
|
|
|
|
|
if err := v.WriteJSON(stdout); 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
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
v.WriteText(stdout)
|
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 inspectErr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type resolveView struct {
|
|
|
|
|
DateKey string `json:"datekey"`
|
|
|
|
|
Profile string `json:"profile"`
|
|
|
|
|
Round uint64 `json:"round"`
|
|
|
|
|
Requested string `json:"requested"`
|
|
|
|
|
UnlockAt string `json:"unlock_at"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func resolve(args []string, stdout io.Writer) error {
|
|
|
|
|
fs := newFlags("datekey resolve")
|
|
|
|
|
at := fs.String("at", "", "instant, RFC 3339")
|
|
|
|
|
if err := parse(fs, args); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
t, err := parseTime(*at)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
p := profile.Quicknet()
|
|
|
|
|
d, err := datekey.Resolve(p, t)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return json.NewEncoder(stdout).Encode(resolveView{
|
|
|
|
|
DateKey: d.Compact(), Profile: d.ProfileID, Round: d.Round,
|
|
|
|
|
Requested: t.Format(time.RFC3339Nano), UnlockAt: d.UnlockAt(p).Format(time.RFC3339),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func profileHash(args []string, stdout io.Writer) error {
|
|
|
|
|
fs := newFlags("profile hash")
|
|
|
|
|
in := fs.String("in", "", "Deterministic CBOR profile file; default: the pinned Quicknet profile")
|
|
|
|
|
if err := parse(fs, args); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
p := profile.Quicknet()
|
|
|
|
|
if *in != "" {
|
|
|
|
|
b, err := os.ReadFile(*in)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if p, err = profile.Decode(b); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
b, err := p.CanonicalCBOR()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
h, err := p.Hash()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
pinned := *in == "" || hex.EncodeToString(h[:]) == profile.QuicknetProfileHash
|
|
|
|
|
return json.NewEncoder(stdout).Encode(map[string]any{
|
|
|
|
|
"profile_id": p.ID,
|
|
|
|
|
"profile_hash": hex.EncodeToString(h[:]),
|
|
|
|
|
"canonical_cbor": hex.EncodeToString(b),
|
|
|
|
|
"pinned": pinned,
|
|
|
|
|
})
|
|
|
|
|
}
|