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>v0.11
parent
b0bda15d20
commit
78f56a2f1c
@ -0,0 +1,61 @@
|
|||||||
|
package capsule_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"g.activething.com/go/DateKeys/capsule"
|
||||||
|
"g.activething.com/go/DateKeys/internal/testkit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// testdata/vectors/security_cms.json is frozen: each case is evaluated again,
|
||||||
|
// with its context, and must give its verdicts, the results of each signer
|
||||||
|
// and the authority of a valid seal (spec v0.11 §29.7, §29.10, §29.11).
|
||||||
|
func TestCMSVectors(t *testing.T) {
|
||||||
|
var f testkit.CMSVectorFile
|
||||||
|
if err := testkit.ReadJSON(filepath.Join("..", "testdata", "vectors", "security_cms.json"), &f); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(f.Cases) < 20 {
|
||||||
|
t.Fatalf("%d cases", len(f.Cases))
|
||||||
|
}
|
||||||
|
for _, c := range f.Cases {
|
||||||
|
t.Run(c.Name, func(t *testing.T) {
|
||||||
|
area, err := hex.DecodeString(c.SecurityCBOR)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var v capsule.Verdicts
|
||||||
|
if c.NoContext {
|
||||||
|
v = capsule.EvaluateSecurity(area)
|
||||||
|
} else {
|
||||||
|
var sc capsule.SecurityContext
|
||||||
|
cc, _ := hex.DecodeString(c.Context.ControlCommit)
|
||||||
|
hd, _ := hex.DecodeString(c.Context.HeadDigest)
|
||||||
|
copy(sc.ControlCommit[:], cc)
|
||||||
|
copy(sc.HeadDigest[:], hd)
|
||||||
|
if sc.RoundTime, err = time.Parse(time.RFC3339, c.Context.RoundTime); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
v = capsule.EvaluateSecurityIn(area, &sc)
|
||||||
|
}
|
||||||
|
if string(v.Signature) != c.Signature || string(v.Seal) != c.Seal {
|
||||||
|
t.Fatalf("verdicts %s and %s, want %s and %s", v.Signature, v.Seal, c.Signature, c.Seal)
|
||||||
|
}
|
||||||
|
var signers, foreign []testkit.FixtureSignerResult
|
||||||
|
var holder, when string
|
||||||
|
if d := v.Detail; d != nil {
|
||||||
|
signers, foreign = signerResults(d.Signers), signerResults(d.Foreign)
|
||||||
|
if !d.SealTime.IsZero() {
|
||||||
|
holder, when = d.SealHolder, d.SealTime.UTC().Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(signers, c.Signers) || !reflect.DeepEqual(foreign, c.Foreign) || holder != c.SealHolder || when != c.SealTime {
|
||||||
|
t.Errorf("signers %+v foreign %+v seal %q %q; want %+v %+v %q %q", signers, foreign, holder, when, c.Signers, c.Foreign, c.SealHolder, c.SealTime)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,97 @@
|
|||||||
|
package testkit
|
||||||
|
|
||||||
|
// CMSVectorFile is testdata/vectors/security_cms.json: SECURITY_CBOR areas
|
||||||
|
// with an author signature of alg 2 (CMS with certificates) or a time seal
|
||||||
|
// of seal_type 2 (RFC 3161), each with the context of its capsule and the
|
||||||
|
// verdicts that spec v0.11 §29.7, §29.10 and §29.11 give. The certificates
|
||||||
|
// and the tokens are made once, with test keys, and the file is frozen: a
|
||||||
|
// second implementation reads them and must reach the same verdicts.
|
||||||
|
type CMSVectorFile struct {
|
||||||
|
Description string `json:"description"`
|
||||||
|
Spec string `json:"spec"`
|
||||||
|
Cases []CMSVectorCase `json:"cases"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CMSVectorContext is what a verdict needs besides SECURITY_CBOR (§29.7):
|
||||||
|
// control_commit and head_digest in hexadecimal, and round_time in RFC 3339.
|
||||||
|
type CMSVectorContext struct {
|
||||||
|
ControlCommit string `json:"control_commit"`
|
||||||
|
HeadDigest string `json:"head_digest"`
|
||||||
|
RoundTime string `json:"round_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CMSVectorCase is one case: the area, its context, and the verdicts.
|
||||||
|
type CMSVectorCase struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
SecurityCBOR string `json:"security_cbor"`
|
||||||
|
// NoContext is true when the area is read without the context of a
|
||||||
|
// capsule, as a reader of v0.10 does: any signature is F1, any seal S1.
|
||||||
|
NoContext bool `json:"no_context,omitempty"`
|
||||||
|
Context CMSVectorContext `json:"context"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
|
Seal string `json:"seal"`
|
||||||
|
// Signers and Foreign are the results of an alg 2 signature, in the
|
||||||
|
// order of SIGNERS; SealHolder and SealTime are those of a valid seal.
|
||||||
|
Signers []FixtureSignerResult `json:"signers,omitempty"`
|
||||||
|
Foreign []FixtureSignerResult `json:"foreign_signers,omitempty"`
|
||||||
|
SealHolder string `json:"seal_holder,omitempty"`
|
||||||
|
SealTime string `json:"seal_time,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocatorVectorFile is testdata/vectors/locator.json: the extension
|
||||||
|
// datekeys.capsule of a .dkk and what it points to (spec v0.11, §44.1). It is
|
||||||
|
// made once and frozen: the envelope and the sealed locator hold randomness.
|
||||||
|
type LocatorVectorFile struct {
|
||||||
|
Description string `json:"description"`
|
||||||
|
Spec string `json:"spec"`
|
||||||
|
// Round is the round of the DateKey of the extension, and the locator is
|
||||||
|
// sealed with tlock for it: it opens with the release of that round.
|
||||||
|
Round uint64 `json:"round"`
|
||||||
|
DateKey string `json:"datekey"`
|
||||||
|
Note string `json:"note"`
|
||||||
|
// DKC is the capsule that the envelope hides, in hexadecimal.
|
||||||
|
DKC string `json:"dkc"`
|
||||||
|
// Rest is what is kept outside, Header what the locator carries, and Host
|
||||||
|
// a file with the rest appended at HostOffset.
|
||||||
|
Rest string `json:"rest"`
|
||||||
|
Header string `json:"envelope_header"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
HostOffset uint64 `json:"host_offset"`
|
||||||
|
// Plaintext is the plaintext of the locator, in 4096 bytes; Sealed, the
|
||||||
|
// age file with the tlock stanza; Extension, the data of datekeys.capsule.
|
||||||
|
Plaintext string `json:"locator_plaintext"`
|
||||||
|
Sealed string `json:"locator_sealed"`
|
||||||
|
Extension string `json:"extension_data"`
|
||||||
|
// The fields of the locator, for a reader that compares them.
|
||||||
|
Addresses []LocatorVectorAddress `json:"addresses"`
|
||||||
|
EnvelopeKey string `json:"envelope_key"`
|
||||||
|
RestDigest string `json:"rest_digest"`
|
||||||
|
RestSize uint64 `json:"rest_size"`
|
||||||
|
CapsuleDigest string `json:"capsule_digest"`
|
||||||
|
// PaddingCases give the length of the plaintext for a length of the CBOR
|
||||||
|
// without key 6, around the boundaries where the CBOR length of key 6
|
||||||
|
// changes: the least multiple of 4096 that key 6 can fill exactly.
|
||||||
|
PaddingCases []LocatorPaddingCase `json:"padding_cases"`
|
||||||
|
// URICases give the verdict of the rules of §44.1 on an address.
|
||||||
|
URICases []LocatorURICase `json:"uri_cases"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocatorVectorAddress is an address of the locator.
|
||||||
|
type LocatorVectorAddress struct {
|
||||||
|
URI string `json:"uri"`
|
||||||
|
Offset uint64 `json:"offset"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocatorPaddingCase is a length of the CBOR without padding and the length of
|
||||||
|
// the plaintext that results.
|
||||||
|
type LocatorPaddingCase struct {
|
||||||
|
Base int `json:"base"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocatorURICase is an address and whether it is accepted.
|
||||||
|
type LocatorURICase struct {
|
||||||
|
URI string `json:"uri"`
|
||||||
|
OK bool `json:"ok"`
|
||||||
|
}
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/sha256"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"g.activething.com/go/DateKeys/capsule"
|
||||||
|
"g.activething.com/go/DateKeys/internal/cms/cmstest"
|
||||||
|
"g.activething.com/go/DateKeys/internal/testkit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The fixtures with certificates (spec v0.11, §29.10, §29.11) are signed by
|
||||||
|
// test certificates made when the fixture is generated, so their bytes are
|
||||||
|
// random and, like those of the other fixtures, frozen once written. The
|
||||||
|
// private keys are not kept: a reader only verifies.
|
||||||
|
var (
|
||||||
|
certFrom = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
certTo = time.Date(2040, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
)
|
||||||
|
|
||||||
|
func sum256(b []byte) [32]byte { return sha256.Sum256(b) }
|
||||||
|
|
||||||
|
// certSigner is a CMSSigner that signs as a signing application would, and
|
||||||
|
// seals each signature with the authority tsa at the writing time.
|
||||||
|
type certSigner struct {
|
||||||
|
signers []cmstest.Signer
|
||||||
|
tsa cmstest.Signer
|
||||||
|
when time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *certSigner) Signers() (out [][32]byte) {
|
||||||
|
for _, s := range c.signers {
|
||||||
|
out = append(out, sum256(s.Cert.Raw))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *certSigner) Sign(message []byte) ([]byte, error) {
|
||||||
|
return cmstest.Signature(message, cmstest.Options{Token: func(sig []byte) []byte {
|
||||||
|
return cmstest.Token(sig, c.when, cmstest.TokenOptions{Accuracy: time.Second}, c.tsa)
|
||||||
|
}}, c.signers...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenSealer asks the authority tsa for the token over SEAL_SUBJECT.
|
||||||
|
type tokenSealer struct {
|
||||||
|
tsa cmstest.Signer
|
||||||
|
when time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s tokenSealer) Seal(subject [32]byte) ([]byte, error) {
|
||||||
|
return cmstest.Token(subject[:], s.when, cmstest.TokenOptions{Accuracy: time.Second}, s.tsa), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// configureCMS makes the capsule be signed by two certificates, an ECDSA one
|
||||||
|
// and an RSA one, each sealed by a time-stamping authority: verdict F6.
|
||||||
|
func configureCMS(o *capsule.EncryptOptions) error {
|
||||||
|
ana := cmstest.NewECDSA("Ana López", elliptic.P256(), certFrom, certTo)
|
||||||
|
luis := cmstest.NewRSA("Luis Gómez", 2048, certFrom, certTo)
|
||||||
|
tsa := cmstest.NewECDSA("Autoridad de Sellado de prueba", elliptic.P256(), certFrom, certTo)
|
||||||
|
o.CMSSigner = &certSigner{signers: []cmstest.Signer{ana, luis}, tsa: tsa, when: testkit.Genesis()}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// configureSeal makes the capsule carry a seal of seal_type 2 over the
|
||||||
|
// signature of alg 1 that its spec gives a key for: verdicts F4 and S4.
|
||||||
|
func configureSeal(o *capsule.EncryptOptions) error {
|
||||||
|
tsa := cmstest.NewECDSA("Autoridad de Sellado de prueba", elliptic.P256(), certFrom, certTo)
|
||||||
|
o.Sealer = tokenSealer{tsa: tsa, when: testkit.Genesis()}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -0,0 +1,260 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"g.activething.com/go/DateKeys/authorkey"
|
||||||
|
"g.activething.com/go/DateKeys/capsule"
|
||||||
|
"g.activething.com/go/DateKeys/datekey"
|
||||||
|
"g.activething.com/go/DateKeys/extension"
|
||||||
|
"g.activething.com/go/DateKeys/internal/cms/cmstest"
|
||||||
|
"g.activething.com/go/DateKeys/internal/testkit"
|
||||||
|
"g.activething.com/go/DateKeys/locator"
|
||||||
|
"g.activething.com/go/DateKeys/profile"
|
||||||
|
)
|
||||||
|
|
||||||
|
// frozenVectors writes testdata/vectors/security_cms.json and locator.json
|
||||||
|
// when they are missing: their bytes hold the randomness of certificates, of
|
||||||
|
// age and of tlock, so they are made once and kept, as the fixtures are.
|
||||||
|
// Delete a file to make it again.
|
||||||
|
func frozenVectors(dir string) error {
|
||||||
|
for name, gen := range map[string]func() (any, error){
|
||||||
|
"security_cms.json": securityCMSVectors,
|
||||||
|
"locator.json": locatorVectors,
|
||||||
|
} {
|
||||||
|
path := filepath.Join(dir, name)
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v, err := gen()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %w", name, err)
|
||||||
|
}
|
||||||
|
if err := testkit.WriteJSON(path, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
vecRound = time.Date(2030, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
vecSigned = time.Date(2026, 9, 30, 12, 0, 0, 0, time.UTC)
|
||||||
|
)
|
||||||
|
|
||||||
|
func hex32(b [32]byte) string { return hex.EncodeToString(b[:]) }
|
||||||
|
|
||||||
|
// cmsArea builds SECURITY_CBOR with an alg 2 signature by the signers over
|
||||||
|
// the context c, sealed by tsa at when; required lists who must sign.
|
||||||
|
func cmsArea(c *capsule.SecurityContext, required, signers []cmstest.Signer, tsa cmstest.Signer, when time.Time, seal []byte) ([]byte, error) {
|
||||||
|
var hashes [][32]byte
|
||||||
|
for _, s := range required {
|
||||||
|
hashes = append(hashes, sum256(s.Cert.Raw))
|
||||||
|
}
|
||||||
|
list, err := capsule.EncodeSigners(hashes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
msg := capsule.AuthorMessage(c.ControlCommit, c.HeadDigest, capsule.SignersDigest(capsule.AlgCMS, list))
|
||||||
|
opts := cmstest.Options{}
|
||||||
|
if tsa.Key != nil {
|
||||||
|
opts.Token = func(sig []byte) []byte {
|
||||||
|
return cmstest.Token(sig, when, cmstest.TokenOptions{Accuracy: time.Second}, tsa)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content, err := capsule.EncodeAuthorSignature(capsule.AlgCMS, list, cmstest.Signature(msg, opts, signers...))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return capsule.EncodeSecurityWith(content, seal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func securityCMSVectors() (any, error) {
|
||||||
|
ana := cmstest.NewECDSA("Ana López", elliptic.P256(), certFrom, certTo)
|
||||||
|
luis := cmstest.NewRSA("Luis Gómez", 2048, certFrom, certTo)
|
||||||
|
otro := cmstest.NewECDSA("Otro", elliptic.P384(), certFrom, certTo)
|
||||||
|
tsa := cmstest.NewECDSA("Autoridad de Sellado de prueba", elliptic.P256(), certFrom, certTo)
|
||||||
|
ctx := func() *capsule.SecurityContext {
|
||||||
|
return &capsule.SecurityContext{ControlCommit: sha256.Sum256([]byte("control")), HeadDigest: sha256.Sum256([]byte("head")), RoundTime: vecRound}
|
||||||
|
}
|
||||||
|
key, err := authorkey.NewFromSeed(bytes.Repeat([]byte{7}, 32))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f := testkit.CMSVectorFile{
|
||||||
|
Spec: testkit.SpecVersion,
|
||||||
|
Description: "SECURITY_CBOR with an author signature of alg 2 or a time seal of seal_type 2, its context and the verdicts of spec v0.11 29.7, 29.10 and 29.11. " +
|
||||||
|
"Certificates and tokens are made once with test keys and the file is frozen. See testdata/README.md.",
|
||||||
|
}
|
||||||
|
var errs []error
|
||||||
|
add := func(name string, area []byte, c *capsule.SecurityContext, wantSig, wantSeal capsule.Verdict) {
|
||||||
|
var v capsule.Verdicts
|
||||||
|
vc := testkit.CMSVectorCase{Name: name, SecurityCBOR: hex.EncodeToString(area)}
|
||||||
|
if c == nil {
|
||||||
|
v, vc.NoContext = capsule.EvaluateSecurity(area), true
|
||||||
|
c = ctx()
|
||||||
|
} else {
|
||||||
|
v = capsule.EvaluateSecurityIn(area, c)
|
||||||
|
}
|
||||||
|
vc.Context = testkit.CMSVectorContext{ControlCommit: hex32(c.ControlCommit), HeadDigest: hex32(c.HeadDigest), RoundTime: c.RoundTime.UTC().Format(time.RFC3339)}
|
||||||
|
vc.Signature, vc.Seal = string(v.Signature), string(v.Seal)
|
||||||
|
if v.Signature != wantSig || v.Seal != wantSeal {
|
||||||
|
errs = append(errs, fmt.Errorf("%s: verdicts %s and %s, want %s and %s", name, v.Signature, v.Seal, wantSig, wantSeal))
|
||||||
|
}
|
||||||
|
if d := v.Detail; d != nil {
|
||||||
|
vc.Signers, vc.Foreign = signerResults(d.Signers), signerResults(d.Foreign)
|
||||||
|
if !d.SealTime.IsZero() {
|
||||||
|
vc.SealHolder, vc.SealTime = d.SealHolder, d.SealTime.UTC().Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.Cases = append(f.Cases, vc)
|
||||||
|
}
|
||||||
|
must := func(b []byte, err error) []byte {
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
both := []cmstest.Signer{ana, luis}
|
||||||
|
|
||||||
|
c := ctx()
|
||||||
|
add("alg 2: two signers, each sealed before the round time", must(cmsArea(c, both, both, tsa, vecSigned, nil)), c, capsule.VerdictSignedComplete, capsule.VerdictNoSeal)
|
||||||
|
add("alg 2: a seal after the round time proves nothing before it", must(cmsArea(c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, tsa, vecRound.Add(time.Hour), nil)), c, capsule.VerdictSignedComplete, capsule.VerdictNoSeal)
|
||||||
|
add("alg 2: a signer who is not required shows apart", must(cmsArea(c, []cmstest.Signer{ana}, []cmstest.Signer{ana, otro}, tsa, vecSigned, nil)), c, capsule.VerdictSignedComplete, capsule.VerdictNoSeal)
|
||||||
|
add("alg 2: a required signer is absent", must(cmsArea(c, both, []cmstest.Signer{ana}, tsa, vecSigned, nil)), c, capsule.VerdictSignedIncomplete, capsule.VerdictNoSeal)
|
||||||
|
add("alg 2: no seal", must(cmsArea(c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, cmstest.Signer{}, vecSigned, nil)), c, capsule.VerdictSignedIncomplete, capsule.VerdictNoSeal)
|
||||||
|
add("alg 2: a seal from before the certificate was valid", must(cmsArea(c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, tsa, certFrom.AddDate(-1, 0, 0), nil)), c, capsule.VerdictSignedIncomplete, capsule.VerdictNoSeal)
|
||||||
|
add("alg 2: a key 3 beside it", must(cmsArea(c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, tsa, vecSigned, must(capsule.EncodeSeal(1, []byte{1})))), c, capsule.VerdictSignedIncomplete, capsule.VerdictSealUnsupported)
|
||||||
|
other := ctx()
|
||||||
|
other.HeadDigest[5] ^= 9
|
||||||
|
add("alg 2: another head", must(cmsArea(c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, tsa, vecSigned, nil)), other, capsule.VerdictSignatureInvalid, capsule.VerdictNoSeal)
|
||||||
|
add("alg 2: without the context of a capsule", must(cmsArea(c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, tsa, vecSigned, nil)), nil, capsule.VerdictSignatureUnchecked, capsule.VerdictNoSeal)
|
||||||
|
|
||||||
|
one, two := sum256([]byte("a")), sum256([]byte("b"))
|
||||||
|
if bytes.Compare(one[:], two[:]) > 0 {
|
||||||
|
one, two = two, one
|
||||||
|
}
|
||||||
|
unsorted := append(append([]byte{0x82, 0x58, 0x20}, two[:]...), append([]byte{0x58, 0x20}, one[:]...)...)
|
||||||
|
for name, signers := range map[string][]byte{"alg 2: SIGNERS out of order": unsorted, "alg 2: an empty SIGNERS": {0x80}} {
|
||||||
|
content, _ := capsule.EncodeAuthorSignature(capsule.AlgCMS, signers, []byte{0x30, 0x00})
|
||||||
|
add(name, must(capsule.EncodeSecurityWith(content, nil)), c, capsule.VerdictSignatureUnchecked, capsule.VerdictNoSeal)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
list, _ := capsule.EncodeSigners([][32]byte{sum256(ana.Cert.Raw)})
|
||||||
|
content, _ := capsule.EncodeAuthorSignature(capsule.AlgCMS, list, []byte("not DER"))
|
||||||
|
add("alg 2: not a CMS", must(capsule.EncodeSecurityWith(content, nil)), c, capsule.VerdictSignatureUnchecked, capsule.VerdictNoSeal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seals of seal_type 2 over an alg 1 signature.
|
||||||
|
msg := capsule.AuthorMessage(c.ControlCommit, c.HeadDigest, capsule.SignersDigest(capsule.AlgEd25519, nil))
|
||||||
|
sig := must(capsule.EncodeAuthorSignature(capsule.AlgEd25519, key.Public(), key.Sign(msg)))
|
||||||
|
subject := capsule.SealSubject(c.ControlCommit, c.HeadDigest, capsule.SigPart(sig))
|
||||||
|
sealed := func(token []byte) []byte {
|
||||||
|
return must(capsule.EncodeSecurityWith(sig, must(capsule.EncodeSeal(capsule.SealTypeRFC3161, token))))
|
||||||
|
}
|
||||||
|
tok := func(subject []byte, when time.Time, o cmstest.TokenOptions, s cmstest.Signer) []byte {
|
||||||
|
return cmstest.Token(subject, when, o, s)
|
||||||
|
}
|
||||||
|
okSig := capsule.VerdictSignedOther
|
||||||
|
add("seal: before the round time", sealed(tok(subject[:], vecSigned, cmstest.TokenOptions{}, tsa)), c, okSig, capsule.VerdictSealed)
|
||||||
|
add("seal: after the round time", sealed(tok(subject[:], vecRound.Add(time.Minute), cmstest.TokenOptions{}, tsa)), c, okSig, capsule.VerdictSealedLate)
|
||||||
|
add("seal: the accuracy reaches the round time", sealed(tok(subject[:], vecRound.Add(-time.Second), cmstest.TokenOptions{Accuracy: 2 * time.Second}, tsa)), c, okSig, capsule.VerdictSealedLate)
|
||||||
|
add("seal: over another subject", sealed(tok([]byte("other"), vecSigned, cmstest.TokenOptions{}, tsa)), c, okSig, capsule.VerdictSealInvalid)
|
||||||
|
add("seal: the authority had expired at its time", sealed(tok(subject[:], certTo.AddDate(1, 0, 0), cmstest.TokenOptions{}, tsa)), c, okSig, capsule.VerdictSealInvalid)
|
||||||
|
add("seal: a TSTInfo of version 2", sealed(tok(subject[:], vecSigned, cmstest.TokenOptions{Version: 2}, tsa)), c, okSig, capsule.VerdictSealUnreadable)
|
||||||
|
add("seal: not DER", sealed([]byte("not DER")), c, okSig, capsule.VerdictSealUnreadable)
|
||||||
|
add("seal: SHA-384 in the imprint", sealed(tok(subject[:], vecSigned, cmstest.TokenOptions{Hash: crypto.SHA384}, tsa)), c, okSig, capsule.VerdictSealUnsupported)
|
||||||
|
add("seal: without a context, as a reader of v0.10", sealed(tok(subject[:], vecSigned, cmstest.TokenOptions{}, tsa)), nil, capsule.VerdictSignatureUnchecked, capsule.VerdictSealUnsupported)
|
||||||
|
noSig := capsule.SealSubject(c.ControlCommit, c.HeadDigest, capsule.SigPart(nil))
|
||||||
|
add("seal: over a capsule without a signature", must(capsule.EncodeSecurityWith(nil, must(capsule.EncodeSeal(capsule.SealTypeRFC3161, tok(noSig[:], vecSigned, cmstest.TokenOptions{}, tsa))))),
|
||||||
|
c, capsule.VerdictNoSignature, capsule.VerdictSealed)
|
||||||
|
if err := errors.Join(errs...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// locatorVectors makes the vector of the extension datekeys.capsule: a .dkc of
|
||||||
|
// patterned bytes in an envelope, hidden in a host, its locator sealed with
|
||||||
|
// tlock for round 1000, and the data of the extension with a note.
|
||||||
|
func locatorVectors() (any, error) {
|
||||||
|
p := profile.Quicknet()
|
||||||
|
dkc := patterned("locator dkc", 5000)
|
||||||
|
loc, rest, err := locator.NewEnvelope(dkc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
host := patterned("host file", 3000)
|
||||||
|
file, offset := locator.Hide(host, rest)
|
||||||
|
const cid = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"
|
||||||
|
loc.Addresses = []locator.Address{{URI: "https://ejemplo.org/foto.jpg", Offset: offset}, {URI: "ipfs://" + cid}}
|
||||||
|
plain, err := loc.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
const round = 1000
|
||||||
|
sealed, err := locator.Seal(p, round, loc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dk, err := datekey.Resolve(p, mustRoundTime(p, round))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
const note = "Cartas del viaje a Lisboa"
|
||||||
|
x, err := (&locator.Info{Note: note, DateKey: dk, Sealed: sealed}).Extension()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if x.ID != extension.CapsuleID {
|
||||||
|
return nil, errors.New("not datekeys.capsule")
|
||||||
|
}
|
||||||
|
v := testkit.LocatorVectorFile{
|
||||||
|
Spec: testkit.SpecVersion,
|
||||||
|
Description: "The extension datekeys.capsule of a .dkk and what it points to (spec v0.11, 44.1): an envelope of age with its header apart from its rest, " +
|
||||||
|
"the rest hidden in a host file, the locator sealed with tlock for round 1000, and the data of the extension. Frozen. See testdata/README.md.",
|
||||||
|
Round: round, DateKey: dk.Compact(), Note: note, DKC: hex.EncodeToString(dkc), Rest: hex.EncodeToString(rest), Header: hex.EncodeToString(loc.EnvelopeHeader),
|
||||||
|
Host: hex.EncodeToString(file), HostOffset: offset, Plaintext: hex.EncodeToString(plain), Sealed: hex.EncodeToString(sealed), Extension: hex.EncodeToString(x.Data),
|
||||||
|
EnvelopeKey: hex.EncodeToString(loc.EnvelopeKey[:]), RestDigest: hex32(loc.RestDigest), RestSize: loc.RestSize, CapsuleDigest: hex32(loc.CapsuleDigest),
|
||||||
|
}
|
||||||
|
for _, a := range loc.Addresses {
|
||||||
|
v.Addresses = append(v.Addresses, testkit.LocatorVectorAddress{URI: a.URI, Offset: a.Offset, Host: a.Host()})
|
||||||
|
}
|
||||||
|
for _, base := range []int{100, 3000, 4000, 4060, 4066, 4067, 4068, 4069, 4070, 4071, 4072, 4090, 4094, 4095, 4096, 4097, 4100, 8160, 8190, 8192, 8193, 12000} {
|
||||||
|
v.PaddingCases = append(v.PaddingCases, testkit.LocatorPaddingCase{Base: base, Total: locator.PlaintextLength(base)})
|
||||||
|
}
|
||||||
|
for _, c := range []struct {
|
||||||
|
uri string
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{"https://ejemplo.org/a.bin", true}, {"https://ejemplo.org:8443/x?y=1", true}, {"ipfs://" + cid, true}, {"ipfs://" + cid + "/ruta", true},
|
||||||
|
{"", false}, {"http://ejemplo.org/a", false}, {"file:///etc/passwd", false}, {"ftp://x/y", false}, {"https://user:pass@ejemplo.org/", false},
|
||||||
|
{"https://", false}, {"ipfs://notacid", false}, {"https://ejemplo.org/ñ", false}, {"https://ejemplo.org/a b", false},
|
||||||
|
{"https://%D0%B0pple.com/x", false}, {"https://ejemplo.org%E2%80%AEtxt.exe/", false}, {"https://127.0.0.1/", false}, {"https://[::1]/", false},
|
||||||
|
{"https://0x7f000001/", false}, {"https://a.com:99999999/", false}, {"https://a.com:0/", false}, {"https://xn--pple-43d.com/", true},
|
||||||
|
} {
|
||||||
|
if (locator.CheckURI(c.uri) == nil) != c.ok {
|
||||||
|
return nil, fmt.Errorf("the address %q: accepted %v, want %v", c.uri, !c.ok, c.ok)
|
||||||
|
}
|
||||||
|
v.URICases = append(v.URICases, testkit.LocatorURICase{URI: c.uri, OK: c.ok})
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustRoundTime(p *profile.Profile, round uint64) time.Time {
|
||||||
|
t, err := datekey.RoundTime(p, round)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
package locator_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"g.activething.com/go/DateKeys/extension"
|
||||||
|
"g.activething.com/go/DateKeys/internal/testkit"
|
||||||
|
"g.activething.com/go/DateKeys/locator"
|
||||||
|
"g.activething.com/go/DateKeys/profile"
|
||||||
|
)
|
||||||
|
|
||||||
|
func unhex(t *testing.T, s string) []byte {
|
||||||
|
t.Helper()
|
||||||
|
b, err := hex.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// testdata/vectors/locator.json is frozen: the extension of the .dkk reads,
|
||||||
|
// the sealed locator opens with the release of its round, the rest is found
|
||||||
|
// in the host at its offset, the envelope gives the .dkc back, and the rules
|
||||||
|
// of the addresses and of the padding give what the file says (spec v0.11,
|
||||||
|
// §44.1).
|
||||||
|
func TestLocatorVectors(t *testing.T) {
|
||||||
|
var v testkit.LocatorVectorFile
|
||||||
|
if err := testkit.ReadJSON(filepath.Join("..", "testdata", "vectors", "locator.json"), &v); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
p := profile.Quicknet()
|
||||||
|
|
||||||
|
info, err := locator.ParseInfo(extension.Extension{ID: extension.CapsuleID, Version: 1, Data: unhex(t, v.Extension)})
|
||||||
|
if err != nil || info.Note != v.Note || info.DateKey.Compact() != v.DateKey || info.DateKey.Round != v.Round || !bytes.Equal(info.Sealed, unhex(t, v.Sealed)) {
|
||||||
|
t.Fatalf("the extension: %+v, %v", info, err)
|
||||||
|
}
|
||||||
|
loc, err := locator.Open(p, v.Round, testkit.Release(v.Round), unhex(t, v.Sealed))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
plain, err := loc.Marshal()
|
||||||
|
if err != nil || !bytes.Equal(plain, unhex(t, v.Plaintext)) || len(plain) != locator.Block {
|
||||||
|
t.Fatalf("the plaintext of the locator: %d bytes, %v", len(plain), err)
|
||||||
|
}
|
||||||
|
if hex.EncodeToString(loc.EnvelopeKey[:]) != v.EnvelopeKey || hex.EncodeToString(loc.RestDigest[:]) != v.RestDigest || loc.RestSize != v.RestSize ||
|
||||||
|
hex.EncodeToString(loc.CapsuleDigest[:]) != v.CapsuleDigest || hex.EncodeToString(loc.EnvelopeHeader) != v.Header || len(loc.Addresses) != len(v.Addresses) {
|
||||||
|
t.Fatalf("the fields of the locator: %+v", loc)
|
||||||
|
}
|
||||||
|
for i, a := range v.Addresses {
|
||||||
|
if loc.Addresses[i].URI != a.URI || loc.Addresses[i].Offset != a.Offset || loc.Addresses[i].Host() != a.Host {
|
||||||
|
t.Errorf("address %d: %+v", i, loc.Addresses[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The rest, from the host at the offset of the first address, opens the envelope.
|
||||||
|
rest, err := loc.RestIn(unhex(t, v.Host), v.HostOffset)
|
||||||
|
if err != nil || !bytes.Equal(rest, unhex(t, v.Rest)) {
|
||||||
|
t.Fatalf("the rest in the host: %v", err)
|
||||||
|
}
|
||||||
|
dkc, err := loc.OpenEnvelope(rest)
|
||||||
|
if err != nil || !bytes.Equal(dkc, unhex(t, v.DKC)) {
|
||||||
|
t.Fatalf("the envelope: %v", err)
|
||||||
|
}
|
||||||
|
for _, c := range v.PaddingCases {
|
||||||
|
if got := locator.PlaintextLength(c.Base); got != c.Total || got%locator.Block != 0 {
|
||||||
|
t.Errorf("padding of %d: %d, want %d", c.Base, got, c.Total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, c := range v.URICases {
|
||||||
|
if got := locator.CheckURI(c.URI) == nil; got != c.OK {
|
||||||
|
t.Errorf("%q: accepted %v, want %v", c.URI, got, c.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"file": "format3_sealed.dkc",
|
||||||
|
"format": 3,
|
||||||
|
"capsule_id": "b3d25bb6c74cda4e014c65e3d2f43a69",
|
||||||
|
"datekey": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MTAwMH0",
|
||||||
|
"profile": "datekeys:quicknet:v1",
|
||||||
|
"round": 1000,
|
||||||
|
"unlock_at": "2023-08-23T15:59:24Z",
|
||||||
|
"access_policy": "time_only",
|
||||||
|
"valid": true,
|
||||||
|
"checks": [
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"name": "parse DKC1",
|
||||||
|
"ok": true,
|
||||||
|
"detail": "magic DKC1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"name": "prelude",
|
||||||
|
"ok": true,
|
||||||
|
"detail": "DKC1 v3, PUBLIC_HEADER_LEN=121, SEALED_CONTROL_LEN=458"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 3,
|
||||||
|
"name": "public header",
|
||||||
|
"ok": true,
|
||||||
|
"detail": "121 bytes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 4,
|
||||||
|
"name": "header validation",
|
||||||
|
"ok": true,
|
||||||
|
"detail": "capsule_id=b3d25bb6c74cda4e014c65e3d2f43a69 datekey=dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MTAwMH0 policy=time_only profile=datekeys:quicknet:v1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 5,
|
||||||
|
"name": "sealed control structure",
|
||||||
|
"ok": true,
|
||||||
|
"detail": "one tlock stanza"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 6,
|
||||||
|
"name": "payload structure",
|
||||||
|
"ok": true,
|
||||||
|
"detail": "one X25519 stanza"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 7,
|
||||||
|
"name": "condition",
|
||||||
|
"ok": true,
|
||||||
|
"detail": "round 1000, unlock at 2023-08-23T15:59:24Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 8,
|
||||||
|
"name": "tlock stanza",
|
||||||
|
"ok": true,
|
||||||
|
"detail": "round 1000, chain 52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,173 @@
|
|||||||
|
{
|
||||||
|
"description": "format 3 time_only capsule with a single file, nota.txt, signed with alg 1 by the test key of format3_signed and sealed with seal_type 2 by a test time-stamping authority before the round time: verdicts F4 and S4, with SEAL_SUBJECT and the token in the record",
|
||||||
|
"spec": "0.10",
|
||||||
|
"format": 3,
|
||||||
|
"file": "format3_sealed.dkc",
|
||||||
|
"sha256": "dde5a072d8783227d28279d06d3d226a1fb967c766da626f889d1c6fd76ac9c7",
|
||||||
|
"release": {
|
||||||
|
"round": 1000,
|
||||||
|
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"
|
||||||
|
},
|
||||||
|
"prelude": "444b43310300000000000079000001ca",
|
||||||
|
"public_header": "a5006a646174656b657963617001010250b3d25bb6c74cda4e014c65e3d2f43a69037853646b315f65794a325a584a7a61573975496a6f784c434a755a58523362334a72496a6f695a4746305a57746c65584d3663585670593274755a585136646a45694c434a79623356755a4349364d5441774d48300400",
|
||||||
|
"datekey": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MTAwMH0",
|
||||||
|
"capsule_id": "b3d25bb6c74cda4e014c65e3d2f43a69",
|
||||||
|
"access_policy": "time_only",
|
||||||
|
"structure": "time_only",
|
||||||
|
"unlock_at": "2023-08-23T15:59:24Z",
|
||||||
|
"header_binding": "c108d8b34eb0a4237e6aff192364a28d544cb79c84ac5c80d88c26f8bd5560d8",
|
||||||
|
"outer_stanzas": [
|
||||||
|
{
|
||||||
|
"type": "tlock",
|
||||||
|
"args": [
|
||||||
|
"1000",
|
||||||
|
"52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"payload_stanzas": [
|
||||||
|
{
|
||||||
|
"type": "X25519",
|
||||||
|
"args": [
|
||||||
|
"PQPwIpsXx8ZNHf1Xpr7gnPamVYWLPgD8zIGmXqvSkjY"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"control_cbor": "a60070646174656b6579732d636f6e74726f6c0103025820c108d8b34eb0a4237e6aff192364a28d544cb79c84ac5c80d88c26f8bd5560d803582087351091f87f94f2c1155e5bde726116123e321f6cc2c3f2bb934298e0d02ea8064800000000000080930702",
|
||||||
|
"payload_identity": "87351091f87f94f2c1155e5bde726116123e321f6cc2c3f2bb934298e0d02ea8",
|
||||||
|
"payload_length": 32915,
|
||||||
|
"padding": 2,
|
||||||
|
"padded_length": 34816,
|
||||||
|
"plaintext_file": "format3_sealed.plaintext",
|
||||||
|
"plaintext_sha256": "aea0f5feb40acd81ca3b02dd21ea15510234da3ab52b374322f3206e7632d47b",
|
||||||
|
"area_len": 32768,
|
||||||
|
"security_cbor": "a40071646174656b6579732d73656375726974790101025869a3000101582092d3a82b1e2387a860d57cae4cc55091d43904fdd625e4d2b285e3a1b4ba674002584024c8a0fd4ef7338a2607c1489e33eb32b2d2c6a9438e8d01d5beb04911acd5bdf7eb4bd958141293e5fe4074276097cd0df21d8ca12b9c2e6cb5164304666c060359038ba20002015903843082038006092a864886f70d010702a08203713082036d020101310d300b06096086480165030402013065060b2a864886f70d0109100104a0560454305202010106032a0304302f300b060960864801650304020104201ce5a87549e8bed1af2238888c5a4e942b3b3de1fa8d757c70941cd21522638d02012a180f32303233303832333135303932375a3003020101a08201b8308201b430820159a0030201020208074ecca73c287e79300a06082a8648ce3d040302304131163014060355040a130d446174654b6579732074657374312730250603550403131e4175746f72696461642064652053656c6c61646f20646520707275656261301e170d3230303130313030303030305a170d3430303130313030303030305a304131163014060355040a130d446174654b6579732074657374312730250603550403131e4175746f72696461642064652053656c6c61646f206465207072756562613059301306072a8648ce3d020106082a8648ce3d03010703420004aa2eb21e68c810b6271dadcc25575401ceccac2461de40cb2bfde25bceaa384abbbba098880bf14316b62769b3596d701d60c8dd1f1313f257d93c4d5f213a53a33b3039300e0603551d0f0101ff04040302078030270603551d0e0420041e4175746f72696461642064652053656c6c61646f20646520707275656261300a06082a8648ce3d0403020349003046022100a05f22801b8432108d6330dd777a25bcfdd1b32db72bc8feb9be522291559e11022100cfd71b1445e68079aa98af90ac7037ab938eb7e4f11279f1476a93ce0b693aff3182013430820130020101304d304131163014060355040a130d446174654b6579732074657374312730250603550403131e4175746f72696461642064652053656c6c61646f206465207072756562610208074ecca73c287e79300b0609608648016503040201a07a301a06092a864886f70d010903310d060b2a864886f70d0109100104302b060b2a864886f70d010910020c311c301a30183016041426e0465376bf0d652c266e5940c9d01ba8cea5e9302f06092a864886f70d01090431220420545889d11d3289646f8156538dbcc608e8cc543fa052056a56a85eb7356523f1300a06082a8648ce3d04030204473045022100883cfcc51038245652e767fdfecea1b7153b9158df02d95f3fcfc2b58e575cb1022029b9727ead61a3022bcddd62d704ee26f71de738556d97796de3ce1c408c7419",
|
||||||
|
"head_cbor": "a4006d646174656b6579732d686561640101025820990d6781941d508558b06652f4c4db8fc56da5a443811b723adfe4370ea139710581a600686e6f74612e7478740116020003160458205d596d5f49c179f0ec337f4610155fc237e727f9dee85377a5fce67a3c853510051a6abcf9c0",
|
||||||
|
"salt": "990d6781941d508558b06652f4c4db8fc56da5a443811b723adfe4370ea13971",
|
||||||
|
"content_offset": 32893,
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "nota.txt",
|
||||||
|
"size": 22,
|
||||||
|
"start": 0,
|
||||||
|
"end": 22,
|
||||||
|
"sha256": "5d596d5f49c179f0ec337f4610155fc237e727f9dee85377a5fce67a3c853510",
|
||||||
|
"mtime": 1790769600
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"verdicts": {
|
||||||
|
"signature": "F4",
|
||||||
|
"seal": "S4",
|
||||||
|
"lines": [
|
||||||
|
"Firmado con la clave dkauthor1jtf6s2c7ywr6scx40jhye32sj82rjp8a6cj7f54jsh36rd96vaqqcg54xg. No prueba quién la tiene.",
|
||||||
|
"Según un sello a nombre de Autoridad de Sellado de prueba, existía el 2023-08-23T15:09:27Z, antes de que la cápsula pudiera abrirse. DateKeys no comprueba quién emitió el sello."
|
||||||
|
],
|
||||||
|
"author_key": "dkauthor1jtf6s2c7ywr6scx40jhye32sj82rjp8a6cj7f54jsh36rd96vaqqcg54xg"
|
||||||
|
},
|
||||||
|
"signature": {
|
||||||
|
"alg": 1,
|
||||||
|
"secret_seed": "294b60d256e79fc4c18b4bcc0a156810b44144619969dadedcbf01d3b37c1054",
|
||||||
|
"author_key": "dkauthor1jtf6s2c7ywr6scx40jhye32sj82rjp8a6cj7f54jsh36rd96vaqqcg54xg",
|
||||||
|
"control_commit": "1793cc0adaba31bdbf18cb4c97ba86dcff05bec498c8ecf11668a74bfd71718a",
|
||||||
|
"head_digest": "a5537946c281a8e22ca9d3a4deb1aebcddf0d299abe47bded31c90157dd72d3a",
|
||||||
|
"signers_digest": "1665c6f3f1afb37b1a87d87e4265f156d7108c3c1762972d2a4241e6e5ab824e",
|
||||||
|
"author_message": "datekeys:dkc3:author-signature:v1\n111b174e204e0eaa73b87601e1e1d9b3d60799b5a0a5c0e371a9dc65d1d39f38\n",
|
||||||
|
"author_code": "111b-174e",
|
||||||
|
"signature": "24c8a0fd4ef7338a2607c1489e33eb32b2d2c6a9438e8d01d5beb04911acd5bdf7eb4bd958141293e5fe4074276097cd0df21d8ca12b9c2e6cb5164304666c06",
|
||||||
|
"security_key_2": "a3000101582092d3a82b1e2387a860d57cae4cc55091d43904fdd625e4d2b285e3a1b4ba674002584024c8a0fd4ef7338a2607c1489e33eb32b2d2c6a9438e8d01d5beb04911acd5bdf7eb4bd958141293e5fe4074276097cd0df21d8ca12b9c2e6cb5164304666c06"
|
||||||
|
},
|
||||||
|
"seal": {
|
||||||
|
"seal_type": 2,
|
||||||
|
"seal_subject": "8e60fd12a9475d95f77adacf81772774f4a8f508ffc7edda2e15df9ccebf163e",
|
||||||
|
"token": "3082038006092a864886f70d010702a08203713082036d020101310d300b06096086480165030402013065060b2a864886f70d0109100104a0560454305202010106032a0304302f300b060960864801650304020104201ce5a87549e8bed1af2238888c5a4e942b3b3de1fa8d757c70941cd21522638d02012a180f32303233303832333135303932375a3003020101a08201b8308201b430820159a0030201020208074ecca73c287e79300a06082a8648ce3d040302304131163014060355040a130d446174654b6579732074657374312730250603550403131e4175746f72696461642064652053656c6c61646f20646520707275656261301e170d3230303130313030303030305a170d3430303130313030303030305a304131163014060355040a130d446174654b6579732074657374312730250603550403131e4175746f72696461642064652053656c6c61646f206465207072756562613059301306072a8648ce3d020106082a8648ce3d03010703420004aa2eb21e68c810b6271dadcc25575401ceccac2461de40cb2bfde25bceaa384abbbba098880bf14316b62769b3596d701d60c8dd1f1313f257d93c4d5f213a53a33b3039300e0603551d0f0101ff04040302078030270603551d0e0420041e4175746f72696461642064652053656c6c61646f20646520707275656261300a06082a8648ce3d0403020349003046022100a05f22801b8432108d6330dd777a25bcfdd1b32db72bc8feb9be522291559e11022100cfd71b1445e68079aa98af90ac7037ab938eb7e4f11279f1476a93ce0b693aff3182013430820130020101304d304131163014060355040a130d446174654b6579732074657374312730250603550403131e4175746f72696461642064652053656c6c61646f206465207072756562610208074ecca73c287e79300b0609608648016503040201a07a301a06092a864886f70d010903310d060b2a864886f70d0109100104302b060b2a864886f70d010910020c311c301a30183016041426e0465376bf0d652c266e5940c9d01ba8cea5e9302f06092a864886f70d01090431220420545889d11d3289646f8156538dbcc608e8cc543fa052056a56a85eb7356523f1300a06082a8648ce3d04030204473045022100883cfcc51038245652e767fdfecea1b7153b9158df02d95f3fcfc2b58e575cb1022029b9727ead61a3022bcddd62d704ee26f71de738556d97796de3ce1c408c7419",
|
||||||
|
"holder": "Autoridad de Sellado de prueba",
|
||||||
|
"time": "2023-08-23T15:09:27Z"
|
||||||
|
},
|
||||||
|
"stages": [
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"name": "parse DKC1",
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"name": "prelude",
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 3,
|
||||||
|
"name": "public header",
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 4,
|
||||||
|
"name": "header validation",
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 5,
|
||||||
|
"name": "sealed control structure",
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 6,
|
||||||
|
"name": "payload structure",
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 7,
|
||||||
|
"name": "condition",
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 8,
|
||||||
|
"name": "tlock stanza",
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 9,
|
||||||
|
"name": "release",
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 10,
|
||||||
|
"name": "release verification",
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 11,
|
||||||
|
"name": "open sealed control",
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 12,
|
||||||
|
"name": "policy structure",
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 14,
|
||||||
|
"name": "control",
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 15,
|
||||||
|
"name": "header binding",
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 16,
|
||||||
|
"name": "payload identity",
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 17,
|
||||||
|
"name": "open payload",
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 18,
|
||||||
|
"name": "commit",
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"file": "format3_signed_cms.dkc",
|
||||||
|
"format": 3,
|
||||||
|
"capsule_id": "fb1b4917149f996c9c9561997593d37d",
|
||||||
|
"datekey": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MTAwMH0",
|
||||||
|
"profile": "datekeys:quicknet:v1",
|
||||||
|
"round": 1000,
|
||||||
|
"unlock_at": "2023-08-23T15:59:24Z",
|
||||||
|
"access_policy": "time_only",
|
||||||
|
"valid": true,
|
||||||
|
"checks": [
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"name": "parse DKC1",
|
||||||
|
"ok": true,
|
||||||
|
"detail": "magic DKC1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"name": "prelude",
|
||||||
|
"ok": true,
|
||||||
|
"detail": "DKC1 v3, PUBLIC_HEADER_LEN=121, SEALED_CONTROL_LEN=458"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 3,
|
||||||
|
"name": "public header",
|
||||||
|
"ok": true,
|
||||||
|
"detail": "121 bytes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 4,
|
||||||
|
"name": "header validation",
|
||||||
|
"ok": true,
|
||||||
|
"detail": "capsule_id=fb1b4917149f996c9c9561997593d37d datekey=dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MTAwMH0 policy=time_only profile=datekeys:quicknet:v1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 5,
|
||||||
|
"name": "sealed control structure",
|
||||||
|
"ok": true,
|
||||||
|
"detail": "one tlock stanza"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 6,
|
||||||
|
"name": "payload structure",
|
||||||
|
"ok": true,
|
||||||
|
"detail": "one X25519 stanza"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 7,
|
||||||
|
"name": "condition",
|
||||||
|
"ok": true,
|
||||||
|
"detail": "round 1000, unlock at 2023-08-23T15:59:24Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 8,
|
||||||
|
"name": "tlock stanza",
|
||||||
|
"ok": true,
|
||||||
|
"detail": "round 1000, chain 52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue