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.
187 lines
5.7 KiB
187 lines
5.7 KiB
|
6 days ago
|
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
|
||
|
|
}
|