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) } }