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/capsule/wire_test.go

128 lines
5.4 KiB

package capsule_test
import (
"errors"
"maps"
"slices"
"testing"
datekeys "g.activething.com/go/DateKeys"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/datekey"
"g.activething.com/go/DateKeys/internal/cbortest"
"g.activething.com/go/DateKeys/profile"
)
// swapped encodes m with the entries of keys a and b in each other's place.
func swapped(t *testing.T, m map[uint64]any, a, b uint64) []byte {
t.Helper()
var p cbortest.Pairs
for _, k := range slices.Sorted(maps.Keys(m)) {
switch k {
case a:
k = b
case b:
k = a
}
p = append(p, k, m[k])
}
out, err := cbortest.Marshal(p)
if err != nil {
t.Fatal(err)
}
return out
}
func with(m map[uint64]any, edit func(m map[uint64]any)) map[uint64]any {
c := maps.Clone(m)
edit(c)
return c
}
// Spec §58: the maps of PUBLIC_HEADER and CONTROL_CBOR are closed, their keys
// strictly ascending unsigned integers, and every required key is present.
func TestDecodeMapStructure(t *testing.T) {
dk := datekey.DateKey{ProfileID: profile.QuicknetID, Round: 1000}.Compact()
h := map[uint64]any{0: capsule.HeaderTypeTag, 1: uint64(1), 2: make([]byte, 16), 3: dk, 4: uint64(0)}
c := map[uint64]any{0: capsule.ControlTypeTag, 1: uint64(1), 2: make([]byte, 32), 3: make([]byte, 32)}
exts := []any{ext("a", 1)}
for name, in := range map[string][]byte{
"missing capsule_id": marshal(t, with(h, func(m map[uint64]any) { delete(m, 2) })),
"missing access_policy": marshal(t, with(h, func(m map[uint64]any) { delete(m, 4) })),
"capsule_id as text": marshal(t, with(h, func(m map[uint64]any) { m[2] = "x" })),
"access_policy 2^53": marshal(t, with(h, func(m map[uint64]any) { m[4] = uint64(1) << 53 })),
"unknown key 7": marshal(t, with(h, func(m map[uint64]any) { m[7] = uint64(0) })),
"eight entries": marshal(t, with(h, func(m map[uint64]any) { m[5], m[6], m[7] = exts, []any{ext("b", 1)}, uint64(0) })),
"keys 3 and 4 swapped": swapped(t, h, 3, 4),
"text key": mustMarshal(t, cbortest.Pairs{uint64(0), capsule.HeaderTypeTag, uint64(1), uint64(1), "2", make([]byte, 16)}),
} {
if _, err := capsule.DecodeHeader(in); !errors.Is(err, datekeys.ErrNonCanonicalCBOR) {
t.Errorf("PUBLIC_HEADER %s: %v", name, err)
}
}
// access_policy is 0 or 1 (spec §25): a value whose low byte is 0 or 1
// is not a V1 policy.
for _, p := range []uint64{2, 255, 256, 257, 512, 65536, 65537, 1 << 32, 1<<32 + 1, 1<<53 - 256, 1<<53 - 255, 1<<53 - 1} {
if _, err := capsule.DecodeHeader(marshal(t, with(h, func(m map[uint64]any) { m[4] = p }))); !errors.Is(err, datekeys.ErrNonCanonicalCBOR) {
t.Errorf("PUBLIC_HEADER access_policy %d: %v", p, err)
}
}
for name, in := range map[string][]byte{
"missing payload_identity": marshal(t, with(c, func(m map[uint64]any) { delete(m, 3) })),
"seven entries": marshal(t, with(c, func(m map[uint64]any) { m[4], m[5], m[6] = exts, []any{ext("b", 1)}, uint64(0) })),
"keys 2 and 3 swapped": swapped(t, c, 2, 3),
"text key": mustMarshal(t, cbortest.Pairs{uint64(0), capsule.ControlTypeTag, uint64(1), uint64(1), "2", make([]byte, 32)}),
} {
if _, err := capsule.DecodeControl(in, capsule.Format1); !errors.Is(err, datekeys.ErrNonCanonicalCBOR) {
t.Errorf("CONTROL_CBOR %s: %v", name, err)
}
}
}
func mustMarshal(t *testing.T, v any) []byte {
t.Helper()
b, err := cbortest.Marshal(v)
if err != nil {
t.Fatal(err)
}
return b
}
// Spec §70: the schema version is reported as such whatever follows it, and
// a version that is missing or outside the profile is not a version.
func TestDecodeSchemaVersion(t *testing.T) {
for name, tc := range map[string]struct {
in cbortest.Pairs
want error
}{
"version 2, rest unreadable": {cbortest.Pairs{uint64(0), capsule.HeaderTypeTag, uint64(1), uint64(2), uint64(2), cbortest.Raw{0xff}}, datekeys.ErrUnsupportedVersion},
"version missing": {cbortest.Pairs{uint64(0), capsule.HeaderTypeTag, uint64(2), make([]byte, 16)}, datekeys.ErrNonCanonicalCBOR},
"version null": {cbortest.Pairs{uint64(0), capsule.HeaderTypeTag, uint64(1), nil}, datekeys.ErrNonCanonicalCBOR},
"version before type": {cbortest.Pairs{uint64(1), uint64(1), uint64(0), capsule.HeaderTypeTag}, datekeys.ErrNonCanonicalCBOR},
} {
if _, err := capsule.DecodeHeader(mustMarshal(t, tc.in)); !errors.Is(err, tc.want) {
t.Errorf("%s: got %v, want %v", name, err, tc.want)
}
}
}
// A PUBLIC_HEADER that breaks the CDDL and holds an invalid DateKey reports
// the CDDL violation: the DateKey is parsed after the map (spec §57, §63
// step 4).
func TestDecodeHeaderReportsTheCDDLFirst(t *testing.T) {
h := map[uint64]any{0: capsule.HeaderTypeTag, 1: uint64(1), 2: make([]byte, 16), 3: "dk1_x", 4: uint64(0)}
if _, err := capsule.DecodeHeader(marshal(t, h)); !errors.Is(err, datekeys.ErrDateKeyInvalid) {
t.Fatalf("invalid DateKey alone: %v", err)
}
for name, edit := range map[string]func(m map[uint64]any){
"undefined access_policy": func(m map[uint64]any) { m[4] = uint64(2) },
"access_policy 256": func(m map[uint64]any) { m[4] = uint64(256) },
"extensions out of order": func(m map[uint64]any) { m[6] = []any{ext("b", 1), ext("a", 1)} },
"id in both arrays": func(m map[uint64]any) { m[5], m[6] = []any{ext("a", 1)}, []any{ext("a", 1)} },
} {
if _, err := capsule.DecodeHeader(marshal(t, with(h, edit))); !errors.Is(err, datekeys.ErrNonCanonicalCBOR) {
t.Errorf("%s and an invalid DateKey: %v", name, err)
}
}
}

Powered by TurnKey Linux.