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

128 lines
3.7 KiB

package capsule_test
import (
"bytes"
"context"
"encoding/hex"
"errors"
"io"
"os"
"path/filepath"
"testing"
datekeys "g.activething.com/go/DateKeys"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/internal/testkit"
)
func seedFixtures(f *testing.F, part func(testkit.Parts) []byte) {
for _, name := range fixtureNames {
b, err := os.ReadFile(filepath.Join(fixtureDir, name+".dkc"))
if err != nil {
f.Fatal(err)
}
p, err := testkit.Split(b)
if err != nil {
f.Fatal(err)
}
f.Add(part(p))
}
}
// whole keeps the first 512 bytes of the payload: the pre-unlock checks read
// only its age header, and small inputs keep the fuzzer fast.
func whole(p testkit.Parts) []byte {
payload := p.Payload
if len(payload) > 512 {
payload = payload[:512]
}
return testkit.Join(p.Prelude, p.Header, p.Sealed, payload)
}
func FuzzParsePrelude(f *testing.F) {
seedFixtures(f, func(p testkit.Parts) []byte { return p.Prelude })
f.Fuzz(func(t *testing.T, b []byte) {
p, err := capsule.ParsePrelude(b)
if err != nil {
if datekeys.Code(err) == "" {
t.Fatalf("error without a normative code: %v", err)
}
return
}
got := p.Bytes()
if !bytes.Equal(got[:], b[:capsule.PreludeSize]) {
t.Fatal("accepted a prelude that does not re-encode to its input")
}
})
}
func FuzzDecodeHeader(f *testing.F) {
seedFixtures(f, func(p testkit.Parts) []byte { return p.Header })
f.Fuzz(func(t *testing.T, b []byte) {
h, err := capsule.DecodeHeader(b)
if err != nil {
if datekeys.Code(err) == "" {
t.Fatalf("error without a normative code: %v", err)
}
return
}
re, err := capsule.EncodeHeader(h)
if err != nil || !bytes.Equal(re, b) {
t.Fatal("accepted a PUBLIC_HEADER that does not re-encode to its input")
}
})
}
func FuzzDecodeControl(f *testing.F) {
for _, name := range fixtureNames {
fx := loadFixture(f, name)
b, _ := hexDecode(fx.ControlCBOR)
f.Add(b)
}
f.Fuzz(func(t *testing.T, b []byte) {
c, err := capsule.DecodeControl(b)
if err != nil {
if datekeys.Code(err) == "" {
t.Fatalf("error without a normative code: %v", err)
}
return
}
re, err := capsule.EncodeControl(c)
if err != nil || !bytes.Equal(re, b) {
t.Fatal("accepted a CONTROL_CBOR that does not re-encode to its input")
}
})
}
// FuzzInspect feeds whole capsules to the pre-unlock validation, and to Open
// with a source that never has the release: a capsule that Inspect rejects
// must not cause a request, and nothing may pass the release step. The
// cryptographic steps after it are exercised by the mutation corpus; keeping
// them out of this target keeps it fast.
func FuzzInspect(f *testing.F) {
seedFixtures(f, whole)
reg := testkit.Registry()
far := testkit.Fixed(testkit.Genesis().AddDate(5, 0, 0))
f.Fuzz(func(t *testing.T, b []byte) {
_, err := capsule.Inspect(bytes.NewReader(b), capsule.InspectOptions{Registry: reg})
if err != nil && datekeys.Code(err) == "" {
t.Fatalf("error without a normative code: %v", err)
}
src := testkit.NewSource()
o := capsule.OpenOptions{Registry: reg, Source: src, Now: far}
_, openErr := capsule.Open(context.Background(), io.Discard, bytes.NewReader(b), o)
switch {
case openErr == nil:
t.Fatal("opened without a release")
case datekeys.Code(openErr) == "":
t.Fatalf("error without a normative code: %v", openErr)
case err != nil && src.Calls != 0:
t.Fatal("a capsule rejected by Inspect caused a release request")
case err == nil && !errors.Is(openErr, datekeys.ErrReleaseUnavailable) && !errors.Is(openErr, datekeys.ErrAccessRequired):
t.Fatalf("a capsule accepted by Inspect failed before the release step: %v", openErr)
}
})
}
func hexDecode(s string) ([]byte, error) { return hex.DecodeString(s) }

Powered by TurnKey Linux.