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

118 lines
3.6 KiB

package capsule_test
import (
"bytes"
"encoding/hex"
"io"
"testing"
"filippo.io/age"
"g.activething.com/go/DateKeys/agewrap"
"g.activething.com/go/DateKeys/internal/testkit"
"g.activething.com/go/DateKeys/profile"
)
// reducingIdentity models a reader whose decoder reduces coordinates modulo
// p: it reduces c0 of U before the strict tlock identity sees the stanza.
type reducingIdentity struct{ id *agewrap.TimeIdentity }
func (r reducingIdentity) Unwrap(stanzas []*age.Stanza) ([]byte, error) {
s := *stanzas[0]
s.Body = testkit.ReduceCoordinate(s.Body, testkit.CoordinateLen)
return r.id.Unwrap([]*age.Stanza{&s})
}
// Spec §12.2, §64: the point mutations of the exported corpus differ from a
// capsule that opens only in the encoding of one point. A reader that
// reduces coordinates modulo p opens the capsules with c0 + p in U and with
// x + p in the signature, which the reference rejects
// (TestExportedMutationCorpus), and every edited tlock body keeps a valid
// header MAC, so that only the rules of the body reject it.
func TestPointMutationsChangeOnlyTheEncoding(t *testing.T) {
var f testkit.MutationFile
if err := testkit.ReadJSON(mutationsFile, &f); err != nil {
t.Fatal(err)
}
cases := map[string]*testkit.MutationCase{}
for i := range f.Cases {
cases[f.Cases[i].Name] = &f.Cases[i]
}
input := func(name string) *testkit.MutationInput {
t.Helper()
c := cases[name]
if c == nil {
t.Fatalf("no case %q", name)
}
in, err := c.Input(fixtureDir)
if err != nil {
t.Fatal(err)
}
return in
}
// The frozen capsule of round XPlusPRound opens with the signature
// reduced modulo p, the published one.
in := input("release signature re-encoded with x + p")
reduced := testkit.ReduceCoordinate(in.Release.Signature, 0)
if !bytes.Equal(reduced, testkit.Release(testkit.XPlusPRound).Signature) {
t.Fatalf("x + p reduces to %x", reduced)
}
in.Release.Signature = reduced
if v, err := in.Open(); err != nil || v.Err != nil {
t.Fatalf("with the published signature: %v %v", err, v.Err)
}
// The tlock bodies: the header MAC of OUTER_TIME_AGE verifies with
// FK_TIME, and OUTER_TIME_AGE decrypts to the CONTROL_CBOR of the fixture.
e, err := testkit.NewMutationEnv(fixtureDir)
if err != nil {
t.Fatal(err)
}
fk, err := e.TimeOnly.TimeFileKey()
if err != nil {
t.Fatal(err)
}
control, err := hex.DecodeString(e.TimeOnly.ControlCBOR)
if err != nil {
t.Fatal(err)
}
sealed := func(in *testkit.MutationInput, id age.Identity) []byte {
t.Helper()
parts, err := testkit.Split(in.DKC)
if err != nil {
t.Fatal(err)
}
r, err := age.Decrypt(bytes.NewReader(parts.Sealed), id)
if err != nil {
t.Fatal(err)
}
out, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
return out
}
for _, name := range []string{
"tlock stanza U re-encoded with c0 + p",
"tlock stanza U is the point at infinity",
"tlock stanza U with the infinity flag and a payload",
"tlock stanza body of 127 bytes",
"tlock stanza body of 129 bytes",
"negated release signature and U re-encoded with c0 + p",
} {
if got := sealed(input(name), age.NewInjectedFileKeyIdentity(fk)); !bytes.Equal(got, control) {
t.Fatalf("%s: OUTER_TIME_AGE does not decrypt to the fixture's CONTROL_CBOR", name)
}
}
// With c0 reduced modulo p, U is the U of the fixture again.
in = input("tlock stanza U re-encoded with c0 + p")
id, err := agewrap.NewTimeIdentity(profile.Quicknet(), in.Release.Round, *in.Release)
if err != nil {
t.Fatal(err)
}
if got := sealed(in, reducingIdentity{id}); !bytes.Equal(got, control) {
t.Fatal("a reader that reduces c0 does not open OUTER_TIME_AGE")
}
}

Powered by TurnKey Linux.