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/framing_test.go

193 lines
6.7 KiB

package capsule_test
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"strings"
"testing"
datekeys "github.com/datekeys/datekeys-go"
"github.com/datekeys/datekeys-go/capsule"
"github.com/datekeys/datekeys-go/codec"
"github.com/datekeys/datekeys-go/datekey"
"github.com/datekeys/datekeys-go/extension"
"github.com/datekeys/datekeys-go/internal/testkit"
"github.com/datekeys/datekeys-go/profile"
)
func TestPolicyNames(t *testing.T) {
for _, p := range []capsule.Policy{capsule.TimeOnly, capsule.TimeAndKey} {
got, err := capsule.ParsePolicy(p.String())
if err != nil || got != p {
t.Fatalf("%s: %v", p, err)
}
}
if _, err := capsule.ParsePolicy("time_or_key"); err == nil {
t.Fatal("unknown policy parsed")
}
if capsule.Policy(9).String() != "policy(9)" {
t.Fatal("unknown policy name")
}
}
func TestControlIsNotPrinted(t *testing.T) {
c := capsule.Control{}
for i := range c.PayloadIdentity {
c.PayloadIdentity[i] = 0xab
}
for _, s := range []string{fmt.Sprint(c), fmt.Sprintf("%+v", &c), fmt.Sprintf("%#v", c)} {
if strings.Contains(s, "abab") || !strings.Contains(s, "REDACTED") {
t.Fatalf("I_PAYLOAD printed: %s", s)
}
}
}
func TestEncodeHeaderAndControlReject(t *testing.T) {
dk := datekey.DateKey{ProfileID: profile.QuicknetID, Round: 1000}
dup := []extension.Extension{{ID: "a", Version: 1}, {ID: "a", Version: 2}}
for name, h := range map[string]capsule.Header{
"invalid DateKey": {},
"unknown policy": {DateKey: dk, Policy: 5},
"repeated critical": {DateKey: dk, Critical: dup},
"repeated noncritical": {DateKey: dk, Noncritical: dup},
"both arrays": {DateKey: dk, Critical: dup[:1], Noncritical: dup[1:]},
} {
if _, err := capsule.EncodeHeader(&h); err == nil {
t.Errorf("%s: accepted", name)
}
}
for name, c := range map[string]capsule.Control{
"repeated critical": {Critical: dup},
"repeated noncritical": {Noncritical: dup},
"both arrays": {Critical: dup[:1], Noncritical: dup[1:]},
} {
if _, err := capsule.EncodeControl(&c); err == nil {
t.Errorf("control %s: accepted", name)
}
}
}
func marshal(t *testing.T, m map[uint64]any) []byte {
t.Helper()
b, err := codec.Marshal(m)
if err != nil {
t.Fatal(err)
}
return b
}
func ext(id string, v uint64) map[uint64]any { return map[uint64]any{0: id, 1: v} }
func TestDecodeHeaderRejects(t *testing.T) {
dk := datekey.DateKey{ProfileID: profile.QuicknetID, Round: 1000}.Compact()
base := func() map[uint64]any {
return map[uint64]any{0: capsule.HeaderTypeTag, 1: uint64(1), 2: make([]byte, 16), 3: dk, 4: uint64(0)}
}
for _, tc := range []struct {
name string
edit func(m map[uint64]any)
want error
}{
{"short capsule_id", func(m map[uint64]any) { m[2] = make([]byte, 15) }, datekeys.ErrNonCanonicalCBOR},
{"invalid DateKey", func(m map[uint64]any) { m[3] = "dk1_x" }, datekeys.ErrDateKeyInvalid},
{"type tag", func(m map[uint64]any) { m[0] = capsule.ControlTypeTag }, datekeys.ErrNonCanonicalCBOR},
{"critical out of order", func(m map[uint64]any) { m[5] = []any{ext("b", 1), ext("a", 1)} }, datekeys.ErrNonCanonicalCBOR},
{"noncritical repeated", func(m map[uint64]any) { m[6] = []any{ext("a", 1), ext("a", 2)} }, datekeys.ErrNonCanonicalCBOR},
{"both arrays", func(m map[uint64]any) { m[5] = []any{ext("a", 1)}; m[6] = []any{ext("a", 1)} }, datekeys.ErrNonCanonicalCBOR},
} {
m := base()
tc.edit(m)
if _, err := capsule.DecodeHeader(marshal(t, m)); !errors.Is(err, tc.want) {
t.Errorf("%s: got %v, want %v", tc.name, err, tc.want)
}
}
if _, err := capsule.DecodeHeader(marshal(t, base())); err != nil {
t.Fatalf("valid header rejected: %v", err)
}
}
func TestDecodeControlRejects(t *testing.T) {
base := func() map[uint64]any {
return map[uint64]any{0: capsule.ControlTypeTag, 1: uint64(1), 2: make([]byte, 32), 3: make([]byte, 32)}
}
for _, tc := range []struct {
name string
edit func(m map[uint64]any)
want error
}{
{"schema version", func(m map[uint64]any) { m[1] = uint64(2) }, datekeys.ErrUnsupportedVersion},
{"type tag", func(m map[uint64]any) { m[0] = capsule.HeaderTypeTag }, datekeys.ErrNonCanonicalCBOR},
{"short binding", func(m map[uint64]any) { m[2] = make([]byte, 31) }, datekeys.ErrNonCanonicalCBOR},
{"long identity", func(m map[uint64]any) { m[3] = make([]byte, 33) }, datekeys.ErrNonCanonicalCBOR},
{"unknown key", func(m map[uint64]any) { m[6] = "x" }, datekeys.ErrNonCanonicalCBOR},
{"critical repeated", func(m map[uint64]any) { m[4] = []any{ext("a", 1), ext("a", 2)} }, datekeys.ErrNonCanonicalCBOR},
{"noncritical out of order", func(m map[uint64]any) { m[5] = []any{ext("b", 1), ext("a", 1)} }, datekeys.ErrNonCanonicalCBOR},
{"both arrays", func(m map[uint64]any) { m[4] = []any{ext("a", 1)}; m[5] = []any{ext("a", 1)} }, datekeys.ErrNonCanonicalCBOR},
} {
m := base()
tc.edit(m)
if _, err := capsule.DecodeControl(marshal(t, m)); !errors.Is(err, tc.want) {
t.Errorf("%s: got %v, want %v", tc.name, err, tc.want)
}
}
if _, err := capsule.DecodeControl([]byte("age-encryption.org/v1\n")); !errors.Is(err, datekeys.ErrNonCanonicalCBOR) {
t.Fatalf("garbage control: %v", err)
}
}
func TestHeaderLimit(t *testing.T) {
big, err := extension.New("org.example.big", 1, bytes.Repeat([]byte{1}, capsule.MaxPublicHeaderLen))
if err != nil {
t.Fatal(err)
}
opts := past(t, 1000)
opts.Noncritical = []extension.Extension{big}
var dkc bytes.Buffer
if _, err := capsule.Encrypt(&dkc, strings.NewReader("x"), opts); err == nil || dkc.Len() != 0 {
t.Fatalf("PUBLIC_HEADER above 1 MiB accepted: %v", err)
}
}
// A .dkk whose critical extension the application does not know is refused
// before any request.
func TestAccessKeyCriticalExtension(t *testing.T) {
f := loadFixture(t, "time_and_key_portable")
k := *f.dkk
k.Critical = []extension.Extension{{ID: "org.example.must-understand", Version: 1}}
o := f.openOptions(t)
o.AccessKey = &k
src := testkit.NewSource(f.release)
o.Source = src
if _, err := open(t, f.dkc, o); !errors.Is(err, datekeys.ErrExtensionCriticalUnknown) || src.Calls != 0 {
t.Fatalf("got %v (requests %d)", err, src.Calls)
}
o.Extensions = extension.Set{"org.example.must-understand": {1}}
if got, err := open(t, f.dkc, o); err != nil || !bytes.Equal(got, f.plaintext) {
t.Fatalf("known .dkk extension rejected: %v", err)
}
}
type brokenSeeker struct {
*bytes.Reader
seeks int
}
func (b *brokenSeeker) Seek(off int64, whence int) (int64, error) {
b.seeks++
if b.seeks > 1 {
return 0, errors.New("seek failed")
}
return b.Reader.Seek(off, whence)
}
func TestCapsuleDigestSeekFailure(t *testing.T) {
f := loadFixture(t, "time_and_key_portable")
r := &brokenSeeker{Reader: bytes.NewReader(f.dkc)}
if _, err := capsule.Open(context.Background(), io.Discard, r, f.openOptions(t)); err == nil {
t.Fatal("seek failure ignored")
}
}

Powered by TurnKey Linux.