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.
106 lines
3.5 KiB
106 lines
3.5 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 anyone builds a capsule with the same PRELUDE, the same
|
|
// PUBLIC_HEADER and another plaintext, which passes all 18 steps
|
|
// (spec §36.1).
|
|
g := loadFixture(t, "time_only")
|
|
p, _ := parts(t, "time_only")
|
|
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)
|
|
}
|
|
payload, _, err := testkit.Encrypt([]byte("forged"), payloadID.Recipient())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
draft, err := capsule.EncodeControl(&capsule.Control{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sealedDraft, _, err := testkit.Encrypt(draft, rec)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
prelude := capsule.Prelude{PublicHeaderLen: uint32(len(p.Header)), SealedControlLen: uint32(len(sealedDraft))}.Bytes()
|
|
ctrl := &capsule.Control{HeaderBinding: capsule.HeaderBinding(prelude, p.Header)}
|
|
copy(ctrl.PayloadIdentity[:], raw)
|
|
control, err := capsule.EncodeControl(ctrl)
|
|
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("PRELUDE %x differs from the official %x", 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("forged capsule: %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)
|
|
}
|
|
}
|