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

136 lines
4.8 KiB

package capsule_test
import (
"bytes"
"testing"
"filippo.io/age"
datekeys "g.activething.com/go/DateKeys"
"g.activething.com/go/DateKeys/accesskey"
"g.activething.com/go/DateKeys/agewrap"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/extension"
"g.activething.com/go/DateKeys/internal/testkit"
"g.activething.com/go/DateKeys/profile"
)
// Spec §55.1: what each section proves, and what it never proves.
func TestTrustModel(t *testing.T) {
// PUBLIC_HEADER never proves authorship: from the public bytes of
// time_only.dkc, or of format2_time_only.dkc, anyone builds a capsule
// with the same PRELUDE, the same PUBLIC_HEADER and another plaintext,
// which passes all 18 steps (spec §36.1).
for _, c := range []struct {
fixture string
format capsule.Format
}{{"time_only", capsule.Format1}, {"format2_time_only", capsule.Format2}} {
g := loadFixture(t, c.fixture)
p, _ := parts(t, c.fixture)
rec, err := agewrap.NewTimeRecipient(profile.Quicknet(), 1000)
if err != nil {
t.Fatal(err)
}
payloadID, err := age.GenerateX25519Identity()
if err != nil {
t.Fatal(err)
}
raw, err := agewrap.RawX25519Identity(payloadID)
if err != nil {
t.Fatal(err)
}
content := []byte("forged")
ctrl := &capsule.Control{}
plaintext := content
if c.format == capsule.Format2 {
ctrl.PayloadLength, ctrl.Padding = uint64(len(content)), capsule.Reforzado
plaintext = append(bytes.Clone(content), make([]byte, 256-len(content))...)
}
payload, _, err := testkit.Encrypt(plaintext, payloadID.Recipient())
if err != nil {
t.Fatal(err)
}
draft, err := capsule.EncodeControl(ctrl, c.format)
if err != nil {
t.Fatal(err)
}
sealedDraft, _, err := testkit.Encrypt(draft, rec)
if err != nil {
t.Fatal(err)
}
prelude := capsule.Prelude{Format: c.format, PublicHeaderLen: uint32(len(p.Header)), SealedControlLen: uint32(len(sealedDraft))}.Bytes()
ctrl.HeaderBinding = capsule.HeaderBinding(prelude, p.Header)
copy(ctrl.PayloadIdentity[:], raw)
control, err := capsule.EncodeControl(ctrl, c.format)
if err != nil {
t.Fatal(err)
}
sealed, _, err := testkit.Encrypt(control, rec)
if err != nil || len(sealed) != len(sealedDraft) {
t.Fatalf("sealing: %v", err)
}
if !bytes.Equal(prelude[:], p.Prelude) {
t.Fatalf("%s: PRELUDE %x differs from the official %x", c.fixture, prelude, p.Prelude)
}
forged := testkit.Join(prelude[:], p.Header, sealed, payload)
if got, err := open(t, forged, g.openOptions(t)); err != nil || string(got) != "forged" {
t.Fatalf("%s: forged capsule: %q, %v", c.fixture, got, err)
}
}
// In format 2 the control fixes the length and the padding of the
// plaintext, not the original L: whoever can seal another control, as
// anyone can for time_only once the round is published, declares another
// L with the same P, here L + 1 over a padding byte that is zero, and the
// capsule opens to another content (spec §30.1, §55.1).
x, err := testkit.LoadFixture(fixtureDir, "format2_time_only_extensions")
if err != nil {
t.Fatal(err)
}
longer, err := x.WithControl(func(m map[uint64]any) { m[6] = payloadLength(x.PayloadLength + 1) })
if err != nil {
t.Fatal(err)
}
xf := loadFixture(t, "format2_time_only_extensions")
if got, err := open(t, longer.DKC, xf.openOptions(t)); err != nil || !bytes.Equal(got, append(bytes.Clone(xf.plaintext), 0)) {
t.Fatalf("control with L + 1: %q, %v", got, err)
}
// PUBLIC_HEADER is bound from step 15 only: other extension data passes
// steps 1 to 8 and fails header_binding.
e := loadFixture(t, "time_only_extensions")
ep, err := testkit.Split(e.dkc)
if err != nil {
t.Fatal(err)
}
header := bytes.Replace(ep.Header, []byte("public label"), []byte("Public label"), 1)
if bytes.Equal(header, ep.Header) {
t.Fatal("the header data was not found")
}
edited := testkit.Join(ep.Prelude, header, ep.Sealed, ep.Payload)
if step, err := inspectStep(t, edited, nil); err != nil {
t.Fatalf("steps 1 to 8 rejected the edited header data at step %d: %v", step, err)
}
step, _, err := openStep(t, edited, capsule.OpenOptions{Now: testkit.Fixed(e.unlock(t))})
expectStep(t, "PUBLIC_HEADER data edited", step, err, datekeys.ErrHeaderBinding, 15)
// The data of .dkk extensions is bound to nothing: other data opens the
// capsule all the same (spec §72: advisory for its holder only).
f := loadFixture(t, "time_and_key_portable")
k := loadAccessKey(t, "time_and_key_portable_extension")
k.Noncritical = []extension.Extension{mustExt(t, "org.example.delivery", []byte("anything else"))}
var dkk bytes.Buffer
if err := accesskey.Encode(&dkk, k); err != nil {
t.Fatal(err)
}
back, err := accesskey.Decode(&dkk)
if err != nil {
t.Fatal(err)
}
o := f.openOptions(t)
o.AccessKey = back
if got, err := open(t, f.dkc, o); err != nil || !bytes.Equal(got, f.plaintext) {
t.Fatalf(".dkk with other extension data: %v", err)
}
}

Powered by TurnKey Linux.