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