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

187 lines
5.7 KiB

package cms_test
import (
"crypto"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"os"
"path/filepath"
"testing"
"time"
"g.activething.com/go/DateKeys/internal/cms"
"g.activething.com/go/DateKeys/internal/cms/cmstest"
"g.activething.com/go/DateKeys/internal/der"
)
// The fuzz targets of the reader. Each one is seeded with the signatures and
// the tokens of testdata/vectors/security_cms.json and with what cmstest
// builds, and checks that the reader never panics and fails only with the
// errors of its verdicts: ErrForm (F1, S2) or ErrAlgorithm (S1).
// FuzzParseSignature reads any bytes as the CMS signature of alg 2 (spec
// §29.10). What it accepts has a certificate for each SignerInfo, and its
// checks, and the token of each, run without a panic.
func FuzzParseSignature(f *testing.F) {
for _, b := range fuzzSeeds(f) {
f.Add(b)
}
f.Fuzz(func(t *testing.T, b []byte) {
sd, err := cms.ParseSignature(b)
if err != nil {
if sd != nil || !errors.Is(err, cms.ErrForm) && !errors.Is(err, cms.ErrAlgorithm) {
t.Fatalf("%v, with a result %v", err, sd != nil)
}
return
}
if der.Check(b) != nil || len(sd.Signers) == 0 {
t.Fatal("a signature that is not DER, or without a SignerInfo")
}
for _, s := range sd.Signers {
if s.Cert == nil || s.Cert.Hash != sha256.Sum256(s.Cert.Raw) {
t.Fatal("a SignerInfo without its certificate")
}
switch s.Check(msg) {
case cms.Valid, cms.Invalid, cms.NotVerifiable:
default:
t.Fatal("a result outside the three")
}
s.Cert.Holder()
s.Cert.IssuerName()
if s.Token != nil {
checkToken(t, s.Token, s.Signature)
}
}
})
}
// FuzzParseToken reads any bytes as an RFC 3161 token (spec §29.11).
func FuzzParseToken(f *testing.F) {
for _, b := range fuzzSeeds(f) {
f.Add(b)
}
f.Fuzz(func(t *testing.T, b []byte) { checkToken(t, b, []byte("seal subject")) })
}
// maxAccuracy is the largest precision of a token: 2^31 - 1 seconds, 999
// milliseconds and 999 microseconds.
const maxAccuracy = (1<<31-1)*time.Second + 999*time.Millisecond + 999*time.Microsecond
func checkToken(t *testing.T, b, subject []byte) {
t.Helper()
tok, err := cms.ParseToken(b)
if err != nil {
if tok != nil || !errors.Is(err, cms.ErrForm) && !errors.Is(err, cms.ErrAlgorithm) {
t.Fatalf("%v, with a result %v", err, tok != nil)
}
return
}
if tok.TSA == nil || tok.GenTime.IsZero() || tok.Accuracy < 0 || tok.Accuracy > maxAccuracy {
t.Fatalf("a token of %+v", tok)
}
tok.Check(subject)
tok.ImprintIsSHA256()
tok.TSA.Holder()
}
// FuzzParseCert reads any bytes as a certificate with the profile of spec
// §29.10. What it accepts names its holder and its issuer without a panic,
// and has a valid period that it contains.
func FuzzParseCert(f *testing.F) {
for _, b := range fuzzSeeds(f) {
if sd, err := cms.ParseSignature(b); err == nil {
for _, c := range sd.Certs {
f.Add(c.Raw)
}
}
if tok, err := cms.ParseToken(b); err == nil {
f.Add(tok.TSA.Raw)
}
}
for _, spec := range []cmstest.CertSpec{
{CN: "Ana López"},
{Subject: cmstest.Name(cn(cmstest.BMPText("Ana")), given(cmstest.Printable("Ana")), surname(cmstest.Teletex("Lopez")), org(cmstest.IA5("Banco")))},
{NoVersion: true},
{NotAfter: cmstest.GeneralizedTime("20501231235959Z"), UniqueIDs: [][]byte{cmstest.TLV(0x81, []byte{0, 1})}},
{Extensions: [][]byte{cmstest.ExtSKI([]byte{1}), cmstest.ExtSKI([]byte{1})}},
} {
f.Add(cert(spec))
}
f.Fuzz(func(t *testing.T, b []byte) {
c, err := cms.ParseCert(b)
if err != nil {
return
}
if c.Hash != sha256.Sum256(b) || c.NotBefore.IsZero() || c.NotAfter.IsZero() {
t.Fatalf("a certificate of %+v", c)
}
if !c.NotAfter.Before(c.NotBefore) && (!c.ValidAt(c.NotBefore) || !c.ValidAt(c.NotAfter)) {
t.Fatal("a period that does not contain its ends")
}
c.Holder()
c.IssuerName()
})
}
// fuzzSeeds returns the signatures and the tokens of security_cms.json, and
// some that cmstest builds.
func fuzzSeeds(f *testing.F) [][]byte {
raw, err := os.ReadFile(filepath.Join("..", "..", "testdata", "vectors", "security_cms.json"))
if err != nil {
f.Fatal(err)
}
var file struct {
Cases []struct {
Area string `json:"security_cbor"`
} `json:"cases"`
}
if err := json.Unmarshal(raw, &file); err != nil {
f.Fatal(err)
}
var out [][]byte
for _, c := range file.Cases {
area, err := hex.DecodeString(c.Area)
if err != nil {
f.Fatal(err)
}
out = append(out, contentInfos(area)...)
}
tok := func(sig []byte) []byte {
return cmstest.Token(sig, now, cmstest.TokenOptions{Accuracy: 1500 * time.Millisecond, After: [][]byte{cmstest.Bool(true), cmstest.Int(7)}}, tsa)
}
return append(out,
cmstest.Signature(msg, cmstest.Options{Token: tok, OCSP: cmstest.Seq(cmstest.Int(0))}, ana, luis),
cmstest.Signature(msg, cmstest.Options{PSS: true, SKI: true, Hash: crypto.SHA384}, luis),
cmstest.Signature(msg, cmstest.Options{SigCertV1: true, ESSHashAlg: cmstest.HashAlg(crypto.SHA512), ExtraAttrs: [][]byte{cmstest.BigArcAttr()}}, ana),
cmstest.Token([]byte("seal subject"), now, cmstest.TokenOptions{SigCertV2: true, TSATwice: true, CRL: cmstest.Seq(cmstest.Int(1))}, tsa),
)
}
// contentInfos returns the outermost runs of bytes of b that are one DER
// SEQUENCE of more than 127 bytes: the signatures and the tokens of an area.
func contentInfos(b []byte) [][]byte {
var out [][]byte
for i := 0; i+4 < len(b); i++ {
if b[i] != 0x30 || b[i+1] < 0x81 || b[i+1] > 0x83 {
continue
}
n := int(b[i+1] & 0x7f)
if i+2+n > len(b) {
continue
}
l := 0
for _, c := range b[i+2 : i+2+n] {
l = l<<8 | int(c)
}
end := i + 2 + n + l
if end > len(b) || der.Check(b[i:end]) != nil {
continue
}
out = append(out, b[i:end])
i = end - 1
}
return out
}

Powered by TurnKey Linux.