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.
118 lines
3.6 KiB
118 lines
3.6 KiB
|
2 weeks ago
|
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")
|
||
|
|
}
|
||
|
|
}
|