You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dateKeys-dart/tool/security_go_vectors.go

1354 lines
51 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

//go:build ignore
// Writes the vectors of the security area of datekeys-dart, stage 5b of
// docs/PLAN_dart.md: test/vectors/security_vectors.json, and a part of it as
// a Dart constant, security_vectors.g.dart, for the tests that also run
// compiled to JavaScript. Every expected value is computed here by package
// capsule of the Go reference at the draft v0.12; none is written by hand.
//
// - commitments: PayloadCommit; ControlCommit of the control of every
// fixture of the synced testdata and of controls built here with
// extensions, in their format and in the others, with the text of the
// error where EncodeControl refuses CONTROL_SIG; HeadDigest,
// SignersDigest, AuthorMessage with its AuthorCode, AuthorCode of
// messages that AuthorMessage never writes, SigPart and SealSubject.
// - encode: EncodeSecurity, EncodeSecurityWith, EncodeAuthorSignature and
// EncodeSeal, at the edges of the lengths of CBOR.
// - evaluate: EvaluateSecurityIn in the contexts of the file, and
// EvaluateSecurity without one, on SECURITY_CBOR: the structure of the
// outer map, of author-signature and of seal, each broken in every way
// its schema can be, at its limits; signatures of alg 1, valid and
// invalid, among them the cases of «Taming the many EdDSAs» made over
// AUTHOR_MESSAGE, whose context is searched so that k = SHA-512(R || A ||
// AUTHOR_MESSAGE) mod ℓ is what each case needs; and mutations of a few
// bases drawn from a fixed seed, as edits of the base (see "Edited
// files" in testdata/README.md). Each case gives the verdicts, the key
// and the label of alg 1, the lines as indices into texts, and alg and
// seal_type as SecurityKey2, DecodeAuthorSignature and SecurityKey3 read
// them; cms names the parts that only the reader of CMS evaluates: a
// signature of alg 2, and a seal of seal_type 2, in a context.
// - lines: Verdicts.Lines and SealedAt of verdicts built here, every pair
// of verdicts and the details of signers and seals that alg 2 and
// seal_type 2 give.
// - holder: holderText, how §29.7 shows the name of a certificate, on
// names at its limits and drawn from the seed. Package capsule does not
// export it: this program reaches it with go:linkname, which Go allows
// for a package outside the standard library.
//
// The Ed25519 arithmetic that makes the cases of «Taming the many EdDSAs»
// is restated from internal/testkit (ed25519vectors.go), with math/big:
// only the inputs come from it, and the verdicts from capsule.
//
// Binary values are lower-case hexadecimal. The seed is fixed: every run
// writes the same bytes. Run it in the module of the reference
// implementation, which it imports, without changing anything there, from
// the datekeys-go next to this repository, on the branch v0.12 at c531e93:
//
// cd ../datekeys-go && go run ../datekeys-dart/tool/security_go_vectors.go \
// -testdata ../datekeys-dart/testdata -out ../datekeys-dart/test/vectors
package main
import (
"bytes"
"crypto/ed25519"
"crypto/sha256"
"crypto/sha512"
"encoding/binary"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"math"
"math/big"
"math/rand/v2"
"os"
"path/filepath"
"slices"
"sort"
"strings"
"time"
"unicode/utf16"
_ "unsafe"
"g.activething.com/go/DateKeys/authorkey"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/extension"
)
//go:linkname holderText g.activething.com/go/DateKeys/capsule.holderText
func holderText(name string, hash [32]byte) string
const specVersion = "0.11"
// The fixed seed of every random choice.
var rng = rand.New(rand.NewPCG(0x5b0a052026, 0x5ec))
func must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
func hx(b []byte) string { return hex.EncodeToString(b) }
func randBytes(n int) []byte {
b := make([]byte, n)
for i := range b {
b[i] = byte(rng.IntN(256))
}
return b
}
func digest(s string) [32]byte { return sha256.Sum256([]byte(s)) }
// Case is one vector: a JSON object.
type Case map[string]any
// ---------------------------------------------------------------------------
// CBOR written byte by byte, in the profile and out of it
func head(major byte, n uint64) []byte {
switch {
case n < 24:
return []byte{major<<5 | byte(n)}
case n <= 0xff:
return []byte{major<<5 | 24, byte(n)}
case n <= 0xffff:
return binary.BigEndian.AppendUint16([]byte{major<<5 | 25}, uint16(n))
case n <= 0xffffffff:
return binary.BigEndian.AppendUint32([]byte{major<<5 | 26}, uint32(n))
default:
return binary.BigEndian.AppendUint64([]byte{major<<5 | 27}, n)
}
}
// wide is the head with an argument of 1 << (info - 24) bytes, in its
// shortest form or not.
func wide(major byte, n uint64, info byte) []byte {
b := []byte{major<<5 | info}
size := 1 << (info - 24)
arg := binary.BigEndian.AppendUint64(nil, n)
return append(b, arg[8-size:]...)
}
func uintOf(n uint64) []byte { return head(0, n) }
func bstr(b []byte) []byte { return append(head(2, uint64(len(b))), b...) }
func text(s string) []byte { return append(head(3, uint64(len(s))), s...) }
func cat(bs ...[]byte) []byte { return slices.Concat(bs...) }
// entry is a key and a value, each already encoded.
type entry struct{ k, v []byte }
func mapOf(es ...entry) []byte {
out := head(5, uint64(len(es)))
for _, e := range es {
out = append(append(out, e.k...), e.v...)
}
return out
}
func outer(signature, seal []byte) []byte {
es := []entry{{uintOf(0), text(capsule.SecurityTypeTag)}, {uintOf(1), uintOf(1)}}
if signature != nil {
es = append(es, entry{uintOf(2), bstr(signature)})
}
if seal != nil {
es = append(es, entry{uintOf(3), bstr(seal)})
}
return mapOf(es...)
}
func authorSig(alg uint64, key, value []byte) []byte {
return mapOf(entry{uintOf(0), uintOf(alg)}, entry{uintOf(1), bstr(key)}, entry{uintOf(2), bstr(value)})
}
func sealOf(sealType uint64, token []byte) []byte {
return mapOf(entry{uintOf(0), uintOf(sealType)}, entry{uintOf(1), bstr(token)})
}
// ---------------------------------------------------------------------------
// Ed25519 on math/big, restated from internal/testkit, only to build inputs
var (
edP = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 255), big.NewInt(19))
edL = func() *big.Int {
l, _ := new(big.Int).SetString("7237005577332262213973186563042994240857116359379907606001950938285454250989", 10)
return l
}()
edD = func() *big.Int {
d := new(big.Int).Mul(big.NewInt(-121665), edInv(big.NewInt(121666)))
return d.Mod(d, edP)
}()
edSqrtM1 = new(big.Int).Exp(big.NewInt(2), new(big.Int).Rsh(new(big.Int).Sub(edP, big.NewInt(1)), 2), edP)
)
type edPoint struct{ x, y *big.Int }
func edInv(x *big.Int) *big.Int {
return new(big.Int).Exp(x, new(big.Int).Sub(edP, big.NewInt(2)), edP)
}
func edAdd(a, b edPoint) edPoint {
t := new(big.Int).Mul(edD, a.x)
t.Mul(t, b.x).Mul(t, a.y).Mul(t, b.y).Mod(t, edP)
x := new(big.Int).Add(new(big.Int).Mul(a.x, b.y), new(big.Int).Mul(b.x, a.y))
x.Mul(x, edInv(new(big.Int).Add(big.NewInt(1), t))).Mod(x, edP)
y := new(big.Int).Add(new(big.Int).Mul(a.y, b.y), new(big.Int).Mul(a.x, b.x))
y.Mul(y, edInv(new(big.Int).Mod(new(big.Int).Sub(big.NewInt(1), t), edP))).Mod(y, edP)
return edPoint{x, y}
}
func edMul(k *big.Int, a edPoint) edPoint {
r := edPoint{big.NewInt(0), big.NewInt(1)}
for i := k.BitLen() - 1; i >= 0; i-- {
r = edAdd(r, r)
if k.Bit(i) == 1 {
r = edAdd(r, a)
}
}
return r
}
// edX recovers x from y and its sign bit, or nil when y is not on the curve.
func edX(y *big.Int, sign uint) *big.Int {
yy := new(big.Int).Mul(y, y)
num := new(big.Int).Sub(yy, big.NewInt(1))
den := new(big.Int).Add(new(big.Int).Mul(edD, yy), big.NewInt(1))
xx := new(big.Int).Mul(num, edInv(den.Mod(den, edP)))
xx.Mod(xx, edP)
if xx.Sign() == 0 {
return big.NewInt(0)
}
x := new(big.Int).Exp(xx, new(big.Int).Rsh(new(big.Int).Add(edP, big.NewInt(3)), 3), edP)
if new(big.Int).Mod(new(big.Int).Sub(new(big.Int).Mul(x, x), xx), edP).Sign() != 0 {
x.Mul(x, edSqrtM1).Mod(x, edP)
}
if new(big.Int).Mod(new(big.Int).Sub(new(big.Int).Mul(x, x), xx), edP).Sign() != 0 {
return nil
}
if x.Bit(0) != sign {
x.Sub(edP, x)
}
return x
}
func edBase() edPoint {
y := new(big.Int).Mul(big.NewInt(4), edInv(big.NewInt(5)))
y.Mod(y, edP)
return edPoint{edX(y, 0), y}
}
func leInt(b []byte) *big.Int {
be := slices.Clone(b)
slices.Reverse(be)
return new(big.Int).SetBytes(be)
}
func leBytes(x *big.Int) []byte {
b := x.FillBytes(make([]byte, 32))
slices.Reverse(b)
return b
}
func edEncode(a edPoint) []byte {
b := leBytes(a.y)
b[31] |= byte(a.x.Bit(0)) << 7
return b
}
// edTorsion returns the eight points of small order, [i]T for a point T of
// order 8 and i from 0 to 7.
func edTorsion() []edPoint {
for y := int64(2); ; y++ {
x := edX(big.NewInt(y), 0)
if x == nil {
continue
}
t := edMul(edL, edPoint{x, big.NewInt(y)})
if q := edMul(big.NewInt(4), t); q.x.Sign() == 0 && q.y.Cmp(big.NewInt(1)) == 0 {
continue
}
out := make([]edPoint, 8)
out[0] = edPoint{big.NewInt(0), big.NewInt(1)}
for i := 1; i < 8; i++ {
out[i] = edAdd(out[i-1], t)
}
return out
}
}
func hramScalar(r, a, m []byte) *big.Int {
h := sha512.Sum512(slices.Concat(r, a, m))
return new(big.Int).Mod(leInt(h[:]), edL)
}
// nonCanonicalZero is y = p, the non-canonical encoding of y = 0, with the
// sign bit given.
func nonCanonicalZero(sign byte) []byte {
b := make([]byte, 32)
b[0] = 0xed
for i := 1; i < 31; i++ {
b[i] = 0xff
}
b[31] = 0x7f | sign
return b
}
// ---------------------------------------------------------------------------
// Contexts and keys
type context struct {
name string
cc, hd [32]byte
round time.Time // zero: none
keys map[string]string
security *capsule.SecurityContext
}
var contexts []*context
func addContext(c *context) int {
c.security = &capsule.SecurityContext{ControlCommit: c.cc, HeadDigest: c.hd, RoundTime: c.round, AuthorKeys: c.keys}
contexts = append(contexts, c)
return len(contexts) - 1
}
func (c *context) json() Case {
out := Case{"name": c.name, "control_commit": hx(c.cc[:]), "head_digest": hx(c.hd[:])}
if !c.round.IsZero() {
out["round_time"] = c.round.UTC().Format(time.RFC3339Nano)
}
if len(c.keys) > 0 {
out["author_keys"] = c.keys
}
return out
}
func (c *context) message() []byte {
return capsule.AuthorMessage(c.cc, c.hd, capsule.SignersDigest(capsule.AlgEd25519, nil))
}
func keyOf(seed string) *authorkey.Key {
s := digest(seed)
return must(authorkey.NewFromSeed(s[:]))
}
func pub(k *authorkey.Key) string { return must(authorkey.PublicString(k.Public())) }
// ---------------------------------------------------------------------------
// The evaluation of a case
var texts []string
var textIndex = map[string]int{}
func lineIndices(lines []string) []int {
out := []int{}
for _, l := range lines {
i, ok := textIndex[l]
if !ok {
i = len(texts)
texts = append(texts, l)
textIndex[l] = i
}
out = append(out, i)
}
return out
}
// evaluate records the verdicts of security b in the context ctx, -1 for
// none.
func evaluate(c Case, b []byte, ctx int) Case {
var sc *capsule.SecurityContext
if ctx >= 0 {
c["context"] = ctx
sc = contexts[ctx].security
} else {
c["context"] = nil
}
v := capsule.EvaluateSecurityIn(b, sc)
if v.Detail != nil && (v.Signature != capsule.VerdictSignedComplete && v.Signature != capsule.VerdictSignedIncomplete &&
v.Signature != capsule.VerdictSignatureInvalid && v.Seal != capsule.VerdictSealed && v.Seal != capsule.VerdictSealedLate) {
panic("a detail without a verdict that names it")
}
c["signature"], c["seal"] = string(v.Signature), string(v.Seal)
if v.Signature == capsule.VerdictSignedSaved || v.Signature == capsule.VerdictSignedOther {
c["author_key"] = must(authorkey.PublicString(v.AuthorKey[:]))
}
if v.Signature == capsule.VerdictSignedSaved {
c["author_label"] = v.AuthorLabel
}
c["lines"] = lineIndices(v.Lines())
var cms []string
if content, _, err := capsule.SecurityKey2(b); err == nil {
alg, _, _, err := capsule.DecodeAuthorSignature(content)
if err != nil {
panic(err)
}
c["alg"] = alg
if alg == capsule.AlgCMS && sc != nil {
cms = append(cms, "signature")
}
}
if st, _, err := capsule.SecurityKey3(b); err == nil {
c["seal_type"] = st
if st == capsule.SealTypeRFC3161 && sc != nil {
cms = append(cms, "seal")
}
}
if cms != nil {
c["cms"] = cms
}
return c
}
// ---------------------------------------------------------------------------
// The sections
func commitments(testdata string) Case {
var payload, controls, heads, signers, messages, codes, sigParts, subjects []Case
for i := range 6 {
var id [32]byte
switch i {
case 0:
case 1:
for j := range id {
id[j] = 0xff
}
default:
id = digest(fmt.Sprintf("I_PAYLOAD %d", i))
}
pc := capsule.PayloadCommit(id)
payload = append(payload, Case{"identity": hx(id[:]), "commit": hx(pc[:])})
}
// The control of every fixture, and controls built here, each in its
// format and in the others.
type control struct {
name string
b []byte
format capsule.Format
}
var cs []control
names := must(filepath.Glob(filepath.Join(testdata, "fixtures", "*.json")))
sort.Strings(names)
for _, path := range names {
if strings.HasSuffix(path, ".inspect.json") || strings.HasSuffix(path, ".dkk.json") {
continue
}
var rec struct {
Format int `json:"format"`
Control string `json:"control_cbor"`
}
if err := json.Unmarshal(must(os.ReadFile(path)), &rec); err != nil {
panic(err)
}
cs = append(cs, control{strings.TrimSuffix(filepath.Base(path), ".json"), must(hex.DecodeString(rec.Control)), capsule.Format(rec.Format)})
}
exts := func(prefix string) [][]extension.Extension {
return [][]extension.Extension{
nil,
{{ID: prefix + ".a", Version: 1}},
{must(extension.New(prefix+".b", 7, []byte{1, 2, 3}))},
{{ID: prefix, Version: 1}, must(extension.New(prefix+".z", 1<<32-1, randBytes(40)))},
}
}
critical, noncritical := exts("org.example.c"), exts("org.example.n")
for i := range 12 {
f := capsule.Format(1 + i%3)
c := capsule.Control{
HeaderBinding: [32]byte(randBytes(32)),
PayloadIdentity: [32]byte(randBytes(32)),
Critical: critical[i%4],
Noncritical: noncritical[(i/4+1)%4],
}
if f != capsule.Format1 {
c.Padding = capsule.Padding(1 + i%2)
c.PayloadLength = []uint64{0, 1, 78000, capsule.MaxPayloadLength}[i%4]
}
cs = append(cs, control{fmt.Sprintf("built %d", i), must(capsule.EncodeControl(&c, f)), f})
}
for _, c := range cs {
decoded := must(capsule.DecodeControl(c.b, c.format))
for _, f := range []capsule.Format{capsule.Format1, capsule.Format2, capsule.Format3} {
out := Case{"name": c.name, "control": hx(c.b), "decode_format": int(c.format), "format": int(f)}
if cc, err := capsule.ControlCommit(decoded, f); err != nil {
out["error"] = err.Error()
} else {
out["commit"] = hx(cc[:])
}
controls = append(controls, out)
}
}
for _, n := range []int{0, 1, 55, 56, 63, 64, 65, 1000} {
h := randBytes(n)
d := capsule.HeadDigest(h)
heads = append(heads, Case{"head": hx(h), "digest": hx(d[:])})
}
for _, s := range []struct {
alg uint64
signers []byte
}{
{1, nil}, {1, []byte{}}, {2, must(capsule.EncodeSigners([][32]byte{digest("a")}))},
{2, must(capsule.EncodeSigners([][32]byte{digest("a"), digest("b"), digest("c")}))},
{0, nil}, {3, randBytes(10)}, {0xffffffff, randBytes(33)}, {0x01020304, nil},
} {
d := capsule.SignersDigest(uint32(s.alg), s.signers)
c := Case{"alg": s.alg, "digest": hx(d[:])}
if s.signers != nil {
c["signers"] = hx(s.signers)
}
signers = append(signers, c)
}
for range 8 {
cc, hd, sd := [32]byte(randBytes(32)), [32]byte(randBytes(32)), [32]byte(randBytes(32))
m := capsule.AuthorMessage(cc, hd, sd)
messages = append(messages, Case{"control_commit": hx(cc[:]), "head_digest": hx(hd[:]),
"signers_digest": hx(sd[:]), "message": string(m), "code": capsule.AuthorCode(m)})
}
// AuthorCode of messages of other lengths and of bytes that AuthorMessage
// never writes: the code is Go's string of them, given as its bytes and
// as JSON writes it, U+FFFD for each byte that is not UTF-8.
valid := capsule.AuthorMessage(digest("cc"), digest("hd"), capsule.SignersDigest(1, nil))
at := len(capsule.AuthorMessagePrefix) + 1
edit := func(b []byte, off int, with ...byte) []byte {
out := slices.Clone(b)
copy(out[off:], with)
return out
}
for _, m := range [][]byte{
valid, valid[:98], append(slices.Clone(valid), '\n'), {}, bytes.Repeat([]byte{'a'}, 99),
make([]byte, 99),
edit(valid, at, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'),
edit(valid, at, 0xff, 0xfe, 'a', 'b', 0x80, 'c', 'd', 'e'),
edit(valid, at+3, 0xc3, 0xa9),
edit(valid, at, 0xef, 0xbb, 0xbf, 'x', 0xe2, 0x82, 0xac, 'y'),
edit(valid, at, 0xf0, 0x9f, 0x98, 0x80, 0xed, 0xa0, 0x80, 'z'),
edit(valid, at, 0xe2, 0x82, '-', '-', 0xf4, 0x90, 0x80, 0x80),
} {
code := capsule.AuthorCode(m)
codes = append(codes, Case{"message": hx(m), "code": code, "code_hex": hx([]byte(code))})
}
for _, s := range [][]byte{nil, {}, {0}, randBytes(1), randBytes(105), randBytes(1000)} {
p := capsule.SigPart(s)
c := Case{"sig_part": hx(p)}
if s != nil {
c["signature"] = hx(s)
}
sigParts = append(sigParts, c)
}
for _, p := range [][]byte{capsule.SigPart(nil), capsule.SigPart(randBytes(105)), {}, randBytes(7)} {
cc, hd := [32]byte(randBytes(32)), [32]byte(randBytes(32))
s := capsule.SealSubject(cc, hd, p)
subjects = append(subjects, Case{"control_commit": hx(cc[:]), "head_digest": hx(hd[:]), "sig_part": hx(p), "seal_subject": hx(s[:])})
}
return Case{"payload_commit": payload, "control_commit": controls, "head_digest": heads, "signers_digest": signers,
"author_message": messages, "author_code": codes, "sig_part": sigParts, "seal_subject": subjects}
}
func encodeCases() []Case {
var out []Case
out = append(out, Case{"what": "security", "hex": hx(capsule.EncodeSecurity())})
for _, s := range [][2][]byte{
{nil, nil}, {{1}, nil}, {nil, {2}}, {{1}, {2}}, {randBytes(23), randBytes(24)},
{randBytes(255), randBytes(256)}, {bytes.Repeat([]byte{0xab}, 65535), nil}, {nil, randBytes(300)},
} {
c := Case{"what": "security_with"}
blob(c, "hex", must(capsule.EncodeSecurityWith(s[0], s[1])))
if s[0] != nil {
blob(c, "signature", s[0])
}
if s[1] != nil {
blob(c, "seal", s[1])
}
out = append(out, c)
}
for _, alg := range []uint64{1, 2, 23, 24, 255, 256, 65535, 65536, 0xffffffff} {
key, value := randBytes(rng.IntN(40)), randBytes(rng.IntN(300))
out = append(out, Case{"what": "author_signature", "alg": alg, "key": hx(key), "value": hx(value),
"hex": hx(must(capsule.EncodeAuthorSignature(alg, key, value)))})
}
for _, st := range []uint64{1, 2, 3, 0xffffffff} {
token := randBytes(rng.IntN(300))
out = append(out, Case{"what": "seal", "seal_type": st, "token": hx(token), "hex": hx(must(capsule.EncodeSeal(st, token)))})
}
return out
}
// blob records b in c as name, its hex, or as name_parts when it is long.
func blob(c Case, name string, b []byte) {
if len(b) > 4096 {
c[name+"_parts"] = parts(b)
} else {
c[name] = hx(b)
}
}
// parts writes b as [hex, count] runs, so that a case of 64 KiB is short.
func parts(b []byte) [][]any {
var out [][]any
for i := 0; i < len(b); {
j := i + 1
for j < len(b) && b[j] == b[i] {
j++
}
if j-i >= 16 {
out = append(out, []any{hx(b[i : i+1]), j - i})
i = j
continue
}
start := i
for i < len(b) {
k := i + 1
for k < len(b) && b[k] == b[i] {
k++
}
if k-i >= 16 {
break
}
i = k
}
out = append(out, []any{hx(b[start:i]), 1})
}
return out
}
func evaluateCases() (bases []string, cases []Case) {
k0, k1 := keyOf("datekeys-dart: author 0"), keyOf("datekeys-dart: author 1")
round := time.Date(2030, 1, 1, 0, 0, 0, 0, time.UTC)
cc0, hd0 := digest("datekeys-dart: control 0"), digest("datekeys-dart: head 0")
c0 := addContext(&context{name: "a capsule", cc: cc0, hd: hd0, round: round})
c1 := addContext(&context{name: "the same, with keys saved", cc: cc0, hd: hd0, round: round,
keys: map[string]string{pub(k0): "Ana", pub(k1): "Luis"}})
c2 := addContext(&context{name: "another capsule, with keys saved", cc: digest("datekeys-dart: control 1"), hd: hd0, round: round,
keys: map[string]string{pub(k0): "Ana"}})
c3 := addContext(&context{name: "the same, with a key saved in upper case", cc: cc0, hd: hd0, round: round,
keys: map[string]string{strings.ToUpper(pub(k0)): "ANA", pub(k1): "Luis"}})
c4 := addContext(&context{name: "the same, without a round time, with a key saved without a label", cc: cc0, hd: hd0,
keys: map[string]string{pub(k0): ""}})
c5 := addContext(&context{name: "the same, with a label that is not ASCII", cc: cc0, hd: hd0, round: round,
keys: map[string]string{pub(k0): "Mam\u00e1 \U0001f30d \u00abx\u00bb"}})
c6 := addContext(&context{name: "another head", cc: cc0, hd: digest("datekeys-dart: head 1"), round: round})
ctxs := []int{-1, c0, c1, c2, c3, c4, c5, c6}
m0 := contexts[c0].message()
sig0 := k0.Sign(m0)
sig1 := k1.Sign(m0)
signers := must(capsule.EncodeSigners([][32]byte{digest("a certificate")}))
auth0 := authorSig(1, k0.Public(), sig0)
// The bases, as the contents of their keys 2 and 3, nil when absent.
pairs := [][2][]byte{
{nil, nil},
{auth0, nil},
{auth0, sealOf(1, randBytes(32))},
{auth0, sealOf(2, randBytes(40))},
{authorSig(2, signers, randBytes(60)), nil},
{authorSig(capsule.AlgTest, randBytes(32), randBytes(64)), sealOf(capsule.SealTypeTest, randBytes(32))},
{nil, sealOf(1, randBytes(20))},
{authorSig(1, k1.Public(), sig1), nil},
{authorSig(1, randBytes(31), randBytes(64)), sealOf(3, nil)},
}
var baseList [][]byte
for _, p := range pairs {
b := outer(p[0], p[1])
if !bytes.Equal(b, must(capsule.EncodeSecurityWith(p[0], p[1]))) {
panic("a base is not what EncodeSecurityWith writes")
}
baseList = append(baseList, b)
bases = append(bases, hx(b))
}
add := func(name string, b []byte, ctx int) {
c := Case{"name": name}
if len(b) > 4096 {
c["parts"] = parts(b)
} else {
c["hex"] = hx(b)
}
cases = append(cases, evaluate(c, b, ctx))
}
// Each base in every context.
for i, b := range baseList {
for _, ctx := range ctxs {
c := Case{"name": fmt.Sprintf("base %d", i), "base": i, "edits": [][]any{}}
cases = append(cases, evaluate(c, b, ctx))
}
}
// The outer map, broken in every way.
seal1 := sealOf(1, randBytes(8))
std := []entry{{uintOf(0), text(capsule.SecurityTypeTag)}, {uintOf(1), uintOf(1)}}
key2 := entry{uintOf(2), bstr(auth0)}
key3 := entry{uintOf(3), bstr(seal1)}
full := mapOf(append(slices.Clone(std), key2, key3)...)
for _, o := range []struct {
name string
b []byte
}{
{"the outer map", full},
{"without key 0", mapOf(std[1], key2, key3)},
{"without key 1", mapOf(std[0], key2, key3)},
{"without keys 0 and 1", mapOf(key2, key3)},
{"keys 0 and 1 swapped", mapOf(std[1], std[0], key2, key3)},
{"the type tag of the head", mapOf(entry{uintOf(0), text(capsule.HeadTypeTag)}, std[1], key2)},
{"an empty type tag", mapOf(entry{uintOf(0), text("")}, std[1], key2)},
{"a type tag of 18 bytes", mapOf(entry{uintOf(0), text("datekeys-securityy")}, std[1], key2)},
{"the type tag in upper case", mapOf(entry{uintOf(0), text("DATEKEYS-SECURITY")}, std[1], key2)},
{"the type tag as a byte string", mapOf(entry{uintOf(0), bstr([]byte(capsule.SecurityTypeTag))}, std[1], key2)},
{"the type tag with its length in two bytes", mapOf(entry{uintOf(0), cat(wide(3, 17, 24), []byte(capsule.SecurityTypeTag))}, std[1], key2)},
{"version 0", mapOf(std[0], entry{uintOf(1), uintOf(0)}, key2)},
{"version 2", mapOf(std[0], entry{uintOf(1), uintOf(2)}, key2)},
{"version 1 in two bytes", mapOf(std[0], entry{uintOf(1), wide(0, 1, 24)}, key2)},
{"version 1 as a byte string", mapOf(std[0], entry{uintOf(1), bstr([]byte{1})}, key2)},
{"version 2^53", mapOf(std[0], entry{uintOf(1), uintOf(1 << 53)}, key2)},
{"key 1 in two bytes", mapOf(std[0], entry{wide(0, 1, 24), uintOf(1)}, key2)},
{"key 4, a byte string", mapOf(append(slices.Clone(std), key2, key3, entry{uintOf(4), bstr([]byte{1})})...)},
{"key 4 alone", mapOf(append(slices.Clone(std), entry{uintOf(4), bstr([]byte{1})})...)},
{"key 2 twice", mapOf(append(slices.Clone(std), key2, key2)...)},
{"keys 3 and 2 out of order", mapOf(append(slices.Clone(std), key3, key2)...)},
{"key 2 a text string", mapOf(append(slices.Clone(std), entry{uintOf(2), text("x")})...)},
{"key 2 a map", mapOf(append(slices.Clone(std), entry{uintOf(2), auth0})...)},
{"key 2 an empty byte string", mapOf(append(slices.Clone(std), entry{uintOf(2), bstr(nil)})...)},
{"key 3 an empty byte string", mapOf(append(slices.Clone(std), entry{uintOf(3), bstr(nil)})...)},
{"key 2 with its length not in its shortest form", mapOf(append(slices.Clone(std), entry{uintOf(2), cat(wide(2, uint64(len(auth0)), 25), auth0)})...)},
{"key 2 with a length past the end", cat(head(5, 3), std[0].k, std[0].v, std[1].k, std[1].v, uintOf(2), head(2, 200), auth0)},
{"a map of three entries with two", cat(head(5, 3), std[0].k, std[0].v, std[1].k, std[1].v)},
{"a map of one entry with two", cat(head(5, 1), std[0].k, std[0].v, std[1].k, std[1].v)},
{"a map of five entries", mapOf(append(slices.Clone(std), key2, key3, entry{uintOf(4), uintOf(0)})...)},
{"the head of the map in two bytes", cat(wide(5, 4, 24), full[1:])},
{"an indefinite map", cat([]byte{0xbf}, full[1:], []byte{0xff})},
{"a tag around the map", cat([]byte{0xc1}, full)},
{"an array", cat(head(4, 4), std[0].v, std[1].v, bstr(auth0), bstr(seal1))},
{"a byte more", cat(full, []byte{0})},
{"a byte less", full[:len(full)-1]},
{"a negative key", mapOf(append(slices.Clone(std), entry{[]byte{0x20}, uintOf(0)})...)},
{"a text key", mapOf(append(slices.Clone(std), entry{text("2"), bstr(auth0)})...)},
{"nothing", nil},
{"a byte", []byte{0}},
{"an empty map", []byte{0xa0}},
{"key 2 of 65 536 bytes", outer(make([]byte, 65536), nil)},
{"key 2 of 65 537 bytes", outer(make([]byte, 65537), nil)},
{"key 3 of 65 536 bytes", outer(nil, make([]byte, 65536))},
{"key 3 of 65 537 bytes", outer(nil, make([]byte, 65537))},
} {
add(o.name, o.b, c0)
}
// author-signature, broken in every way, beside a seal of seal_type 1.
for _, s := range []struct {
name string
b []byte
}{
{"alg 1 that verifies", auth0},
{"alg 0", authorSig(0, k0.Public(), sig0)},
{"alg 2^32 - 1", authorSig(0xffffffff, k0.Public(), sig0)},
{"alg 2^32", authorSig(1<<32, k0.Public(), sig0)},
{"alg 2^53", authorSig(1<<53, k0.Public(), sig0)},
{"alg 2^64 - 1", authorSig(math.MaxUint64, k0.Public(), sig0)},
{"alg 3", authorSig(3, k0.Public(), sig0)},
{"alg 23", authorSig(23, k0.Public(), sig0)},
{"alg 24", authorSig(24, k0.Public(), sig0)},
{"alg 256", authorSig(256, k0.Public(), sig0)},
{"alg 1 in two bytes", mapOf(entry{uintOf(0), wide(0, 1, 24)}, entry{uintOf(1), bstr(k0.Public())}, entry{uintOf(2), bstr(sig0)})},
{"alg 1 in nine bytes", mapOf(entry{uintOf(0), wide(0, 1, 27)}, entry{uintOf(1), bstr(k0.Public())}, entry{uintOf(2), bstr(sig0)})},
{"alg as a byte string", mapOf(entry{uintOf(0), bstr([]byte{1})}, entry{uintOf(1), bstr(k0.Public())}, entry{uintOf(2), bstr(sig0)})},
{"an empty key", authorSig(1, nil, sig0)},
{"a key of 31 bytes", authorSig(1, k0.Public()[:31], sig0)},
{"a key of 33 bytes", authorSig(1, append(k0.Public(), 0), sig0)},
{"a key of 64 bytes", authorSig(1, append(k0.Public(), k0.Public()...), sig0)},
{"an empty signature", authorSig(1, k0.Public(), nil)},
{"a signature of 63 bytes", authorSig(1, k0.Public(), sig0[:63])},
{"a signature of 65 bytes", authorSig(1, k0.Public(), append(slices.Clone(sig0), 0))},
{"a signature of 128 bytes", authorSig(1, k0.Public(), append(slices.Clone(sig0), sig0...))},
{"the key as a text string", mapOf(entry{uintOf(0), uintOf(1)}, entry{uintOf(1), text(string(k0.Public()[:8]))}, entry{uintOf(2), bstr(sig0)})},
{"without key 0", mapOf(entry{uintOf(1), bstr(k0.Public())}, entry{uintOf(2), bstr(sig0)})},
{"without key 1", mapOf(entry{uintOf(0), uintOf(1)}, entry{uintOf(2), bstr(sig0)})},
{"without key 2", mapOf(entry{uintOf(0), uintOf(1)}, entry{uintOf(1), bstr(k0.Public())})},
{"a key 3 more", mapOf(entry{uintOf(0), uintOf(1)}, entry{uintOf(1), bstr(k0.Public())}, entry{uintOf(2), bstr(sig0)}, entry{uintOf(3), bstr(nil)})},
{"keys 1 and 0 swapped", mapOf(entry{uintOf(1), bstr(k0.Public())}, entry{uintOf(0), uintOf(1)}, entry{uintOf(2), bstr(sig0)})},
{"key 0 twice", mapOf(entry{uintOf(0), uintOf(1)}, entry{uintOf(0), uintOf(1)}, entry{uintOf(2), bstr(sig0)})},
{"the map head in two bytes", cat(wide(5, 3, 24), auth0[1:])},
{"an indefinite map", cat([]byte{0xbf}, auth0[1:], []byte{0xff})},
{"a byte more", cat(auth0, []byte{0})},
{"a byte less", auth0[:len(auth0)-1]},
{"an array", cat(head(4, 3), uintOf(1), bstr(k0.Public()), bstr(sig0))},
{"a tag", cat([]byte{0xc1}, auth0)},
{"an empty map", []byte{0xa0}},
{"not CBOR", []byte{0xff}},
{"alg 2 with SIGNERS and a SignedData that is not DER", authorSig(2, signers, randBytes(30))},
{"alg 2 with SIGNERS empty", authorSig(2, []byte{0x80}, randBytes(30))},
{"alg 2 with an empty key", authorSig(2, nil, nil)},
} {
add("author-signature: "+s.name, outer(s.b, seal1), c0)
if strings.HasPrefix(s.name, "alg 1 that") || strings.HasPrefix(s.name, "alg 2 ") {
add("author-signature without a context: "+s.name, outer(s.b, seal1), -1)
}
}
// seal, broken in every way, beside a signature of alg 1 that verifies.
for _, s := range []struct {
name string
b []byte
}{
{"seal_type 1", sealOf(1, randBytes(16))},
{"seal_type 2", sealOf(2, randBytes(16))},
{"seal_type 2 with an empty token", sealOf(2, nil)},
{"seal_type 3", sealOf(3, randBytes(16))},
{"seal_type 0", sealOf(0, randBytes(16))},
{"seal_type 2^32 - 1", sealOf(0xffffffff, randBytes(16))},
{"seal_type 2^32", sealOf(1<<32, randBytes(16))},
{"seal_type 2^53 - 1", sealOf(1<<53-1, randBytes(16))},
{"seal_type 1 in two bytes", mapOf(entry{uintOf(0), wide(0, 1, 24)}, entry{uintOf(1), bstr(nil)})},
{"an empty token", sealOf(1, nil)},
{"a token of 65 536 bytes", sealOf(1, make([]byte, 65530))},
{"without key 0", mapOf(entry{uintOf(1), bstr(nil)})},
{"without key 1", mapOf(entry{uintOf(0), uintOf(1)})},
{"a key 2 more", mapOf(entry{uintOf(0), uintOf(1)}, entry{uintOf(1), bstr(nil)}, entry{uintOf(2), uintOf(0)})},
{"an unknown seal_type in a map that breaks its schema", mapOf(entry{uintOf(0), uintOf(99)}, entry{uintOf(1), bstr(nil)}, entry{uintOf(2), uintOf(0)})},
{"keys 1 and 0 swapped", mapOf(entry{uintOf(1), bstr(nil)}, entry{uintOf(0), uintOf(1)})},
{"the token as a text string", mapOf(entry{uintOf(0), uintOf(1)}, entry{uintOf(1), text("x")})},
{"an array", cat(head(4, 2), uintOf(1), bstr(nil))},
{"a byte more", cat(sealOf(1, nil), []byte{0})},
{"a tag", cat([]byte{0xc1}, sealOf(1, nil))},
{"an empty map", []byte{0xa0}},
{"not CBOR", []byte{0xff}},
} {
add("seal: "+s.name, outer(auth0, s.b), c0)
if strings.HasPrefix(s.name, "seal_type 2") {
add("seal without a context: "+s.name, outer(auth0, s.b), -1)
add("seal without a signature: "+s.name, outer(nil, s.b), c0)
}
}
// Signatures of alg 1 that verify, or not, over AUTHOR_MESSAGE.
sigCase := func(name string, key, sig []byte, ctx int) {
add("alg 1: "+name, outer(authorSig(1, key, sig), nil), ctx)
}
sigCase("valid, saved in another capsule", k0.Public(), sig0, c2)
sigCase("valid, in the context of another head", k0.Public(), sig0, c6)
sigCase("valid, by another key", k1.Public(), sig1, c1)
for i := 0; i < 64; i += 7 {
bad := slices.Clone(sig0)
bad[i] ^= 1 << (i % 8)
sigCase(fmt.Sprintf("a bit of byte %d of the signature flipped", i), k0.Public(), bad, c0)
}
for i := 0; i < 32; i += 9 {
bad := k0.Public()
bad[i] ^= 0x10
sigCase(fmt.Sprintf("a bit of byte %d of the key flipped", i), bad, sig0, c0)
}
s := leInt(sig0[32:])
s.Add(s, edL)
sigCase("S + \u2113", k0.Public(), slices.Concat(sig0[:32], leBytes(s)), c0)
high := slices.Clone(sig0)
high[63] |= 0x20
sigCase("S with bit 253 set", k0.Public(), high, c0)
high = slices.Clone(sig0)
high[63] |= 0x80
sigCase("S with bit 255 set", k0.Public(), high, c0)
r := slices.Clone(sig0)
copy(r[:32], nonCanonicalZero(0))
sigCase("R not canonical", k0.Public(), r, c0)
for y := int64(2); ; y++ {
if edX(big.NewInt(y), 0) == nil {
sigCase(fmt.Sprintf("A not on the curve, y = %d", y), leBytes(big.NewInt(y)), sig0, c0)
break
}
}
// The cases of «Taming the many EdDSAs» over AUTHOR_MESSAGE: the
// context of each is searched so that k is what the case needs.
search := func(name string, rEnc, a []byte, ok func(k *big.Int) bool, keys map[string]string) int {
hd := digest("datekeys-dart: the head of " + name)
for n := 0; ; n++ {
cc := digest(fmt.Sprintf("datekeys-dart: the control of %s, %d", name, n))
m := capsule.AuthorMessage(cc, hd, capsule.SignersDigest(capsule.AlgEd25519, nil))
if ok(hramScalar(rEnc, a, m)) {
return addContext(&context{name: name, cc: cc, hd: hd, round: round, keys: keys})
}
}
}
eightDivides := func(k *big.Int) bool { return new(big.Int).Mod(k, big.NewInt(8)).Sign() == 0 }
identity := edEncode(edPoint{big.NewInt(0), big.NewInt(1)})
forged := slices.Concat(identity, make([]byte, 32))
for i, t := range edTorsion() {
a := edEncode(t)
name := fmt.Sprintf("A of small order, the point %d of the torsion, R the identity and S = 0", i)
sigCase(name, a, forged, search(name, identity, a, eightDivides, nil))
}
for _, sign := range []byte{0, 0x80} {
a := nonCanonicalZero(sign)
name := fmt.Sprintf("A not canonical, y = p, sign %d, R the identity and S = 0", sign>>7)
sigCase(name, a, forged, search(name, identity, a, eightDivides, nil))
}
negZero := slices.Clone(identity)
negZero[31] |= 0x80
sigCase("A the identity with the sign bit, R the identity and S = 0", negZero, forged,
search("negZero", identity, negZero, func(*big.Int) bool { return true }, nil))
seed := digest("datekeys-dart: a key of mixed order")
a := new(big.Int).Mod(leInt(seed[:]), edL)
mixed := edEncode(edAdd(edMul(a, edBase()), edTorsion()[1]))
rr := new(big.Int).Mod(leInt(slices.Concat(seed[:], seed[:])), edL)
rp := edEncode(edMul(rr, edBase()))
mixedKey := must(authorkey.PublicString(mixed))
for _, holds := range []bool{true, false} {
name := "A of mixed order, 8 divides k: the equation without the cofactor holds"
if !holds {
name = "A of mixed order, 8 does not divide k: only the equation with the cofactor holds"
}
for _, keys := range []map[string]string{nil, {mixedKey: "Mixta"}} {
n := name
if keys != nil {
n += ", the key saved"
}
ctx := search(n, rp, mixed, func(k *big.Int) bool { return eightDivides(k) == holds }, keys)
k := hramScalar(rp, mixed, contexts[ctx].message())
sv := new(big.Int).Mod(new(big.Int).Add(rr, new(big.Int).Mul(k, a)), edL)
sigCase(n, mixed, slices.Concat(rp, leBytes(sv)), ctx)
}
}
ap := edEncode(edMul(a, edBase()))
ctx := search("R the identity, A of prime order", identity, ap, func(*big.Int) bool { return true }, nil)
k := hramScalar(identity, ap, contexts[ctx].message())
sigCase("R the identity, A of prime order", ap, slices.Concat(identity, leBytes(new(big.Int).Mod(new(big.Int).Mul(k, a), edL))), ctx)
if !ed25519.Verify(ap, contexts[ctx].message(), slices.Concat(identity, leBytes(new(big.Int).Mod(new(big.Int).Mul(k, a), edL)))) {
panic("R the identity: the signature does not verify")
}
// Mutations of the bases, drawn from the seed, one edit each: of
// SECURITY_CBOR itself, or of the content of its key 2 or 3, written
// again with EncodeSecurityWith; such a case gives the key it edits and
// the first 8 bytes of the SHA-256 of the area, which a reader that makes
// it again checks.
for range 1500 {
i := rng.IntN(len(baseList))
b := baseList[i]
sig, seal := pairs[i][0], pairs[i][1]
key := 0
if rng.IntN(3) != 0 {
switch {
case sig != nil && (seal == nil || rng.IntN(2) == 0):
key = 2
case seal != nil:
key = 3
}
}
target := b
switch key {
case 2:
target = sig
case 3:
target = seal
}
e := randomEdit(target)
// slices.Concat gives nil for nothing: an empty content is still
// there, and its key is written with an empty byte string.
mutated := append([]byte{}, slices.Concat(target[:e[0].(int)], must(hex.DecodeString(e[2].(string))), target[e[0].(int)+e[1].(int):])...)
c := Case{"base": i, "edits": [][]any{e}}
if key != 0 {
if key == 2 {
sig = mutated
} else {
seal = mutated
}
mutated = must(capsule.EncodeSecurityWith(sig, seal))
sum := sha256.Sum256(mutated)
c["key"], c["sha256"] = key, hx(sum[:8])
}
cases = append(cases, evaluate(c, mutated, ctxs[rng.IntN(len(ctxs))]))
}
return bases, cases
}
// randomEdit draws one edit of b: a bit flipped, a byte replaced, bytes
// inserted or deleted, a cut, or a byte of the heads of CBOR replaced.
func randomEdit(b []byte) []any {
switch rng.IntN(6) {
case 0:
at := rng.IntN(len(b))
return []any{at, 1, hx([]byte{b[at] ^ 1<<rng.IntN(8)})}
case 1:
at := rng.IntN(len(b))
return []any{at, 1, hx([]byte{byte(rng.IntN(256))})}
case 2:
return []any{rng.IntN(len(b) + 1), 0, hx(randBytes(1 + rng.IntN(4)))}
case 3:
at := rng.IntN(len(b))
return []any{at, min(1+rng.IntN(4), len(b)-at), ""}
case 4:
at := rng.IntN(len(b))
return []any{at, len(b) - at, ""}
default:
heads := []int{0, 1, 2, 3, 4, 5, 19, 20, 21, 22, 23, 24, 25, 38, 39, 40}
at := min(heads[rng.IntN(len(heads))], len(b)-1)
return []any{at, 1, hx([]byte{pickByte()})}
}
}
// pickByte draws a byte among those of the heads of CBOR that change a
// type or a length, and any other.
func pickByte() byte {
special := []byte{0x00, 0x01, 0x02, 0x03, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1f, 0x20, 0x40, 0x41, 0x58, 0x59, 0x5f, 0x60, 0x71, 0x78,
0x80, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xb8, 0xbf, 0xc0, 0xc1, 0xd8, 0xf4, 0xf5, 0xf6, 0xf7, 0xf9, 0xff}
if rng.IntN(4) == 0 {
return byte(rng.IntN(256))
}
return special[rng.IntN(len(special))]
}
// ---------------------------------------------------------------------------
// Lines
func signerJSON(s capsule.SignerLine) Case {
c := Case{"holder": s.Holder, "issuer": s.Issuer, "result": s.Result, "seal_holder": s.SealHolder, "before": s.Before}
if !s.SealTime.IsZero() {
c["seal_time"] = s.SealTime.UTC().Format(time.RFC3339Nano)
}
return c
}
func lineCase(name string, v capsule.Verdicts) Case {
c := Case{"name": name, "signature": string(v.Signature), "seal": string(v.Seal), "author_key": hx(v.AuthorKey[:]),
"author_label": v.AuthorLabel, "lines": v.Lines()}
if d := v.Detail; d != nil {
dj := Case{"signers": []Case{}, "foreign": []Case{}, "seal_holder": d.SealHolder}
for _, s := range d.Signers {
dj["signers"] = append(dj["signers"].([]Case), signerJSON(s))
}
for _, s := range d.Foreign {
dj["foreign"] = append(dj["foreign"].([]Case), signerJSON(s))
}
if !d.SealTime.IsZero() {
dj["seal_time"] = d.SealTime.UTC().Format(time.RFC3339Nano)
}
c["detail"] = dj
}
if t, ok := v.SealedAt(); ok {
c["sealed_at"] = t.UTC().Format(time.RFC3339Nano)
}
return c
}
func lines() []Case {
var out []Case
key := [32]byte(keyOf("datekeys-dart: author 0").Public())
sigs := []capsule.Verdict{"X", "F0", "F1", "F2", "F3", "F4", "F5", "F6"}
seals := []capsule.Verdict{"X", "S0", "S1", "S2", "S3", "S4", "S5"}
for _, s := range sigs {
for _, l := range seals {
out = append(out, lineCase(fmt.Sprintf("%s and %s", s, l), capsule.Verdicts{Signature: s, Seal: l, AuthorKey: key, AuthorLabel: "Ana"}))
}
}
for _, label := range []string{"", "Ana", "Mam\u00e1 \U0001f30d", "a b", "\u00abx\u00bb", "Firmado con la clave"} {
out = append(out, lineCase("F3 with the label "+label, capsule.Verdicts{Signature: "F3", Seal: "S0", AuthorKey: key, AuthorLabel: label}))
}
out = append(out, lineCase("F4 with the zero key", capsule.Verdicts{Signature: "F4", Seal: "S1"}))
t := func(s string) time.Time { return must(time.Parse(time.RFC3339Nano, s)) }
at := []time.Time{t("2026-09-30T12:00:00Z"), t("2026-09-30T12:00:00.5Z"), t("2026-09-30T12:00:00.123456789Z"),
t("2023-08-23T15:09:27.000001Z"), t("2029-12-31T23:59:59.1Z"), t("2030-01-01T00:00:00Z")}
holders := []string{"Ana L\u00f3pez", "Luis G\u00f3mez", "JUAN ESPA\u00d1OL ESPA\u00d1OL", "TSA", "\u674e\u5c0f\u9f99", "\U0001f600",
hx(randBytes(32)), "Autoridad de Sellado de prueba"}
signer := func(i int, result string) capsule.SignerLine {
l := capsule.SignerLine{Holder: holders[i%len(holders)], Issuer: holders[(i+3)%len(holders)], Result: result}
if result == "valid" {
l.SealHolder, l.SealTime, l.Before = holders[(i+7)%len(holders)], at[i%len(at)], i%3 != 2
}
if result == "absent" {
l.Issuer = ""
}
return l
}
results := []string{"valid", "invalid", "absent", "not verifiable", "without seal", "invalid seal", "out of validity"}
for _, n := range []int{1, 2, 3, 16} {
for _, before := range []string{"all", "some", "none"} {
d := &capsule.Detail{}
for i := range n {
l := signer(i, "valid")
switch before {
case "all":
l.Before = true
case "none":
l.Before = false
}
d.Signers = append(d.Signers, l)
}
out = append(out, lineCase(fmt.Sprintf("F6, %d signers, %s before", n, before), capsule.Verdicts{Signature: "F6", Seal: "S0", Detail: d}))
}
}
foreign := &capsule.Detail{Signers: []capsule.SignerLine{signer(0, "valid"), signer(1, "valid")}}
for i, r := range results {
foreign.Foreign = append(foreign.Foreign, signer(i+2, r))
}
out = append(out, lineCase("F6 with foreign signers of every result", capsule.Verdicts{Signature: "F6", Seal: "S0", Detail: foreign}))
for i, r := range results {
d := &capsule.Detail{Signers: []capsule.SignerLine{signer(i, r), signer(i+1, "valid")}, Foreign: []capsule.SignerLine{signer(i+2, r)}}
for _, sig := range []capsule.Verdict{"F2", "F5"} {
out = append(out, lineCase(fmt.Sprintf("%s with a signer %s and a foreign one", sig, r), capsule.Verdicts{Signature: sig, Seal: "S0", Detail: d}))
}
}
for i, tm := range at {
for _, sig := range []capsule.Verdict{"F0", "F1", "F2", "F4", "F6"} {
d := &capsule.Detail{SealHolder: holders[i], SealTime: tm}
if sig == "F6" {
d.Signers = []capsule.SignerLine{signer(i, "valid")}
}
for _, seal := range []capsule.Verdict{"S4", "S5"} {
out = append(out, lineCase(fmt.Sprintf("%s and %s, sealed at %s", sig, seal, tm.Format(time.RFC3339Nano)),
capsule.Verdicts{Signature: sig, Seal: seal, AuthorKey: key, Detail: d}))
}
}
}
out = append(out, lineCase("S4 with an empty detail", capsule.Verdicts{Signature: "F0", Seal: "S4", Detail: &capsule.Detail{}}))
out = append(out, lineCase("F6 without a detail", capsule.Verdicts{Signature: "F6", Seal: "S4"}))
out = append(out, lineCase("F6 with no signer", capsule.Verdicts{Signature: "F6", Seal: "S0", Detail: &capsule.Detail{}}))
// SealedAt: the earliest valid seal, of key 3 or of a valid required
// signer; never that of another signer, or of a seal that is not S4 or
// S5.
early := &capsule.Detail{SealHolder: "TSA", SealTime: at[4],
Signers: []capsule.SignerLine{signer(0, "valid"), signer(1, "invalid"), signer(3, "valid")}}
early.Signers[1].SealTime = t("2000-01-01T00:00:00Z")
early.Foreign = []capsule.SignerLine{signer(5, "valid")}
early.Foreign[0].SealTime = t("2001-01-01T00:00:00Z")
for _, seal := range []capsule.Verdict{"S0", "S3", "S4", "S5"} {
out = append(out, lineCase("the earliest seal with "+string(seal), capsule.Verdicts{Signature: "F5", Seal: seal, Detail: early}))
}
return out
}
// ---------------------------------------------------------------------------
// The names of certificates
func holders() []Case {
var out []Case
add := func(name string) {
h := sha256.Sum256([]byte(name))
out = append(out, Case{"name": name, "hash": hx(h[:]), "result": holderText(name, h)})
}
// A name with lone surrogates, given as UTF-16 code units: its bytes are
// their generalized UTF-8, which is not UTF-8.
addUnits := func(units []uint16) {
var b []byte
for _, u := range units {
b = utf16Append(b, u)
}
h := sha256.Sum256(b)
out = append(out, Case{"units": units, "hash": hx(h[:]), "result": holderText(string(b), h)})
}
fixed := []string{
"", "Ana L\u00f3pez", " Ana", "Ana ", "Ana L\u00f3pez", "Ana\u00a0L\u00f3pez", "Ana\u00a0\u00a0L\u00f3pez", "Ana\tL\u00f3pez", "Ana\nL\u00f3pez",
"Ana\u202eL\u00f3pez", "Ana\u200bL\u00f3pez", "\ufeffAna", "Ana\uffff", "Ana\u0378", "Ana\ue000", "Ana\u3000L\u00f3pez", "Ana\u2028",
"\u0000", "Ana\u007f", "Ana\u0085", "ESPA\u00d1OL ESPA\u00d1OL JUAN - 12345678Z", "e\u0301", "\U0001f3f3\ufe0f\u200d\U0001f308",
"a\ufe0f", "TSA" + strings.Repeat(" ", 50) + "Firmado con la clave que guardaste como Banco", "\u00abAna\u00bb", "-", ".", "a b c",
}
for _, n := range fixed {
add(n)
}
for _, unit := range []string{"a", "\u00e9", "\u4e2d", "\U0001f600", "e\u0301"} {
for _, n := range []int{63, 64, 65} {
// e + U+0301 is two code points.
count := n
if unit == "e\u0301" {
count = n / 2
}
add(strings.Repeat(unit, count))
}
}
add(strings.Repeat("a", 63) + " ")
add(strings.Repeat("ab ", 21) + "a")
addUnits([]uint16{'A', 0xd800, 'B'})
addUnits([]uint16{0xdc00})
addUnits([]uint16{'A', 0xd83d})
// Names drawn from the seed, near the limit of 64 code points: mostly
// letters and spaces, now and then a character that the rules refuse.
good := []string{"a", "Z", "\u00f1", "\u00e9", "\u03a9", "\u4e2d", "\U0001f600", " ", "-", ".", "'", "e\u0301", "\u00a0"}
bad := []string{" ", "\u200b", "\u202e", "\t", "\n", "\ufeff", "\uffff", "\U000e0001", "\u0378", "\ue000", "\u3000",
"\u0000", "\u007f", "\u200d", "\ufe0f", "\U0001f3f3\ufe0f\u200d\U0001f308"}
for range 160 {
var sb strings.Builder
n := 50 + rng.IntN(20)
if rng.IntN(4) == 0 {
n = 1 + rng.IntN(12)
}
for range n {
if rng.IntN(40) == 0 {
sb.WriteString(bad[rng.IntN(len(bad))])
} else {
sb.WriteString(good[rng.IntN(len(good))])
}
}
add(sb.String())
}
return out
}
// utf16Append appends the generalized UTF-8 of the code unit u, a lone
// surrogate included.
func utf16Append(b []byte, u uint16) []byte {
r := rune(u)
switch {
case r < 0x80:
return append(b, byte(r))
case r < 0x800:
return append(b, 0xc0|byte(r>>6), 0x80|byte(r&0x3f))
default:
return append(b, 0xe0|byte(r>>12), 0x80|byte(r>>6&0x3f), 0x80|byte(r&0x3f))
}
}
// ---------------------------------------------------------------------------
// Output
func enc(v any) string {
var b bytes.Buffer
e := json.NewEncoder(&b)
e.SetEscapeHTML(false)
if err := e.Encode(v); err != nil {
panic(err)
}
return strings.TrimSuffix(b.String(), "\n")
}
// ascii writes every character of the JSON s outside ASCII as an escape of
// JSON, a pair of surrogates above U+FFFF, so that the files hold no
// invisible or bidirectional character.
func ascii(s string) string {
var b strings.Builder
for _, r := range s {
switch {
case r < 0x80:
b.WriteRune(r)
case r <= 0xffff:
fmt.Fprintf(&b, `\u%04x`, r)
default:
hi, lo := utf16.EncodeRune(r)
fmt.Fprintf(&b, `\u%04x\u%04x`, hi, lo)
}
}
return b.String()
}
// render writes the fields of a file in this order, each list one case per
// line.
func render(fields []string, values Case) string {
var sb strings.Builder
sb.WriteString("{")
for i, f := range fields {
if i > 0 {
sb.WriteString(",")
}
sb.WriteString("\n " + enc(f) + ": ")
switch v := values[f].(type) {
case []Case:
sb.WriteString("[")
for j, c := range v {
if j > 0 {
sb.WriteString(",")
}
sb.WriteString("\n " + enc(c))
}
sb.WriteString("\n ]")
case Case:
sb.WriteString("{")
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
sort.Strings(keys)
for j, k := range keys {
if j > 0 {
sb.WriteString(",")
}
sb.WriteString("\n " + enc(k) + ": [")
for n, c := range v[k].([]Case) {
if n > 0 {
sb.WriteString(",")
}
sb.WriteString("\n " + enc(c))
}
sb.WriteString("\n ]")
}
sb.WriteString("\n }")
default:
sb.WriteString(enc(v))
}
}
sb.WriteString("\n}\n")
return sb.String()
}
func main() {
testdata := flag.String("testdata", "../datekeys-dart/testdata", "the synced testdata/ of datekeys-dart")
outDir := flag.String("out", "../datekeys-dart/test/vectors", "where the vectors go")
flag.Parse()
commits := commitments(*testdata)
encodes := encodeCases()
bases, cases := evaluateCases()
lineCases := lines()
holderCases := holders()
var ctxJSON []Case
for _, c := range contexts {
ctxJSON = append(ctxJSON, c.json())
}
fields := []string{"spec", "generator", "description", "contexts", "texts", "bases", "commitments", "encode", "evaluate", "lines", "holder"}
whole := Case{
"spec": specVersion,
"generator": "tool/security_go_vectors.go",
"description": "The security area of format 3 as package capsule of the Go reference gives it at the draft v0.12: commitments (PayloadCommit, ControlCommit with decode_format and format, or the text of its error, HeadDigest, SignersDigest, AuthorMessage, AuthorCode as Go's string and its bytes, SigPart, SealSubject); encode (EncodeSecurity, EncodeSecurityWith, EncodeAuthorSignature, EncodeSeal); " +
"evaluate: EvaluateSecurityIn of SECURITY_CBOR (hex, parts as [hex, repeat], or a base with edits) in the context of the index, EvaluateSecurity when null: the verdicts, author_key and author_label of alg 1, lines as indices into texts, alg and seal_type as read, and cms, the parts that only the reader of CMS evaluates; " +
"lines: Verdicts.Lines and SealedAt of verdicts built here; holder: holderText of a name, or of UTF-16 code units, and a hash. See the header of tool/security_go_vectors.go.",
"contexts": ctxJSON,
"texts": texts,
"bases": bases,
"commitments": commits,
"encode": encodes,
"evaluate": cases,
"lines": lineCases,
"holder": holderCases,
}
text := ascii(render(fields, whole))
if err := os.WriteFile(filepath.Join(*outDir, "security_vectors.json"), []byte(text), 0o644); err != nil {
panic(err)
}
fmt.Fprintf(os.Stderr, "security_vectors.json: %d bytes, %d contexts, %d texts, %d evaluations, %d lines, %d names\n",
len(text), len(contexts), len(texts), len(cases), len(lineCases), len(holderCases))
// The part for the tests compiled to JavaScript: everything but every
// eighth evaluation, without the cases of 64 KiB.
var part []Case
for i, c := range cases {
if _, big := c["parts"]; i%8 == 0 && !big {
part = append(part, c)
}
}
small := Case{}
for k, v := range whole {
small[k] = v
}
small["evaluate"] = part
small["description"] = "Part of test/vectors/security_vectors.json: every eighth evaluation."
// No apostrophe, so that the raw string of Dart holds the JSON.
dart := strings.ReplaceAll(ascii(render(fields, small)), "'", `\u0027`)
if strings.Contains(dart, "'''") {
panic("a raw string of Dart cannot hold '''")
}
var b strings.Builder
b.WriteString("// Generated by tool/security_go_vectors.go: a part of\n")
b.WriteString("// test/vectors/security_vectors.json, for the tests that also run compiled\n")
b.WriteString("// to JavaScript, where no file can be read. Do not edit.\n\n")
fmt.Fprintf(&b, "/// Part of test/vectors/security_vectors.json.\nconst securityVectorsJson = r'''\n%s''';\n", dart)
if err := os.WriteFile(filepath.Join(*outDir, "security_vectors.g.dart"), []byte(b.String()), 0o644); err != nil {
panic(err)
}
fmt.Fprintf(os.Stderr, "security_vectors.g.dart: %d bytes, %d evaluations\n", b.Len(), len(part))
}

Powered by TurnKey Linux.