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/internal/testkit/cborvectors.go

659 lines
30 KiB

package testkit
import (
"bytes"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"math"
"strconv"
"strings"
datekeys "g.activething.com/go/DateKeys"
"g.activething.com/go/DateKeys/accesskey"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/codec"
"g.activething.com/go/DateKeys/datekey"
"g.activething.com/go/DateKeys/internal/cbortest"
"g.activething.com/go/DateKeys/profile"
)
// Limits with which the generic vectors of cbor.json are walked: at most three
// nested containers, the most any object of the protocol has (object,
// extension array, extension), and at most 64 bytes in a string and 64 items
// or entries in a container, the size of the largest extension array.
const (
WalkMaxDepth = 3
WalkMaxLen = 64
)
// Schema names of the schema vectors: the object the bytes encode and the
// decoder that reads them.
const (
SchemaProfile = "provider_profile"
SchemaHeader = "public_header"
SchemaControl = "control_cbor"
SchemaDKKBody = "dkk_body"
)
// ResultOK is the result of a vector, mutation or inspection that is accepted.
const ResultOK = "ok"
// CBORVectorFile is testdata/vectors/cbor.json.
type CBORVectorFile struct {
Spec string `json:"spec"`
Description string `json:"description"`
Walk WalkLimits `json:"walk"`
Accept []CBORVector `json:"accept"`
Reject []CBORVector `json:"reject"`
Schemas []SchemaVector `json:"schemas"`
}
// WalkLimits are the maxDepth and maxLen arguments of codec.Walk.
type WalkLimits struct {
MaxDepth int `json:"max_depth"`
MaxLen int `json:"max_len"`
}
// CBORVector is one generic vector: bytes that are, or are not, exactly one
// data item of the CBOR profile of spec §58 within the walk limits.
type CBORVector struct {
Name string `json:"name"`
Hex string `json:"hex"`
// Value is the value of an accepted unsigned integer: a JSON number up
// to 2^53-1, a decimal string above.
Value any `json:"value,omitempty"`
// Error is the code of a rejected vector.
Error string `json:"error,omitempty"`
}
// SchemaVector is one encoded object and the result of its decoder.
type SchemaVector struct {
// Block names the schema the vector exercises: one of the Schema*
// names, "verification_metadata" (inside a .dkk body) or "extension"
// (inside the noncritical_extensions of a PUBLIC_HEADER).
Block string `json:"block"`
// Schema names the object the bytes encode and the decoder to run.
Schema string `json:"schema"`
// Format is the capsule format a control_cbor vector is decoded for,
// which fixes the schema version its control must have (spec §22, §31).
// Absent means 1; the other schemas do not depend on it.
Format int `json:"format,omitempty"`
Name string `json:"name"`
Hex string `json:"hex"`
// Result is ResultOK or the normative code of the rejection.
Result string `json:"result"`
}
// DecodeSchema runs the decoder of the named schema on b, as a reader does
// before any registry is consulted: profile.Decode, capsule.DecodeHeader,
// capsule.DecodeControl for a capsule of the given format (0 meaning 1) or
// accesskey.DecodeBody. Secrets it decodes are wiped.
func DecodeSchema(schema string, b []byte, format int) error {
switch schema {
case SchemaProfile:
_, err := profile.Decode(b)
return err
case SchemaHeader:
_, err := capsule.DecodeHeader(b)
return err
case SchemaControl:
c, err := capsule.DecodeControl(b, capsule.Format(max(format, 1)))
if c != nil {
clear(c.PayloadIdentity[:])
}
return err
case SchemaDKKBody:
k, err := accesskey.DecodeBody(b)
if k != nil {
k.Wipe()
}
return err
}
return fmt.Errorf("testkit: unknown schema %q", schema)
}
// Result returns ResultOK for a nil error and its normative code otherwise.
func Result(err error) string {
if err == nil {
return ResultOK
}
if c := datekeys.Code(err); c != "" {
return c
}
return "error without a normative code: " + err.Error()
}
// CBORVectors computes testdata/vectors/cbor.json with the implementation,
// and fails if any vector does not get the result it is written for.
func CBORVectors() (CBORVectorFile, error) {
f := CBORVectorFile{
Spec: SpecVersion,
Description: "CBOR profile of spec §58 and the schemas of spec/datekeys.cddl, generated by the reference implementation. " +
"accept and reject are walked as one data item of the profile with the limits of walk; " +
"schemas are decoded with the decoder of their schema. See testdata/README.md.",
Walk: WalkLimits{MaxDepth: WalkMaxDepth, MaxLen: WalkMaxLen},
}
var errs []error
for _, g := range genericVectors() {
b, err := hex.DecodeString(g.hex)
if err != nil {
return f, fmt.Errorf("vector %q: %w", g.name, err)
}
got := Result(codec.Walk(b, WalkMaxDepth, WalkMaxLen))
v := CBORVector{Name: g.name, Hex: g.hex}
if g.accept {
if got != ResultOK {
errs = append(errs, fmt.Errorf("vector %q: want accepted, got %s", g.name, got))
}
v.Value = uintValue(b)
f.Accept = append(f.Accept, v)
continue
}
if got != datekeys.ErrNonCanonicalCBOR.Code() {
errs = append(errs, fmt.Errorf("vector %q: want %s, got %s", g.name, datekeys.ErrNonCanonicalCBOR.Code(), got))
}
v.Error = got
f.Reject = append(f.Reject, v)
}
sv, err := schemaVectors()
if err != nil {
return f, err
}
for _, s := range sv {
b, err := cbortest.Marshal(s.value)
if err != nil {
return f, fmt.Errorf("schema vector %s %q: %w", s.block, s.name, err)
}
got := Result(DecodeSchema(s.schema, b, s.format))
if got != s.want {
errs = append(errs, fmt.Errorf("schema vector %s %q: want %s, got %s", s.block, s.name, s.want, got))
}
f.Schemas = append(f.Schemas, SchemaVector{Block: s.block, Schema: s.schema, Format: s.format, Name: s.name, Hex: hex.EncodeToString(b), Result: got})
}
return f, errors.Join(errs...)
}
// uintValue returns the value of b when b is exactly one unsigned integer:
// a number up to 2^53-1, a decimal string above. It returns nil otherwise.
func uintValue(b []byte) any {
v, err := cbortest.Unmarshal(b)
if err != nil {
return nil
}
n, ok := v.(uint64)
if !ok {
return nil
}
if n > codec.MaxSafeUint {
return strconv.FormatUint(n, 10)
}
return n
}
type genericVector struct {
name string
hex string
accept bool
}
func genericVectors() []genericVector {
rep := func(s string, n int) string { return strings.Repeat(s, n) }
// 64 map entries {0: 0, ..., 63: 0}, and one more.
mapEntries := func(n int) string {
var b strings.Builder
for k := range n {
if k < 24 {
fmt.Fprintf(&b, "%02x00", k)
} else {
fmt.Fprintf(&b, "18%02x00", k)
}
}
return b.String()
}
accept := []genericVector{
{name: "uint 0", hex: "00"},
{name: "uint 23 inline", hex: "17"},
{name: "uint 24 one byte", hex: "1818"},
{name: "uint 255 one byte", hex: "18ff"},
{name: "uint 256 two bytes", hex: "190100"},
{name: "uint 65535 two bytes", hex: "19ffff"},
{name: "uint 65536 four bytes", hex: "1a00010000"},
{name: "uint 2^32-1 four bytes", hex: "1affffffff"},
{name: "uint 2^32 eight bytes", hex: "1b0000000100000000"},
{name: "uint 2^53-1 eight bytes", hex: "1b001fffffffffffff"},
{name: "uint 2^53 eight bytes", hex: "1b0020000000000000"},
{name: "uint 2^64-1 eight bytes", hex: "1bffffffffffffffff"},
{name: "empty bstr", hex: "40"},
{name: "bstr of one byte", hex: "4100"},
{name: "bstr of 23 bytes, inline length", hex: "57" + rep("ab", 23)},
{name: "bstr of 24 bytes, one-byte length", hex: "5818" + rep("ab", 24)},
{name: "bstr of 64 bytes, max_len", hex: "5840" + rep("ab", 64)},
{name: "empty text", hex: "60"},
{name: "text a", hex: "6161"},
{name: "text with NUL", hex: "6100"},
{name: "text with leading BOM", hex: "64efbbbf61"},
{name: "text U+FF61", hex: "63efbda1"},
{name: "text U+10000", hex: "64f0908080"},
{name: "text U+10FFFF", hex: "64f48fbfbf"},
{name: "text of 64 bytes, max_len", hex: "7840" + rep("61", 64)},
{name: "empty array", hex: "80"},
{name: "empty map", hex: "a0"},
{name: "map two sorted keys", hex: "a200010101"},
{name: "map keys 23 and 24", hex: "a21700181800"},
{name: "map keys 255 and 256", hex: "a218ff0019010000"},
{name: "map with text and bstr values", hex: "a20061610141" + "00"},
{name: "array of mixed items", hex: "8500406080a0"},
{name: "array of 64 items, max_len", hex: "9840" + rep("00", 64)},
{name: "map of 64 entries, max_len", hex: "b840" + mapEntries(64)},
{name: "containers nested 3 deep, max_depth", hex: "81818100"},
{name: "map in array in map", hex: "a10081a10000"},
}
reject := []genericVector{
{name: "empty input", hex: ""},
{name: "uint 23 with one extra byte", hex: "1817"},
{name: "uint 255 in two bytes", hex: "1900ff"},
{name: "uint 65535 in four bytes", hex: "1a0000ffff"},
{name: "uint 2^32-1 in eight bytes", hex: "1b00000000ffffffff"},
{name: "bstr length not shortest", hex: "5800"},
{name: "text length not shortest", hex: "7800"},
{name: "array length not shortest", hex: "9800"},
{name: "map length not shortest", hex: "b800"},
{name: "map key not shortest", hex: "a1180000"},
{name: "keys out of order", hex: "a201000001"},
{name: "duplicate key", hex: "a200000001"},
{name: "keys out of order in a nested map", hex: "a100a2010000" + "00"},
{name: "text key", hex: "a1616100"},
{name: "bstr key", hex: "a1416100"},
{name: "negative integer key", hex: "a12000"},
{name: "array key", hex: "a18000"},
{name: "float key", hex: "a1f93c0000"},
{name: "indefinite array", hex: "9f01ff"},
{name: "indefinite map", hex: "bf0000ff"},
{name: "indefinite bstr", hex: "5f4100ff"},
{name: "indefinite text", hex: "7f6161ff"},
{name: "break", hex: "ff"},
{name: "tag", hex: "c101"},
{name: "tag 24, encoded CBOR data item", hex: "d8184100"},
{name: "tag 2, bignum", hex: "c24101"},
{name: "float", hex: "f97e00"},
{name: "half-precision float 1.0", hex: "f93c00"},
{name: "single-precision float 1.0", hex: "fa3f800000"},
{name: "double-precision float 1.0", hex: "fb3ff0000000000000"},
{name: "false", hex: "f4"},
{name: "true", hex: "f5"},
{name: "null", hex: "f6"},
{name: "undefined", hex: "f7"},
{name: "simple value 16", hex: "f0"},
{name: "simple value 32", hex: "f820"},
{name: "negative int", hex: "20"},
{name: "negative int -25", hex: "3818"},
{name: "reserved additional information 28", hex: "1c"},
{name: "reserved additional information 29 in a bstr head", hex: "5d"},
{name: "reserved additional information 30 in an array head", hex: "9e"},
{name: "truncated uint", hex: "1901"},
{name: "truncated eight-byte uint", hex: "1b00000000"},
{name: "truncated bstr", hex: "4200"},
{name: "truncated text", hex: "6261"},
{name: "truncated array", hex: "8200"},
{name: "map without the value of its last key", hex: "a100"},
{name: "truncated map head", hex: "b900"},
{name: "length beyond input", hex: "5affffffff"},
{name: "array count beyond input", hex: "9affffffff"},
{name: "map count beyond input", hex: "baffffffff"},
{name: "trailing byte", hex: "0100"},
{name: "trailing byte after a map", hex: "a000"},
{name: "invalid UTF-8", hex: "61ff"},
{name: "overlong UTF-8", hex: "62c080"},
{name: "overlong three-byte UTF-8", hex: "63e08080"},
{name: "UTF-8 surrogate", hex: "63eda080"},
{name: "truncated UTF-8 sequence", hex: "62e282"},
{name: "UTF-8 above U+10FFFF", hex: "64f4908080"},
{name: "invalid UTF-8 in a map value", hex: "a10061ff"},
{name: "tag inside an array", hex: "81c101"},
{name: "float inside a map", hex: "a100f93c00"},
{name: "containers nested 4 deep, above max_depth", hex: "8181818100"},
{name: "bstr of 65 bytes, above max_len", hex: "5841" + rep("ab", 65)},
{name: "text of 65 bytes, above max_len", hex: "7841" + rep("61", 65)},
{name: "array of 65 items, above max_len", hex: "9841" + rep("00", 65)},
{name: "map of 65 entries, above max_len", hex: "b841" + mapEntries(65)},
}
for i := range accept {
accept[i].accept = true
}
return append(accept, reject...)
}
type schemaVector struct {
block, schema, name string
format int // of a control_cbor vector; 0 means 1
value any // encoded with cbortest.Marshal
want string
}
// payloadLength is key 6 of a version 2 control: L in 8 bytes, big-endian.
func payloadLength(l uint64) []byte { return binary.BigEndian.AppendUint64(nil, l) }
// with returns a copy of m with the given keys set; a nil value deletes the key.
func with(m map[uint64]any, kv ...any) map[uint64]any {
c := make(map[uint64]any, len(m)+len(kv)/2)
for k, v := range m {
c[k] = v
}
for i := 0; i < len(kv); i += 2 {
k := uint64(kv[i].(int))
if kv[i+1] == nil {
delete(c, k)
} else {
c[k] = kv[i+1]
}
}
return c
}
// null is CBOR null, which with cannot set because nil deletes a key.
var null = cbortest.Raw{0xf6}
// appendRaw returns the encoding of v followed by extra bytes.
func appendRaw(v any, extra ...byte) cbortest.Raw {
b, err := cbortest.Marshal(v)
if err != nil {
panic(err)
}
return cbortest.Raw(append(b, extra...))
}
func fill(b byte, n int) []byte { return bytes.Repeat([]byte{b}, n) }
func ext(id string, version uint64, data ...any) map[uint64]any {
e := map[uint64]any{0: id, 1: version}
if len(data) > 0 {
e[2] = data[0]
}
return e
}
func exts(n int) []any {
out := make([]any, n)
for i := range out {
out[i] = ext(fmt.Sprintf("org.example.%03d", i), 1)
}
return out
}
// chainHash returns the chain hash that profile.Validate requires of a
// Provider Profile map, the drand chain-info hash: SHA-256 of period (key 7)
// as a big-endian uint32, genesis_time (key 8) as a big-endian int64,
// public_key (key 6), genesis_seed (key 10) and, unless it is "default",
// network (key 4). The vectors below that change one of these keys and keep
// the chain hash consistent test that key's own rule, not the self-check.
func chainHash(m map[uint64]any) []byte {
var n [12]byte
binary.BigEndian.PutUint32(n[:4], uint32(m[7].(uint64)))
binary.BigEndian.PutUint64(n[4:], m[8].(uint64))
h := sha256.New()
h.Write(n[:])
h.Write(m[6].([]byte))
h.Write(m[10].([]byte))
if network := m[4].(string); network != "default" {
h.Write([]byte(network))
}
return h.Sum(nil)
}
func schemaVectors() ([]schemaVector, error) {
const (
nc = "ERR_NON_CANONICAL_CBOR"
unsup = "ERR_UNSUPPORTED_VERSION"
unknown = "ERR_UNKNOWN_PROFILE"
)
qb, err := profile.Quicknet().CanonicalCBOR()
if err != nil {
return nil, err
}
q, err := cbortest.UnmarshalMap(qb)
if err != nil {
return nil, err
}
pairs := func(m map[uint64]any, order ...uint64) cbortest.Pairs {
var p cbortest.Pairs
for _, k := range order {
p = append(p, k, m[k])
}
return p
}
if !bytes.Equal(chainHash(q), q[5].([]byte)) {
return nil, errors.New("testkit: chainHash does not reproduce the Quicknet chain hash")
}
var out []schemaVector
add := func(block, schema, name string, v any, want string) {
out = append(out, schemaVector{block: block, schema: schema, name: name, value: v, want: want})
}
// Provider Profile (spec §11): keys 0 to 10, all required.
pp := func(name string, v any, want string) { add(SchemaProfile, SchemaProfile, name, v, want) }
pp("Quicknet profile", q, ResultOK)
pp("unknown key 11", with(q, 11, uint64(0)), nc)
pp("missing key 10, genesis_seed", with(q, 10, nil), nc)
pp("missing key 6, public_key", with(q, 6, nil), nc)
pp("keys 2 and 3 out of order", pairs(q, 0, 1, 3, 2, 4, 5, 6, 7, 8, 9, 10), nc)
pp("period as a text string", with(q, 7, "3"), nc)
pp("chain_hash as a text string", with(q, 5, strings.Repeat("ab", 32)), nc)
pp("null genesis_seed", with(q, 10, null), nc)
pp("chain_hash of 31 bytes", with(q, 5, q[5].([]byte)[:31]), nc)
pp("genesis_seed of 33 bytes", with(q, 10, append(bytes.Clone(q[10].([]byte)), 0)), nc)
pp("period 0", with(q, 7, uint64(0)), nc)
// rehashed keeps chain_hash consistent with the changed keys.
rehashed := func(kv ...any) map[uint64]any {
m := with(q, kv...)
return with(m, 5, chainHash(m))
}
pp("network default, left out of the chain hash", rehashed(4, "default"), ResultOK)
pp("period of one day, the implementation limit", rehashed(7, uint64(86400)), ResultOK)
pp("period of one day and one second, above the implementation limit", rehashed(7, uint64(86401)), nc)
pp("period 2^53", with(q, 7, uint64(1)<<53), nc)
pp("genesis_time 2^53", with(q, 8, uint64(1)<<53), nc)
pp("negative genesis_time", with(q, 8, -1), nc)
pp("period not in shortest form", with(q, 7, cbortest.Raw{0x1a, 0, 0, 0, 3}), nc)
pp("schema version 2", with(q, 1, uint64(2)), unsup)
pp("schema version 2 and an unknown key 11: the version is read first", with(q, 1, uint64(2), 11, uint64(0)), unsup)
pp("schema version 2^53", with(q, 1, uint64(1)<<53), nc)
pp("type tag of PUBLIC_HEADER", with(q, 0, capsule.HeaderTypeTag), nc)
pp("type tag of PUBLIC_HEADER and schema version 2: the type tag is checked first", with(q, 0, capsule.HeaderTypeTag, 1, uint64(2)), nc)
pp("invalid profile_id", with(q, 2, "Datekeys:quicknet:v1"), unknown)
pp("network changed: the chain hash no longer matches", with(q, 4, "quicknet2"), "ERR_PROFILE_MISMATCH")
pp("network changed with its chain hash", rehashed(4, "quicknet2"), ResultOK)
pp("empty public_key", with(q, 6, []byte{}), unknown)
pp("public_key of 1025 bytes", rehashed(6, fill(0xaa, 1025)), unknown)
pp("public_key that is not a group element", rehashed(6, fill(0x00, 96)), unknown)
pp("public_key the identity element", rehashed(6, append([]byte{0xc0}, fill(0x00, 95)...)), unknown)
pp("provider other than drand", rehashed(3, "drand2"), unknown)
pp("unknown scheme", rehashed(9, "bls-unknown"), unknown)
pp("scheme pedersen-bls-chained, not supported by tlock", rehashed(9, "pedersen-bls-chained"), unknown)
pp("genesis_time 0", rehashed(8, uint64(0)), unknown)
pp("genesis_time 253402300798, 9999-12-31T23:59:58Z", rehashed(8, uint64(profile.MaxUnixTime-1)), ResultOK)
pp("genesis_time 253402300799, 9999-12-31T23:59:59Z", rehashed(8, uint64(profile.MaxUnixTime)), unknown)
// PUBLIC_HEADER (spec §24): keys 0 to 4, optional 5 and 6.
id := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}
dk := datekey.DateKey{ProfileID: profile.QuicknetID, Round: 1000}
h := map[uint64]any{0: capsule.HeaderTypeTag, 1: uint64(capsule.HeaderVersion), 2: id, 3: dk.Compact(), 4: uint64(capsule.TimeOnly)}
dkJSON := func(s string) string { return datekey.Prefix + base64.RawURLEncoding.EncodeToString([]byte(s)) }
ph := func(name string, v any, want string) { add(SchemaHeader, SchemaHeader, name, v, want) }
ph("minimal header", h, ResultOK)
ph("time_and_key policy", with(h, 4, uint64(capsule.TimeAndKey)), ResultOK)
ph("both extension arrays", with(h, 5, []any{ext("org.example.a", 1)}, 6, []any{ext("org.example.b", 1, []byte{0})}), ResultOK)
ph("DateKey of a profile that is not pinned: the registry decides", with(h, 3, datekey.DateKey{ProfileID: "datekeys:evmnet:v1", Round: 1000}.Compact()), ResultOK)
ph("unknown key 7", with(h, 7, uint64(0)), nc)
ph("missing key 2, capsule_id", with(h, 2, nil), nc)
ph("missing key 4, access_policy", with(h, 4, nil), nc)
ph("keys 3 and 4 out of order", pairs(h, 0, 1, 2, 4, 3), nc)
ph("capsule_id as a text string", with(h, 2, "0123456789abcdef"), nc)
ph("DateKey as a byte string", with(h, 3, []byte(dk.Compact())), nc)
ph("capsule_id of 15 bytes", with(h, 2, id[:15]), nc)
ph("capsule_id of 17 bytes", with(h, 2, append(bytes.Clone(id), 0x10)), nc)
ph("access_policy 2", with(h, 4, uint64(2)), nc)
ph("access_policy 256", with(h, 4, uint64(256)), nc)
ph("access_policy not in shortest form", with(h, 4, cbortest.Raw{0x18, 0x00}), nc)
ph("null access_policy", with(h, 4, null), nc)
ph("schema version 2", with(h, 1, uint64(2)), unsup)
ph("schema version 2 and a trailing byte: the version is read first", appendRaw(with(h, 1, uint64(2)), 0x00), unsup)
ph("schema version 2^53", with(h, 1, uint64(1)<<53), nc)
ph("schema version 2^64-1", with(h, 1, uint64(math.MaxUint64)), nc)
ph("type tag of CONTROL_CBOR", with(h, 0, capsule.ControlTypeTag), nc)
ph("type tag of CONTROL_CBOR and schema version 2: the type tag is checked first", with(h, 0, capsule.ControlTypeTag, 1, uint64(2)), nc)
ph("empty critical_extensions", with(h, 5, []any{}), nc)
ph("same extension_id in both arrays", with(h, 5, []any{ext("org.example.a", 1)}, 6, []any{ext("org.example.a", 1)}), nc)
ph("trailing byte after the map", appendRaw(h, 0x00), nc)
ph("non-canonical dk1_ JSON", with(h, 3, dkJSON(`{"version":1, "network":"datekeys:quicknet:v1", "round":1000}`)), "ERR_DATEKEY_NON_CANONICAL")
ph("DateKey that is not dk1_", with(h, 3, "hello"), "ERR_DATEKEY_INVALID")
ph("non-canonical DateKey and undefined access_policy: the CDDL is checked first",
with(h, 3, dkJSON(`{"version":1, "network":"datekeys:quicknet:v1", "round":1000}`), 4, uint64(2)), nc)
// CONTROL_CBOR (spec §31) of a format 1 capsule, schema version 1: keys
// 0 to 3, optional 4 and 5.
c := map[uint64]any{0: capsule.ControlTypeTag, 1: uint64(capsule.Format1), 2: fill(0x11, 32), 3: fill(0x22, 32)}
pc := func(name string, v any, want string) { add(SchemaControl, SchemaControl, name, v, want) }
pc("minimal control", c, ResultOK)
pc("both extension arrays", with(c, 4, []any{ext("org.example.a", 1)}, 5, []any{ext("org.example.b", 1, []byte("x"))}), ResultOK)
pc("key 6, defined only in schema version 2", with(c, 6, uint64(0)), nc)
pc("keys 6 and 7 of schema version 2", with(c, 6, payloadLength(0), 7, uint64(capsule.Reforzado)), nc)
pc("missing key 3, payload_identity", with(c, 3, nil), nc)
pc("missing key 2, header_binding", with(c, 2, nil), nc)
pc("header_binding as a text string", with(c, 2, strings.Repeat("a", 32)), nc)
pc("header_binding of 31 bytes", with(c, 2, fill(0x11, 31)), nc)
pc("payload_identity of 33 bytes", with(c, 3, fill(0x22, 33)), nc)
pc("null payload_identity", with(c, 3, null), nc)
pc("schema version 2", with(c, 1, uint64(2)), unsup)
pc("schema version 2 with keys 6 and 7", with(c, 1, uint64(2), 6, payloadLength(0), 7, uint64(capsule.Reforzado)), unsup)
pc("type tag of PUBLIC_HEADER", with(c, 0, capsule.HeaderTypeTag), nc)
pc("empty noncritical_extensions", with(c, 5, []any{}), nc)
// CONTROL_CBOR of a format 2 capsule, schema version 2 (spec §29.1,
// §31): also keys 6, payload_length, and 7, padding. Without extensions
// it is 103 bytes, whatever L and the code.
c2 := with(c, 1, uint64(capsule.Format2), 6, payloadLength(0), 7, uint64(capsule.Reforzado))
pc2 := func(name string, v any, want string) {
out = append(out, schemaVector{block: SchemaControl, schema: SchemaControl, format: 2, name: "format 2: " + name, value: v, want: want})
}
pc2("minimal control, 103 bytes, L = 0", c2, ResultOK)
pc2("both extension arrays", with(c2, 4, []any{ext("org.example.a", 1)}, 5, []any{ext("org.example.b", 1, []byte("x"))}), ResultOK)
pc2("payload_length 78000", with(c2, 6, payloadLength(78000)), ResultOK)
pc2("payload_length 2^32+1, 48 0000000100000001", with(c2, 6, payloadLength(1<<32+1)), ResultOK)
pc2("payload_length L_MAX = 2^53-2^46", with(c2, 6, payloadLength(capsule.MaxPayloadLength)), ResultOK)
pc2("padding 1, bloque256", with(c2, 7, uint64(capsule.Bloque256)), ResultOK)
pc2("payload_length L_MAX + 1", with(c2, 6, payloadLength(capsule.MaxPayloadLength+1)), nc)
pc2("payload_length 2^53", with(c2, 6, payloadLength(1<<53)), nc)
pc2("payload_length 2^64-1", with(c2, 6, fill(0xff, 8)), nc)
pc2("payload_length of 7 bytes", with(c2, 6, payloadLength(78000)[1:]), nc)
pc2("payload_length of 9 bytes", with(c2, 6, append([]byte{0}, payloadLength(78000)...)), nc)
pc2("payload_length as an unsigned integer", with(c2, 6, uint64(78000)), nc)
pc2("payload_length with a length not in its shortest form", with(c2, 6, cbortest.Raw(append([]byte{0x58, 0x08}, payloadLength(78000)...))), nc)
pc2("padding 0", with(c2, 7, uint64(0)), nc)
pc2("padding 3", with(c2, 7, uint64(3)), nc)
pc2("padding 257", with(c2, 7, uint64(257)), nc)
pc2("padding as a byte string", with(c2, 7, []byte{2}), nc)
pc2("missing key 6, payload_length", with(c2, 6, nil), nc)
pc2("missing key 7, padding", with(c2, 7, nil), nc)
pc2("keys 6 and 7 out of order", pairs(c2, 0, 1, 2, 3, 7, 6), nc)
pc2("unknown key 8", with(c2, 8, uint64(0)), nc)
pc2("schema version 2 without keys 6 and 7", with(c, 1, uint64(2)), nc)
pc2("schema version 1, a valid format 1 control", c, unsup)
pc2("schema version 1 with keys 6 and 7", with(c2, 1, uint64(1)), unsup)
pc2("schema version 3", with(c2, 1, uint64(3)), unsup)
// CONTROL_CBOR of a format 3 capsule, schema version 3 (spec §31): the
// keys of version 2, where L is the length of BODY (spec §29.2).
c3 := with(c2, 1, uint64(capsule.Format3))
pc3 := func(name string, v any, want string) {
out = append(out, schemaVector{block: SchemaControl, schema: SchemaControl, format: 3, name: "format 3: " + name, value: v, want: want})
}
pc3("minimal control, 103 bytes, L = 0", c3, ResultOK)
pc3("both extension arrays", with(c3, 4, []any{ext("org.example.a", 1)}, 5, []any{ext("org.example.b", 1, []byte("x"))}), ResultOK)
pc3("payload_length 84078 and padding 1", with(c3, 6, payloadLength(84078), 7, uint64(capsule.Bloque256)), ResultOK)
pc3("payload_length L_MAX + 1", with(c3, 6, payloadLength(capsule.MaxPayloadLength+1)), nc)
pc3("missing key 6, payload_length", with(c3, 6, nil), nc)
pc3("missing key 7, padding", with(c3, 7, nil), nc)
pc3("schema version 3 without keys 6 and 7", with(c, 1, uint64(3)), nc)
pc3("schema version 2, a valid format 2 control", c2, unsup)
pc3("schema version 1, a valid format 1 control", c, unsup)
pc3("schema version 4", with(c3, 1, uint64(4)), unsup)
// .dkk body (spec §41): keys 0 to 5, optional 6, 7 and 8.
k := map[uint64]any{0: accesskey.TypeTag, 1: uint64(accesskey.SchemaVersion), 2: fill(0x33, 16), 3: fill(0x44, 16), 4: accesskey.TypeX25519, 5: fill(0x55, 32)}
pk := func(name string, v any, want string) { add(SchemaDKKBody, SchemaDKKBody, name, v, want) }
pk("minimal body", k, ResultOK)
pk("verification_metadata and both extension arrays",
with(k, 6, map[uint64]any{0: fill(0x66, 32)}, 7, []any{ext("org.example.a", 1)}, 8, []any{ext("org.example.b", 1, []byte("x"))}), ResultOK)
pk("unknown key 9", with(k, 9, uint64(0)), nc)
pk("missing key 5, access_material", with(k, 5, nil), nc)
pk("missing key 3, capsule_id", with(k, 3, nil), nc)
pk("access_type as a byte string", with(k, 4, []byte(accesskey.TypeX25519)), nc)
pk("credential_id of 15 bytes", with(k, 2, fill(0x33, 15)), nc)
pk("capsule_id of 17 bytes", with(k, 3, fill(0x44, 17)), nc)
pk("null access_material", with(k, 5, null), nc)
pk("access_type x448", with(k, 4, "x448"), "ERR_ACCESS_INVALID")
pk("access_material of 31 bytes", with(k, 5, fill(0x55, 31)), "ERR_ACCESS_INVALID")
pk("access_material of 33 bytes", with(k, 5, fill(0x55, 33)), "ERR_ACCESS_INVALID")
pk("schema version 2", with(k, 1, uint64(2)), unsup)
pk("type tag of CONTROL_CBOR", with(k, 0, capsule.ControlTypeTag), nc)
pk("empty critical_extensions", with(k, 7, []any{}), nc)
// verification_metadata (spec §43), key 6 of a .dkk body.
const vm = "verification_metadata"
pv := func(name string, v any, want string) { add(vm, SchemaDKKBody, name, with(k, 6, v), want) }
pv("capsule_digest", map[uint64]any{0: fill(0x66, 32)}, ResultOK)
pv("empty map", map[uint64]any{}, nc)
pv("unknown key 1 instead of key 0", map[uint64]any{1: fill(0x66, 32)}, nc)
pv("unknown key 1 after capsule_digest", map[uint64]any{0: fill(0x66, 32), 1: uint64(0)}, nc)
pv("capsule_digest of 31 bytes", map[uint64]any{0: fill(0x66, 31)}, nc)
pv("capsule_digest of 33 bytes", map[uint64]any{0: fill(0x66, 33)}, nc)
pv("capsule_digest as a text string", map[uint64]any{0: strings.Repeat("f", 32)}, nc)
pv("null capsule_digest", map[uint64]any{0: null}, nc)
pv("verification_metadata as an array", []any{fill(0x66, 32)}, nc)
pv("null verification_metadata", null, nc)
// Extensions (spec §31, §54), in the noncritical_extensions of a
// PUBLIC_HEADER.
const ex = "extension"
pe := func(name string, v []any, want string) { add(ex, SchemaHeader, name, with(h, 6, v), want) }
pe("one extension without data", []any{ext("org.example.a", 1)}, ResultOK)
pe("data of one byte", []any{ext("org.example.a", 1, []byte{0})}, ResultOK)
pe("data that is not CBOR", []any{ext("org.example.a", 1, []byte{0xff, 0xfe})}, ResultOK)
pe("data 40, an empty byte string", []any{ext("org.example.a", 1, []byte{})}, nc)
pe("data 5801xx, a length not in its shortest form", []any{ext("org.example.a", 1, cbortest.Raw{0x58, 0x01, 0x2a})}, nc)
pe("data as a text string", []any{ext("org.example.a", 1, "public label")}, nc)
pe("null data", []any{ext("org.example.a", 1, null)}, nc)
pe("data as an unsigned integer", []any{ext("org.example.a", 1, uint64(7))}, nc)
pe("data as a map", []any{ext("org.example.a", 1, map[uint64]any{0: uint64(7)})}, nc)
pe("data as an array", []any{ext("org.example.a", 1, []any{[]byte{0}})}, nc)
pe("data as a tagged byte string", []any{ext("org.example.a", 1, cbortest.Raw{0xc1, 0x41, 0x00})}, nc)
pe("data as an indefinite-length byte string", []any{ext("org.example.a", 1, cbortest.Raw{0x5f, 0x41, 0x00, 0xff})}, nc)
pe("64 extensions", exts(64), ResultOK)
pe("65 extensions", exts(65), nc)
pe("empty array", []any{}, nc)
pe("extension_id starting with a BOM", []any{ext("\ufefforg.example.a", 1)}, ResultOK)
pe("U+FF61 before U+10000: UTF-8 byte order", []any{ext("\uff61", 1), ext("\U00010000", 1)}, ResultOK)
pe("U+10000 before U+FF61: UTF-16 order, not UTF-8 byte order", []any{ext("\U00010000", 1), ext("\uff61", 1)}, nc)
pe("extensions out of order", []any{ext("org.example.b", 1), ext("org.example.a", 1)}, nc)
pe("extension_id repeated", []any{ext("org.example.a", 1), ext("org.example.a", 2)}, nc)
pe("extension_version 2^32-1", []any{ext("org.example.a", 1<<32-1)}, ResultOK)
pe("extension_version 2^32", []any{ext("org.example.a", 1<<32)}, nc)
pe("extension_version 2^53", []any{ext("org.example.a", 1<<53)}, nc)
pe("extension_version 0", []any{ext("org.example.a", 0)}, ResultOK)
pe("unknown key 3", []any{map[uint64]any{0: "org.example.a", 1: uint64(1), 3: []byte{0}}}, nc)
pe("missing key 1, extension_version", []any{map[uint64]any{0: "org.example.a"}}, nc)
pe("missing key 0, extension_id", []any{map[uint64]any{1: uint64(1)}}, nc)
pe("keys 0 and 1 out of order", []any{cbortest.Pairs{uint64(1), uint64(1), uint64(0), "org.example.a"}}, nc)
pe("extension_id as a byte string", []any{map[uint64]any{0: []byte("org.example.a"), 1: uint64(1)}}, nc)
pe("empty extension_id", []any{ext("", 1)}, nc)
pe("extension_id of 256 bytes, the implementation limit", []any{ext(strings.Repeat("a", 256), 1)}, ResultOK)
pe("extension_id of 257 bytes, above the implementation limit", []any{ext(strings.Repeat("a", 257), 1)}, nc)
pe("extension_id that is not valid UTF-8", []any{map[uint64]any{0: "org.example.\xff", 1: uint64(1)}}, nc)
pe("extension that is not a map", []any{uint64(1)}, nc)
return out, nil
}

Powered by TurnKey Linux.