Tests of the CMS reader: certificates field by field, every check, fuzzing

The builder of the tests, cmstest:

- NewCert writes a certificate from the DER of its tbsCertificate, field by
  field: names of any string type with any bytes (UTF8String,
  PrintableString with an underscore or an at sign, IA5String,
  TeletexString, BMPString of odd length or with a surrogate,
  VisibleString, NumericString), an attribute twice or none, no version,
  times with a fraction, an extension twice, a compressed EC key, an even
  modulus, and any signature. A Signer made so serves Signature and Token.
- Options for the version of a SignerInfo, the hashAlgorithm and the
  certHash of an ESSCertIDv2, a signatureAlgorithm other than the one of
  the key, a certificate twice, two content-type attributes, an attribute
  with an arc of 2^31, a SignerInfo twice, BER, signerInfos out of order,
  two signature-time-stamp attributes, and edits of the SignedData and of
  each SignerInfo.
- Token options for any accuracy, a genTime of free text, ordering FALSE,
  a field after the last, an imprint of any length, no message-digest, a
  CRL in crls and the certificate of the authority twice.
- Edits of the DER after signing: Edit, Retag, Withdraw (a SignerInfo
  removed), WithoutTimeStamp (a CAdES-T removed), Merge (a co-signature)
  and Indefinite.

The tests of internal/cms and internal/der fail for each check of cms.go,
cert.go, verify.go and der.go. A mutation run, which replaces each leaf of
each condition by false and by true, one at a time, kills every mutant
that is not equivalent to the code it mutates.

FuzzParseSignature, FuzzParseToken, FuzzParseCert and FuzzDERCheck, seeded
with security_cms.json and with what cmstest builds: no panic, what Check
accepts Split reads, and the parsers fail only with ErrForm or
ErrAlgorithm.

capsule: the case of a seal outside the validity of the certificate gave
an invalid seal; it now tests a certificate that expired before a valid
seal (out of validity) apart from an authority that was not valid at its
time (invalid seal). SIGNERS out of order, empty, too long, with 31 bytes
or with a hash twice are F1 beside a CMS signature that is valid for the
AUTHOR_MESSAGE of those SIGNERS, and the names of certificates show as
spec v0.12 §29.7 says.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.12
dev 6 days ago
parent b06ab4ffa2
commit 4ce4d59c8d

@ -1,9 +1,12 @@
package capsule_test
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/sha256"
"encoding/hex"
"slices"
"strings"
"testing"
@ -110,17 +113,31 @@ func TestEvaluateCMS(t *testing.T) {
t.Errorf("a foreign signer: %+v %q", f, f.Lines())
}
// F5, and the result of the first required signer (spec §29.10, steps 1
// to 7): the certificate of a signer that expired before the time of a
// valid seal is out of validity; a seal whose authority was not valid at
// its time is an invalid seal.
expired := cmstest.NewECDSA("Ana caducada", elliptic.P256(), certFrom, signedAt.AddDate(0, -1, 0))
small := cmstest.NewRSA("Clave corta", 1024, certFrom, certTo)
for name, tc := range map[string]struct {
area []byte
want capsule.Verdict
area []byte
want capsule.Verdict
result string
}{
"a required signer is absent": {cmsArea(t, c, []cmstest.Signer{ana, luis}, []cmstest.Signer{ana}, tsa, signedAt, nil), capsule.VerdictSignedIncomplete},
"no seal": {cmsArea(t, c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, cmstest.Signer{}, signedAt, nil), capsule.VerdictSignedIncomplete},
"a seal outside the validity of the certificate": {cmsArea(t, c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, tsa, certFrom.AddDate(-1, 0, 0), nil), capsule.VerdictSignedIncomplete},
"a key 3 beside it": {cmsArea(t, c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, tsa, signedAt, mustSeal(t, 1, []byte{1})), capsule.VerdictSignedIncomplete},
"a required signer is absent": {cmsArea(t, c, []cmstest.Signer{luis}, []cmstest.Signer{ana}, tsa, signedAt, nil), capsule.VerdictSignedIncomplete, "absent"},
"no seal": {cmsArea(t, c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, cmstest.Signer{}, signedAt, nil), capsule.VerdictSignedIncomplete, "without seal"},
"a certificate out of validity at the time of a valid seal": {cmsArea(t, c, []cmstest.Signer{expired}, []cmstest.Signer{expired}, tsa, signedAt, nil), capsule.VerdictSignedIncomplete, "out of validity"},
"a seal whose authority was not valid at its time": {cmsArea(t, c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, tsa, certFrom.AddDate(-1, 0, 0), nil), capsule.VerdictSignedIncomplete, "invalid seal"},
"a key outside the table": {cmsArea(t, c, []cmstest.Signer{small}, []cmstest.Signer{small}, tsa, signedAt, nil), capsule.VerdictSignedIncomplete, "not verifiable"},
"a key 3 beside it": {cmsArea(t, c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, tsa, signedAt, mustSeal(t, capsule.SealTypeTest, []byte{1})), capsule.VerdictSignedIncomplete, "valid"},
} {
if got := capsule.EvaluateSecurityIn(tc.area, c).Signature; got != tc.want {
t.Errorf("%s: %s, want %s", name, got, tc.want)
v := capsule.EvaluateSecurityIn(tc.area, c)
if v.Signature != tc.want || v.Detail == nil || v.Detail.Signers[0].Result != tc.result {
t.Errorf("%s: %+v, want %s and %q", name, v, tc.want, tc.result)
continue
}
if lines := v.Lines(); lines[0] != capsule.VerdictSignedIncomplete.Text() {
t.Errorf("%s: lines %q", name, lines)
}
}
@ -135,17 +152,36 @@ func TestEvaluateCMS(t *testing.T) {
t.Errorf("without a context: %s", got)
}
// F1: SIGNERS out of order or empty, or a CMS that is not one.
one, two := sha256Sum([]byte("a")), sha256Sum([]byte("b"))
if one[0] > two[0] {
one, two = two, one
// F1: SIGNERS out of order, empty, too long, with an element of 31 bytes
// or one twice, each beside a CMS signature that is valid for the
// AUTHOR_MESSAGE of those very SIGNERS: the rule decides, not the CMS.
a, l := sha256Sum(ana.Cert.Raw), sha256Sum(luis.Cert.Raw)
if bytes.Compare(a[:], l[:]) > 0 {
a, l = l, a
}
bstr := func(h []byte) []byte { return append([]byte{0x58, byte(len(h))}, h...) }
var seventeen []byte
for range 17 {
seventeen = append(seventeen, bstr(a[:])...)
}
unsorted := append(append([]byte{0x82, 0x58, 0x20}, two[:]...), append([]byte{0x58, 0x20}, one[:]...)...)
for name, signers := range map[string][]byte{"SIGNERS out of order": unsorted, "an empty SIGNERS": {0x80}} {
content, _ := capsule.EncodeAuthorSignature(capsule.AlgCMS, signers, []byte{0x30, 0x00})
for name, signers := range map[string][]byte{
"SIGNERS out of order": append(append([]byte{0x82}, bstr(l[:])...), bstr(a[:])...),
"an empty SIGNERS": {0x80},
"SIGNERS of 17 entries": append([]byte{0x91}, seventeen...),
"SIGNERS with 31 bytes": append([]byte{0x81}, bstr(a[:31])...),
"SIGNERS with one entry twice": append(append([]byte{0x82}, bstr(a[:])...), bstr(a[:])...),
"SIGNERS of indefinite length": append(append([]byte{0x9f}, bstr(a[:])...), 0xff),
"SIGNERS with a byte after them": append(append([]byte{0x81}, bstr(a[:])...), 0x00),
} {
msg := capsule.AuthorMessage(c.ControlCommit, c.HeadDigest, capsule.SignersDigest(capsule.AlgCMS, signers))
tok := func(sig []byte) []byte { return cmstest.Token(sig, signedAt, cmstest.TokenOptions{}, tsa) }
content, err := capsule.EncodeAuthorSignature(capsule.AlgCMS, signers, cmstest.Signature(msg, cmstest.Options{Token: tok}, ana, luis))
if err != nil {
t.Fatal(err)
}
area, _ := capsule.EncodeSecurityWith(content, nil)
if got := capsule.EvaluateSecurityIn(area, c).Signature; got != capsule.VerdictSignatureUnchecked {
t.Errorf("%s: %s", name, got)
if v := capsule.EvaluateSecurityIn(area, c); v.Signature != capsule.VerdictSignatureUnchecked || v.Detail != nil {
t.Errorf("%s: %+v", name, v)
}
}
content, _ := capsule.EncodeAuthorSignature(capsule.AlgCMS, mustSigners(t, ana), []byte("not DER"))
@ -155,6 +191,50 @@ func TestEvaluateCMS(t *testing.T) {
}
}
// Spec v0.12 §29.7: a name of a certificate shows when it meets the rules of
// the declared author, has at most 64 code points and no two spaces in a
// row; otherwise the SHA-256 of the certificate shows, or that of the name of
// the issuer for the issuer.
func TestCertificateNamesShown(t *testing.T) {
tsa := cmstest.NewECDSA("TSA de prueba", elliptic.P256(), certFrom, certTo)
c := testContext()
sixtyFour := strings.Repeat("ñ", 64)
for name, tc := range map[string]struct {
cn string
shown bool
}{
"a name": {"Ana López", true},
"64 code points": {sixtyFour, true},
"65 code points": {sixtyFour + "a", false},
"two spaces in a row": {"Ana López", false},
"an escape": {"Ana\x1b[31mLópez", false},
"U+202E": {"Ana \xe2\x80\xaezepóL", false},
"a byte order mark": {"\xef\xbb\xbfAna López", false},
"a space at the start": {" Ana López", false},
"a line feed": {"Ana\nLópez", false},
"a zero width space": {"Ana\xe2\x80\x8bLópez", false},
"a tag that spells a text": {"Ana\xf3\xa0\x81\x81", false},
"an emoji with its selector": {"Ana \xe2\x9d\xa4\xef\xb8\x8f", true},
"a combining mark, 64 points": {strings.Repeat("n\xcc\x83", 32), true},
} {
s := cmstest.NewCert(cmstest.CertSpec{CN: tc.cn, Issuer: cmstest.Name(cmstest.ATV(cmstest.OIDCommonName, cmstest.UTF8(tc.cn)))}, ecdsaKey())
v := capsule.EvaluateSecurityIn(cmsArea(t, c, []cmstest.Signer{s}, []cmstest.Signer{s}, tsa, signedAt, nil), c)
if v.Signature != capsule.VerdictSignedComplete {
t.Fatalf("%s: %+v", name, v)
}
want, issuer := tc.cn, tc.cn
if !tc.shown {
h, hi := sha256Sum(s.Cert.Raw), sha256Sum(s.Cert.RawIssuer)
want, issuer = hex.EncodeToString(h[:]), hex.EncodeToString(hi[:])
}
if got := v.Detail.Signers[0]; got.Holder != want || got.Issuer != issuer {
t.Errorf("%s: holder %q and issuer %q, want %q and %q", name, got.Holder, got.Issuer, want, issuer)
}
}
}
func ecdsaKey() *ecdsa.PrivateKey { return cmstest.ECKey(elliptic.P256()) }
func mustSigners(t *testing.T, s cmstest.Signer) []byte {
t.Helper()
b, err := capsule.EncodeSigners([][32]byte{sha256Sum(s.Cert.Raw)})

@ -0,0 +1,249 @@
package cms_test
import (
"bytes"
"crypto/sha256"
"encoding/asn1"
"testing"
"time"
"g.activething.com/go/DateKeys/internal/cms"
"g.activething.com/go/DateKeys/internal/cms/cmstest"
)
// The profile of the certificate (spec §29.10): what a certificate made field
// by field gives, and each rule that a certificate can break.
func TestCertProfile(t *testing.T) {
spec := cmstest.CertSpec{CN: "Ana López", From: time.Date(2021, 2, 3, 4, 5, 6, 0, time.UTC), To: time.Date(2051, 2, 3, 4, 5, 6, 0, time.UTC), Serial: cmstest.Int(0x1234)}
s := cmstest.NewCert(spec, ecKey)
c, err := cms.ParseCert(s.Cert.Raw)
if err != nil {
t.Fatal(err)
}
if c.Hash != sha256.Sum256(s.Cert.Raw) || !bytes.Equal(c.Raw, s.Cert.Raw) || !bytes.Equal(c.Serial, []byte{0x12, 0x34}) ||
!bytes.Equal(c.RawIssuer, s.Cert.RawIssuer) || !bytes.Equal(c.RawSubject, s.Cert.RawIssuer) || !bytes.Equal(c.SKI, s.Cert.SubjectKeyId) ||
!c.NotBefore.Equal(spec.From) || !c.NotAfter.Equal(spec.To) || !bytes.Equal(c.SPKI, cmstest.SPKI(ecKey.Public())) {
t.Errorf("the fields: %+v", c)
}
// The validity is inclusive (RFC 5280 4.1.2.5).
for at, want := range map[time.Time]bool{
spec.From: true, spec.From.Add(-time.Nanosecond): false, spec.To: true, spec.To.Add(time.Nanosecond): false, now: true,
} {
if c.ValidAt(at) != want {
t.Errorf("valid at %v: %v", at, !want)
}
}
raw := s.Cert.Raw
tbs := func(i int) []int { return []int{0, i} }
time1 := cmstest.UTCTime("200101000000Z")
for name, b := range map[string][]byte{
"a SET": cmstest.Edit(raw, cmstest.Retag(0x31)),
"a fourth element": cmstest.Edit(raw, cmstest.Append(cmstest.Null())),
"tbsCertificate as a SET": cmstest.Edit(raw, cmstest.Retag(0x31), 0),
"signatureAlgorithm as a SET": cmstest.Edit(raw, cmstest.Retag(0x31), 1),
"the signature as an OCTET STRING": cmstest.Edit(raw, cmstest.Retag(0x04), 2),
"no version: a certificate of v1": cert(cmstest.CertSpec{NoVersion: true}),
"version 1 with extensions": cert(cmstest.CertSpec{NoVersion: true, After: [][]byte{cmstest.Null()}}),
"the version written as 1": cert(cmstest.CertSpec{Version: cmstest.TLV(0xa0, cmstest.Int(0))}),
"the version written as 2": cert(cmstest.CertSpec{Version: cmstest.TLV(0xa0, cmstest.Int(1))}),
"the version as [1]": cert(cmstest.CertSpec{Version: cmstest.TLV(0xa1, cmstest.Int(2))}),
"the version of two INTEGERs": cert(cmstest.CertSpec{Version: cmstest.TLV(0xa0, cmstest.Int(2), cmstest.Int(2))}),
"a tbsCertificate without its SPKI": cmstest.Edit(raw, func(b []byte) []byte { return cmstest.Seq(cmstest.Children(b)[:6]...) }, 0),
"the serial as an OCTET STRING": cert(cmstest.CertSpec{Serial: cmstest.Octets([]byte{1})}),
"the signature of tbs as a SET": cmstest.Edit(raw, cmstest.Retag(0x31), tbs(2)...),
"the issuer as a SET": cmstest.Edit(raw, cmstest.Retag(0x31), tbs(3)...),
"the validity as a SET": cmstest.Edit(raw, cmstest.Retag(0x31), tbs(4)...),
"the subject as a SET": cmstest.Edit(raw, cmstest.Retag(0x31), tbs(5)...),
"the SPKI as a SET": cmstest.Edit(raw, cmstest.Retag(0x31), tbs(6)...),
"a validity of three times": cmstest.Edit(raw, cmstest.Append(time1), tbs(4)...),
"a validity of one time": cmstest.Edit(raw, cmstest.Replace(cmstest.Seq(time1)), tbs(4)...),
"notBefore with a fraction": cert(cmstest.CertSpec{NotBefore: cmstest.GeneralizedTime("20200101000000.5Z")}),
"notAfter with a fraction": cert(cmstest.CertSpec{NotAfter: cmstest.GeneralizedTime("20391231235959.5Z")}),
"notBefore an INTEGER": cert(cmstest.CertSpec{NotBefore: cmstest.Int(1)}),
"notAfter that is not a time": cert(cmstest.CertSpec{NotAfter: cmstest.UTCTime("not a time!!Z")}),
"subjectUniqueID before issuerUniqueID": cert(cmstest.CertSpec{UniqueIDs: [][]byte{cmstest.TLV(0x82, []byte{0, 2}), cmstest.TLV(0x81, []byte{0, 1})}}),
"the extensions as [4]": cert(cmstest.CertSpec{NoExtensions: true, After: [][]byte{cmstest.TLV(0xa4, cmstest.Seq(cmstest.ExtKeyUsage()))}}),
"an element after the extensions": cert(cmstest.CertSpec{After: [][]byte{cmstest.Null()}}),
"[3] of two SEQUENCEs": cert(cmstest.CertSpec{NoExtensions: true, After: [][]byte{cmstest.TLV(0xa3, cmstest.Seq(cmstest.ExtKeyUsage()), cmstest.Seq(cmstest.ExtKeyUsage()))}}),
"[3] holding a SET": cert(cmstest.CertSpec{NoExtensions: true, After: [][]byte{cmstest.TLV(0xa3, cmstest.Set(0x31, cmstest.ExtKeyUsage()))}}),
"an empty [3]": cert(cmstest.CertSpec{NoExtensions: true, After: [][]byte{cmstest.TLV(0xa3)}}),
"no Extension": cert(cmstest.CertSpec{Extensions: [][]byte{}}),
"an Extension as a SET": cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.TLV(0x31, cmstest.OID(oidX), cmstest.Octets(nil))}}),
"an Extension that is an INTEGER": cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Int(1)}}),
"an Extension of only its type": cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Seq(cmstest.OID(oidX))}}),
"an empty Extension": cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Seq()}}),
"an Extension of four elements": cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Seq(cmstest.OID(oidX), cmstest.Bool(true), cmstest.Octets(nil), cmstest.Octets(nil))}}),
"an Extension whose type is an INTEGER": cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Seq(cmstest.Int(1), cmstest.Octets(nil))}}),
"an Extension whose value is not OCTETS": cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Seq(cmstest.OID(oidX), cmstest.BitString(nil))}}),
"an Extension critical by an INTEGER": cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Seq(cmstest.OID(oidX), cmstest.Int(1), cmstest.Octets(nil))}}),
"an extension twice": cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.ExtKeyUsage(), cmstest.ExtKeyUsage()}}),
"subjectKeyIdentifier twice": cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.ExtSKI([]byte{1}), cmstest.ExtSKI([]byte{2})}}),
"a subjectKeyIdentifier not in DER": cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Extension(cmstest.OIDSKI, false, []byte{0x04, 0x01, 0x07, 0x00})}}),
"a subjectKeyIdentifier an INTEGER": cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Extension(cmstest.OIDSKI, false, cmstest.Int(7))}}),
"an empty subjectKeyIdentifier": cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.ExtSKI(nil)}}),
"a subjectKeyIdentifier of no bytes": cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Extension(cmstest.OIDSKI, false, nil)}}),
} {
if _, err := cms.ParseCert(b); err == nil {
t.Errorf("%s: accepted", name)
}
}
// A name that breaks the profile, as the subject beside a good issuer,
// and as the issuer beside a good subject.
good := cmstest.Name(cmstest.ATV(cmstest.OIDCommonName, cmstest.UTF8("A")))
for name, n := range map[string][]byte{
"an RDN as a SEQUENCE": cmstest.NameOf(cmstest.Seq(cmstest.ATV(cmstest.OIDCommonName, cmstest.UTF8("A")))),
"an empty RDN": cmstest.NameOf(cmstest.RDN()),
"an RDN that is an INTEGER": cmstest.NameOf(cmstest.Int(1)),
"an AttributeTypeAndValue as a SET": cmstest.NameOf(cmstest.RDN(cmstest.TLV(0x31, cmstest.OID(cmstest.OIDCommonName), cmstest.UTF8("A")))),
"an AttributeTypeAndValue of three": cmstest.NameOf(cmstest.RDN(cmstest.Seq(cmstest.OID(cmstest.OIDCommonName), cmstest.UTF8("A"), cmstest.Null()))),
"an AttributeTypeAndValue of one": cmstest.NameOf(cmstest.RDN(cmstest.Seq(cmstest.OID(cmstest.OIDCommonName)))),
"an AttributeTypeAndValue primitive": cmstest.NameOf(cmstest.RDN(cmstest.Int(3))),
"an attribute type that is an INTEGER": cmstest.NameOf(cmstest.RDN(cmstest.Seq(cmstest.Int(3), cmstest.UTF8("A")))),
} {
if _, err := cms.ParseCert(cert(cmstest.CertSpec{Subject: n, Issuer: good})); err == nil {
t.Errorf("the subject, %s: accepted", name)
}
if _, err := cms.ParseCert(cert(cmstest.CertSpec{Subject: good, Issuer: n})); err == nil {
t.Errorf("the issuer, %s: accepted", name)
}
}
// What the profile accepts: both unique identifiers, no extensions in a
// certificate of version 3, a critical subjectKeyIdentifier, an unknown
// extension, a GeneralizedTime before 2050, and any signature.
for name, spec := range map[string]cmstest.CertSpec{
"both unique identifiers": {UniqueIDs: [][]byte{cmstest.TLV(0x81, []byte{0, 1}), cmstest.TLV(0x82, []byte{0, 2})}},
"only subjectUniqueID": {UniqueIDs: [][]byte{cmstest.TLV(0x82, []byte{0, 2})}, NoExtensions: true},
"no extensions": {NoExtensions: true},
"a critical subjectKeyIdentifier": {Extensions: [][]byte{cmstest.Extension(cmstest.OIDSKI, true, cmstest.Octets([]byte{9}))}},
"an unknown extension": {Extensions: [][]byte{cmstest.Extension(oidX, false, nil), cmstest.ExtSKI([]byte{9})}},
"a GeneralizedTime in 2030": {NotAfter: cmstest.GeneralizedTime("20300101000000Z")},
"a signature that is no one's": {Signature: cmstest.BitString([]byte("not a signature"))},
"another signature algorithm": {SigAlg: cmstest.AlgID(asn1.ObjectIdentifier{1, 2, 3}, cmstest.Int(7))},
"an empty name": {Subject: cmstest.NameOf(), Issuer: cmstest.NameOf()},
} {
c, err := cms.ParseCert(cert(spec))
if err != nil {
t.Errorf("%s: %v", name, err)
continue
}
if name == "a critical subjectKeyIdentifier" && !bytes.Equal(c.SKI, []byte{9}) || name == "no extensions" && c.SKI != nil {
t.Errorf("%s: SKI %x", name, c.SKI)
}
}
}
var oidX = asn1.ObjectIdentifier{1, 2, 3, 4, 5}
// A NumericString is DER, and a certificate whose name holds one, as the INN
// of a Russian certificate, meets the profile: its value is no text, and the
// signature verifies (spec §29.10).
func TestNumericStringInName(t *testing.T) {
inn := cmstest.ATV(asn1.ObjectIdentifier{1, 2, 643, 3, 131, 1, 1}, cmstest.Numeric("123456789012"))
s := cmstest.NewCert(cmstest.CertSpec{Subject: cmstest.Name(cn(cmstest.UTF8("Ivan Petrov")), inn)}, ecKey)
if si := signerOf(t, "a NumericString", cmstest.Signature(msg, cmstest.Options{}, s)); si.Check(msg) != cms.Valid || si.Cert.Holder() != "Ivan Petrov" {
t.Errorf("a NumericString beside the commonName: %v %q", si.Check(msg), si.Cert.Holder())
}
if c := subject(cn(cmstest.Numeric("12345"))); c.Holder() != "" {
t.Errorf("a commonName in a NumericString: %q", c.Holder())
}
}
// cert returns the DER of the certificate of spec, of the key ecKey.
func cert(spec cmstest.CertSpec) []byte { return cmstest.NewCert(spec, ecKey).Cert.Raw }
// subject is a certificate of ecKey whose subject has the attributes given,
// each in a RelativeDistinguishedName of its own.
func subject(atvs ...[]byte) *cms.Cert {
return parsed(cmstest.CertSpec{Subject: cmstest.Name(atvs...)})
}
func parsed(spec cmstest.CertSpec) *cms.Cert {
c, err := cms.ParseCert(cert(spec))
if err != nil {
panic(err)
}
return c
}
func cn(v []byte) []byte { return cmstest.ATV(cmstest.OIDCommonName, v) }
func given(v []byte) []byte { return cmstest.ATV(cmstest.OIDGivenName, v) }
func surname(v []byte) []byte { return cmstest.ATV(cmstest.OIDSurname, v) }
func org(v []byte) []byte { return cmstest.ATV(cmstest.OIDOrganization, v) }
// The text of a name (spec §29.10): only of the five string types, in their
// alphabets, nothing removed, never from an attribute that appears twice; and
// the holder (spec §29.7): givenName and surname before commonName.
func TestCertNames(t *testing.T) {
utf8 := cmstest.UTF8
for name, tc := range map[string]struct {
c *cms.Cert
want string
}{
"a UTF8String": {subject(cn(utf8("Ana López"))), "Ana López"},
"a UTF8String that is not UTF-8": {subject(cn(utf8("Ana \xff"))), ""},
"a UTF8String with a BOM, kept": {subject(cn(utf8("\xef\xbb\xbfAna"))), "\xef\xbb\xbfAna"},
"a PrintableString of its whole alphabet": {subject(cn(cmstest.Printable("Ana O'Neil (1+2), x-y./:=? Z9"))), "Ana O'Neil (1+2), x-y./:=? Z9"},
"a PrintableString with an underscore": {subject(cn(cmstest.Printable("Ana_Lopez"))), ""},
"a PrintableString with an at sign": {subject(cn(cmstest.Printable("ana@example.com"))), ""},
"a PrintableString with a tilde": {subject(cn(cmstest.Printable("Ana~"))), ""},
"a PrintableString with an ampersand": {subject(cn(cmstest.Printable("A&B"))), ""},
"a PrintableString with an asterisk": {subject(cn(cmstest.Printable("A*B"))), ""},
"a PrintableString with a byte of 0xe9": {subject(cn(cmstest.Printable("L\xe9a"))), ""},
"an IA5String": {subject(cn(cmstest.IA5("ana@example.com"))), "ana@example.com"},
"an IA5String with a byte of 0x80": {subject(cn(cmstest.IA5("Ana\x80"))), ""},
"a TeletexString in ASCII": {subject(cn(cmstest.Teletex("Ana Lopez"))), "Ana Lopez"},
"a TeletexString with a byte of 0xe9": {subject(cn(cmstest.Teletex("L\xe9a"))), ""},
"a BMPString": {subject(cn(cmstest.BMPText("Ana López"))), "Ana López"},
"a BMPString above the surrogates": {subject(cn(cmstest.BMPText("Ana ¥"))), "Ana ¥"},
"a BMPString of odd length": {subject(cn(cmstest.BMP([]byte{0, 'A', 0}))), ""},
"a BMPString with a surrogate pair": {subject(cn(cmstest.BMPText("Ana \U0001F600"))), ""},
"a BMPString with a low surrogate": {subject(cn(cmstest.BMP([]byte{0, 'A', 0xdc, 0}))), ""},
"a BMPString with a high surrogate": {subject(cn(cmstest.BMP([]byte{0xd8, 0, 0, 'A'}))), ""},
"a VisibleString": {subject(cn(cmstest.Visible("Ana"))), ""},
"a UniversalString": {subject(cn(cmstest.TLV(0x1c, []byte{0, 0, 0, 'A'}))), ""},
"a NumericString": {subject(cn(cmstest.Numeric("12345"))), ""},
"two commonNames": {subject(cn(utf8("Ana")), cn(utf8("Luis"))), ""},
"two commonNames in one RDN": {parsed(cmstest.CertSpec{Subject: cmstest.NameOf(cmstest.RDN(cn(utf8("Ana")), cn(utf8("Luis"))))}), ""},
"a commonName in an RDN of two attributes": {parsed(cmstest.CertSpec{Subject: cmstest.NameOf(cmstest.RDN(org(utf8("Banco")), cn(utf8("Ana"))))}), "Ana"},
"no commonName": {subject(org(utf8("Banco"))), ""},
"givenName, surname and the NIF in the CN": {subject(cn(utf8("ESPAÑOL ESPAÑOL JUAN - 12345678Z")), given(utf8("JUAN")), surname(utf8("ESPAÑOL ESPAÑOL"))), "JUAN ESPAÑOL ESPAÑOL"},
"givenName and surname without a CN": {subject(given(utf8("Ana")), surname(utf8("López"))), "Ana López"},
"only a givenName": {subject(cn(utf8("Ana López")), given(utf8("Ana"))), "Ana López"},
"only a surname": {subject(cn(utf8("Ana López")), surname(utf8("López"))), "Ana López"},
"an empty givenName": {subject(cn(utf8("Ana López")), given(utf8("")), surname(utf8("López"))), "Ana López"},
"an empty surname": {subject(cn(utf8("Ana López")), given(utf8("Ana")), surname(utf8(""))), "Ana López"},
"a givenName that is not UTF-8": {subject(cn(utf8("Ana López")), given(utf8("An\xff")), surname(utf8("López"))), "Ana López"},
"a surname that is not UTF-8": {subject(cn(utf8("Ana López")), given(utf8("Ana")), surname(utf8("L\xff"))), "Ana López"},
"a givenName that is no text": {subject(cn(utf8("Ana López")), given(cmstest.Visible("Ana")), surname(utf8("López"))), "Ana López"},
"two givenNames": {subject(cn(utf8("Ana López")), given(utf8("Ana")), given(utf8("Eva")), surname(utf8("López"))), "Ana López"},
"a givenName with an escape, not the CN": {subject(cn(utf8("Ana López")), given(utf8("Ana\x1b")), surname(utf8("López"))), "Ana\x1b López"},
"a commonName with an escape, kept as text": {subject(cn(utf8("Ana\x1b[31m"))), "Ana\x1b[31m"},
} {
if got := tc.c.Holder(); got != tc.want {
t.Errorf("%s: holder %q, want %q", name, got, tc.want)
}
}
// The issuer: its commonName, or its organizationName without one; ""
// with neither, the caller showing then the hash of the name.
issuer := func(atvs ...[]byte) *cms.Cert {
return parsed(cmstest.CertSpec{Issuer: cmstest.Name(atvs...)})
}
for name, tc := range map[string]struct {
c *cms.Cert
want string
}{
"a commonName": {issuer(org(utf8("Banco")), cn(utf8("CA de prueba"))), "CA de prueba"},
"an organizationName without a CN": {issuer(org(utf8("Banco S.A."))), "Banco S.A."},
"a commonName with an escape, kept": {issuer(org(utf8("Banco")), cn(utf8("CA\x1b"))), "CA\x1b"},
"a commonName not UTF-8, then the O": {issuer(org(utf8("Banco")), cn(utf8("C\xff"))), "Banco"},
"two commonNames, then the O": {issuer(org(utf8("Banco")), cn(utf8("A")), cn(utf8("B"))), "Banco"},
"an organizationName that is not UTF-8": {issuer(org(utf8("B\xff"))), ""},
"neither": {issuer(cmstest.ATV(cmstest.OIDCountry, cmstest.Printable("ES"))), ""},
"an organizationName that is not text": {issuer(org(cmstest.Visible("Banco"))), ""},
} {
if got := tc.c.IssuerName(); got != tc.want {
t.Errorf("issuer, %s: %q, want %q", name, got, tc.want)
}
}
}

@ -0,0 +1,287 @@
package cmstest
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/asn1"
"math/big"
"time"
"unicode/utf16"
)
// CertSpec describes a certificate that NewCert writes field by field, from
// the DER of its tbsCertificate, to write what crypto/x509 does not let one
// write: names of any string type with any bytes, an attribute twice or none,
// no version, times with a fraction, an extension twice, a compressed key or
// an even modulus, and any signature, which DateKeys does not check (spec
// §29.10). Each field holds the DER of its element as it goes in the
// certificate; nil takes the default of a certificate of version 3 of the key.
type CertSpec struct {
// CN names the subject, CN = CN in a UTF8String, when Subject is nil.
CN string
// From and To are the validity, 2020-01-01 to 2040-01-01 by default,
// written as RFC 5280 does when NotBefore and NotAfter are nil.
From, To time.Time
// Version is the field [0] EXPLICIT of the version, INTEGER 2 (version
// 3) by default; NoVersion leaves it out, as a version 1 certificate.
Version []byte
NoVersion bool
// Serial is the serialNumber, a random positive INTEGER by default.
Serial []byte
// SigAlg is the AlgorithmIdentifier of the signature, in tbsCertificate
// and after it: that of the key with SHA-256 by default.
SigAlg []byte
// Issuer and Subject are the names; the issuer is the subject by default,
// as in a self-signed certificate.
Issuer, Subject []byte
// NotBefore and NotAfter are the times of validity as written.
NotBefore, NotAfter []byte
// SPKI is the SubjectPublicKeyInfo, that of the key by default.
SPKI []byte
// UniqueIDs are written after the SPKI: [1] issuerUniqueID and [2]
// subjectUniqueID.
UniqueIDs [][]byte
// SKI is the keyIdentifier of the extension subjectKeyIdentifier of the
// default extensions, and what a sid by subjectKeyIdentifier names: 20
// bytes of the hash of the SPKI by default.
SKI []byte
// Extensions are the Extension elements of [3]: subjectKeyIdentifier and
// keyUsage by default. An empty, non-nil slice writes [3] with an empty
// SEQUENCE, and NoExtensions writes no [3].
Extensions [][]byte
NoExtensions bool
// After are elements written at the end of tbsCertificate.
After [][]byte
// Signature is the BIT STRING of the signature as written; by default
// the key signs tbsCertificate.
Signature []byte
}
// NewCert returns a signer of key whose certificate is the one that spec
// describes.
func NewCert(spec CertSpec, key crypto.Signer) Signer {
from, to := spec.From, spec.To
if from.IsZero() {
from = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
}
if to.IsZero() {
to = time.Date(2040, 1, 1, 0, 0, 0, 0, time.UTC)
}
serial := spec.Serial
if serial == nil {
n, err := rand.Int(rand.Reader, big.NewInt(1<<62))
if err != nil {
panic(err)
}
serial = BigInt(n.Add(n, big.NewInt(1)))
}
sigAlg := spec.SigAlg
if sigAlg == nil {
sigAlg = AlgID(OIDECDSA256)
if _, ok := key.(*rsa.PrivateKey); ok {
sigAlg = AlgID(OIDSHA256RSA, Null())
}
}
subject := spec.Subject
if subject == nil {
cn := spec.CN
if cn == "" {
cn = "DateKeys test"
}
subject = Name(ATV(OIDCommonName, UTF8(cn)))
}
issuer := spec.Issuer
if issuer == nil {
issuer = subject
}
notBefore, notAfter := spec.NotBefore, spec.NotAfter
if notBefore == nil {
notBefore = CertTimeOf(from)
}
if notAfter == nil {
notAfter = CertTimeOf(to)
}
spki := spec.SPKI
if spki == nil {
spki = SPKI(key.Public())
}
ski := spec.SKI
if ski == nil {
h := sha256.Sum256(spki)
ski = h[:20]
}
var tbs [][]byte
if !spec.NoVersion {
version := spec.Version
if version == nil {
version = tlv(0xa0, Int(2))
}
tbs = append(tbs, version)
}
tbs = append(tbs, serial, sigAlg, issuer, Seq(notBefore, notAfter), subject, spki)
tbs = append(tbs, spec.UniqueIDs...)
if !spec.NoExtensions {
exts := spec.Extensions
if exts == nil {
exts = [][]byte{ExtSKI(ski), ExtKeyUsage()}
}
tbs = append(tbs, tlv(0xa3, Seq(exts...)))
}
tbsDER := Seq(append(tbs, spec.After...)...)
signature := spec.Signature
if signature == nil {
_, sig := sign(key, Options{Hash: crypto.SHA256}, sum(crypto.SHA256, tbsDER))
signature = BitString(sig)
}
c := &Cert{Raw: Seq(tbsDER, sigAlg, signature), RawIssuer: issuer, Serial: serial}
if spec.Extensions == nil && !spec.NoExtensions || spec.SKI != nil {
c.SubjectKeyId = ski
}
return Signer{Cert: c, Key: key}
}
// The attribute types of a name.
var (
OIDCommonName = asn1.ObjectIdentifier{2, 5, 4, 3}
OIDSurname = asn1.ObjectIdentifier{2, 5, 4, 4}
OIDSerialNumber = asn1.ObjectIdentifier{2, 5, 4, 5}
OIDCountry = asn1.ObjectIdentifier{2, 5, 4, 6}
OIDOrganization = asn1.ObjectIdentifier{2, 5, 4, 10}
OIDOrgUnit = asn1.ObjectIdentifier{2, 5, 4, 11}
OIDGivenName = asn1.ObjectIdentifier{2, 5, 4, 42}
OIDSKI = asn1.ObjectIdentifier{2, 5, 29, 14}
OIDKeyUsage = asn1.ObjectIdentifier{2, 5, 29, 15}
)
// Name is a Name with one RelativeDistinguishedName for each attribute, in
// the order given.
func Name(atvs ...[]byte) []byte {
rdns := make([][]byte, len(atvs))
for i, a := range atvs {
rdns[i] = tlv(0x31, a)
}
return Seq(rdns...)
}
// RDN is a RelativeDistinguishedName of several attributes, in the order
// given, for NameOf.
func RDN(atvs ...[]byte) []byte { return tlv(0x31, atvs...) }
// NameOf is a Name of the RelativeDistinguishedName elements given.
func NameOf(rdns ...[]byte) []byte { return Seq(rdns...) }
// ATV is an AttributeTypeAndValue.
func ATV(oid asn1.ObjectIdentifier, value []byte) []byte { return Seq(OID(oid), value) }
// The string types of a name, with the bytes as given: those that break
// their type too.
// UTF8 is a UTF8String.
func UTF8(s string) []byte { return tlv(0x0c, []byte(s)) }
// Numeric is a NumericString.
func Numeric(s string) []byte { return tlv(0x12, []byte(s)) }
// Printable is a PrintableString.
func Printable(s string) []byte { return tlv(0x13, []byte(s)) }
// Teletex is a TeletexString.
func Teletex(s string) []byte { return tlv(0x14, []byte(s)) }
// IA5 is an IA5String.
func IA5(s string) []byte { return tlv(0x16, []byte(s)) }
// Visible is a VisibleString.
func Visible(s string) []byte { return tlv(0x1a, []byte(s)) }
// BMP is a BMPString with the bytes as given: of odd length, or with a
// surrogate.
func BMP(b []byte) []byte { return tlv(0x1e, b) }
// BMPText is the BMPString of s in UTF-16BE; a code point outside the BMP
// becomes a surrogate pair.
func BMPText(s string) []byte {
var b []byte
for _, u := range utf16.Encode([]rune(s)) {
b = append(b, byte(u>>8), byte(u))
}
return BMP(b)
}
// Extension is an Extension, with critical only when it is true, as DER
// writes it.
func Extension(oid asn1.ObjectIdentifier, critical bool, value []byte) []byte {
f := [][]byte{OID(oid)}
if critical {
f = append(f, Bool(true))
}
return Seq(append(f, Octets(value))...)
}
// ExtSKI is the extension subjectKeyIdentifier with the keyIdentifier id.
func ExtSKI(id []byte) []byte { return Extension(OIDSKI, false, Octets(id)) }
// ExtKeyUsage is the extension keyUsage with digitalSignature.
func ExtKeyUsage() []byte { return Extension(OIDKeyUsage, true, []byte{0x03, 0x02, 0x07, 0x80}) }
// SPKI is the SubjectPublicKeyInfo of a public key, as crypto/x509 writes it:
// an EC point uncompressed, and RSA with NULL parameters.
func SPKI(pub crypto.PublicKey) []byte {
b, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
panic(err)
}
return b
}
// CurveOID returns the named curve of curve: P-256, P-384 or P-521.
func CurveOID(curve elliptic.Curve) asn1.ObjectIdentifier {
switch curve {
case elliptic.P384():
return OIDP384
case elliptic.P521():
return OIDP521
}
return OIDP256
}
// Uncompressed returns the point of pub, uncompressed.
func Uncompressed(pub *ecdsa.PublicKey) []byte {
b, err := pub.Bytes()
if err != nil {
panic(err)
}
return b
}
// Compressed returns the point of pub, compressed (SEC 1 2.3.3).
func Compressed(pub *ecdsa.PublicKey) []byte {
u := Uncompressed(pub)
n := (len(u) - 1) / 2
return append([]byte{2 | u[len(u)-1]&1}, u[1:1+n]...)
}
// SPKIEC is the SubjectPublicKeyInfo of id-ecPublicKey with the parameters
// and the point given.
func SPKIEC(params, point []byte) []byte {
return Seq(AlgID(OIDECPublicKey, params), BitString(point))
}
// SPKICompressed is the SubjectPublicKeyInfo of pub with its point
// compressed, which the table of spec §29.10 does not have.
func SPKICompressed(pub *ecdsa.PublicKey) []byte {
return SPKIEC(OID(CurveOID(pub.Curve)), Compressed(pub))
}
// SPKIRSA is the SubjectPublicKeyInfo of rsaEncryption with NULL parameters
// and the modulus and exponent given: an even modulus, or a size outside the
// table.
func SPKIRSA(n, e *big.Int) []byte {
return Seq(AlgID(OIDRSA, Null()), BitString(Seq(BigInt(n), BigInt(e))))
}

@ -1,8 +1,11 @@
// Package cmstest builds the CMS signatures and the RFC 3161 tokens that the
// tests of internal/cms and of capsule read: certificates of test keys, a
// detached signature of a message with its signedAttrs and, optionally, a
// time-stamp token of its signature. It is the encoder that a signing
// application has; nothing outside tests uses it.
// tests of internal/cms and of capsule read, and that the generator of the
// test vectors writes: certificates of test keys, made by crypto/x509 or field
// by field (cert.go), a detached signature of a message with its signedAttrs
// and, optionally, a time-stamp token of its signature, with options to write
// what the profiles of spec §29.10 and §29.11 reject, or what verifies to
// something else, and edits of the DER of the result (edit.go). It is the
// encoder that a signing application has; nothing outside tests uses it.
package cmstest
import (
@ -14,37 +17,61 @@ import (
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"fmt"
"math/big"
"slices"
"strings"
"time"
)
// Cert is a certificate as a signature or a token names it.
type Cert struct {
// Raw is the DER of the certificate.
Raw []byte
// RawIssuer is the DER of its issuer, Serial the DER of its serialNumber,
// an INTEGER, and SubjectKeyId the keyIdentifier of its extension
// subjectKeyIdentifier, nil without it: what a sid names.
RawIssuer, Serial, SubjectKeyId []byte
}
// Signer is a certificate with its private key.
type Signer struct {
Cert *x509.Certificate
Cert *Cert
Key crypto.Signer
}
// NewRSA returns a signer with an RSA key of bits bits, valid in
// [notBefore, notAfter], named cn.
func NewRSA(cn string, bits int, notBefore, notAfter time.Time) Signer {
// RSAKey returns a new RSA key of bits bits.
func RSAKey(bits int) *rsa.PrivateKey {
k, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
panic(err)
}
return newSigner(cn, k, notBefore, notAfter)
return k
}
// NewECDSA returns a signer with an ECDSA key on curve.
func NewECDSA(cn string, curve elliptic.Curve, notBefore, notAfter time.Time) Signer {
// ECKey returns a new ECDSA key on curve.
func ECKey(curve elliptic.Curve) *ecdsa.PrivateKey {
k, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
panic(err)
}
return newSigner(cn, k, notBefore, notAfter)
return k
}
// NewRSA returns a signer with an RSA key of bits bits, valid in
// [notBefore, notAfter], named cn, with a certificate that crypto/x509 makes.
func NewRSA(cn string, bits int, notBefore, notAfter time.Time) Signer {
return newSigner(cn, RSAKey(bits), notBefore, notAfter)
}
// NewECDSA returns a signer with an ECDSA key on curve, with a certificate
// that crypto/x509 makes.
func NewECDSA(cn string, curve elliptic.Curve, notBefore, notAfter time.Time) Signer {
return newSigner(cn, ECKey(curve), notBefore, notAfter)
}
func newSigner(cn string, k crypto.Signer, notBefore, notAfter time.Time) Signer {
@ -64,7 +91,7 @@ func newSigner(cn string, k crypto.Signer, notBefore, notAfter time.Time) Signer
if err != nil {
panic(err)
}
return Signer{Cert: c, Key: k}
return Signer{Cert: &Cert{Raw: c.Raw, RawIssuer: c.RawIssuer, Serial: mustMarshal(c.SerialNumber), SubjectKeyId: c.SubjectKeyId}, Key: k}
}
// The DER building blocks.
@ -88,19 +115,11 @@ func tlv(tag byte, content ...[]byte) []byte {
// TLV builds an element of any tag from the encodings of its content.
func TLV(tag byte, content ...[]byte) []byte { return tlv(tag, content...) }
// TokenRaw returns a token that tsa signs over the given TSTInfo, as it is:
// for tests that need a TSTInfo that Token would not write.
func TokenRaw(tstInfo []byte, tsa Signer) []byte {
return build(tstInfo, Options{Hash: crypto.SHA256}, true, []Signer{tsa})
}
// GeneralizedTime builds a GeneralizedTime with the text s.
func GeneralizedTime(s string) []byte { return tlv(0x18, []byte(s)) }
// Seq is a SEQUENCE.
func Seq(content ...[]byte) []byte { return tlv(0x30, content...) }
// Set is a SET OF, in DER order.
// Set is a SET OF with the identifier octet tag, in DER order. A SET OF in
// another order is TLV(tag, elems...).
func Set(tag byte, elems ...[]byte) []byte {
e := slices.Clone(elems)
slices.SortFunc(e, bytes.Compare)
@ -108,113 +127,262 @@ func Set(tag byte, elems ...[]byte) []byte {
}
// OID is an OBJECT IDENTIFIER.
func OID(oid asn1.ObjectIdentifier) []byte {
b, err := asn1.Marshal(oid)
if err != nil {
panic(err)
}
return b
}
func OID(oid asn1.ObjectIdentifier) []byte { return mustMarshal(oid) }
// OIDBytes is an OBJECT IDENTIFIER with the content as given: an arc of any
// size, or a content that is not one.
func OIDBytes(content []byte) []byte { return tlv(0x06, content) }
// Octets is an OCTET STRING.
func Octets(b []byte) []byte { return tlv(0x04, b) }
// Int is an INTEGER.
func Int(n int64) []byte {
b, err := asn1.Marshal(n)
func Int(n int64) []byte { return mustMarshal(n) }
// BigInt is an INTEGER of any size.
func BigInt(n *big.Int) []byte { return mustMarshal(n) }
// IntBytes is an INTEGER with the content as given, minimal or not.
func IntBytes(content []byte) []byte { return tlv(0x02, content) }
// Null is a NULL.
func Null() []byte { return []byte{0x05, 0x00} }
// Bool is a BOOLEAN, as DER writes it.
func Bool(v bool) []byte {
if v {
return []byte{0x01, 0x01, 0xff}
}
return []byte{0x01, 0x01, 0x00}
}
// BitString is a BIT STRING of whole bytes.
func BitString(b []byte) []byte { return tlv(0x03, append([]byte{0}, b...)) }
// UTCTime builds a UTCTime with the text s.
func UTCTime(s string) []byte { return tlv(0x17, []byte(s)) }
// GeneralizedTime builds a GeneralizedTime with the text s.
func GeneralizedTime(s string) []byte { return tlv(0x18, []byte(s)) }
// GeneralizedTimeOf builds the GeneralizedTime of t in UTC as DER writes it:
// the fraction of a second only when there is one, without trailing zeros.
func GeneralizedTimeOf(t time.Time) []byte {
t = t.UTC()
s := t.Format("20060102150405")
if ns := t.Nanosecond(); ns != 0 {
s += "." + strings.TrimRight(fmt.Sprintf("%09d", ns), "0")
}
return GeneralizedTime(s + "Z")
}
// CertTimeOf builds a time of validity as RFC 5280 4.1.2.5 writes it: UTCTime
// until 2049 and GeneralizedTime from 2050, without a fraction.
func CertTimeOf(t time.Time) []byte {
t = t.UTC().Truncate(time.Second)
if t.Year() < 2050 {
return UTCTime(t.Format("060102150405") + "Z")
}
return GeneralizedTimeOf(t)
}
// AlgID is an AlgorithmIdentifier.
func AlgID(oid asn1.ObjectIdentifier, params ...[]byte) []byte {
return Seq(append([][]byte{OID(oid)}, params...)...)
}
func mustMarshal(v any) []byte {
b, err := asn1.Marshal(v)
if err != nil {
panic(err)
}
return b
}
// The object identifiers.
var (
OIDData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1}
OIDSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2}
oidContent = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3}
oidDigest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4}
OIDSigCertV2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 47}
oidSigCertV1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 12}
OIDTimeStamp = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 14}
oidTSTInfo = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 1, 4}
oidOCSP = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 16, 2}
oidSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1}
oidSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2}
oidSHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3}
oidRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
oidPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10}
oidMGF1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 8}
oidECDSA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2}
oidECDSA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3}
oidECDSA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4}
OIDData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1}
OIDSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2}
OIDContentType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3}
OIDMessageDigest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4}
OIDSigningTime = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 5}
OIDSigCertV1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 12}
OIDSigCertV2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 47}
OIDTimeStamp = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 14}
OIDTSTInfo = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 1, 4}
OIDOCSP = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 16, 2}
OIDSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26}
OIDSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1}
OIDSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2}
OIDSHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3}
OIDRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
OIDMGF1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 8}
OIDPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10}
OIDSHA256RSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11}
OIDSHA384RSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12}
OIDSHA512RSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13}
OIDECDSASHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1}
OIDECDSA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2}
OIDECDSA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3}
OIDECDSA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4}
OIDECPublicKey = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
OIDP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7}
OIDP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34}
OIDP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35}
OIDBrainpoolP256 = asn1.ObjectIdentifier{1, 3, 36, 3, 3, 2, 8, 1, 1, 7}
)
func hashAlg(h crypto.Hash) []byte {
// HashAlg is the AlgorithmIdentifier of h, without parameters: SHA-1,
// SHA-256, SHA-384 or SHA-512.
func HashAlg(h crypto.Hash) []byte { return AlgID(hashOID(h)) }
func hashOID(h crypto.Hash) asn1.ObjectIdentifier {
switch h {
case crypto.SHA1:
return OIDSHA1
case crypto.SHA384:
return Seq(OID(oidSHA384))
return OIDSHA384
case crypto.SHA512:
return Seq(OID(oidSHA512))
return OIDSHA512
}
return Seq(OID(oidSHA256))
return OIDSHA256
}
func hashOID(h crypto.Hash) asn1.ObjectIdentifier {
func ecdsaOID(h crypto.Hash) asn1.ObjectIdentifier {
switch h {
case crypto.SHA1:
return OIDECDSASHA1
case crypto.SHA384:
return oidSHA384
return OIDECDSA384
case crypto.SHA512:
return oidSHA512
return OIDECDSA512
}
return oidSHA256
return OIDECDSA256
}
func sum(h crypto.Hash, b []byte) []byte {
x := h.New()
x.Write(b)
return x.Sum(nil)
switch h {
case crypto.SHA1:
s := sha1.Sum(b)
return s[:]
case crypto.SHA384:
s := sha512.Sum384(b)
return s[:]
case crypto.SHA512:
s := sha512.Sum512(b)
return s[:]
}
s := sha256.Sum256(b)
return s[:]
}
// hashNamed returns the hash that the AlgorithmIdentifier a names, SHA-256
// for one that this package does not write.
func hashNamed(a []byte) crypto.Hash {
for _, h := range []crypto.Hash{crypto.SHA1, crypto.SHA384, crypto.SHA512} {
if bytes.HasPrefix(a[2:], OID(hashOID(h))) {
return h
}
}
return crypto.SHA256
}
// Options changes what Build writes, to make signatures that the profile
// rejects or that verify to something else.
// Options changes what Signature and Token write, to make signatures that the
// profile rejects or that verify to something else. The zero value writes
// what AutoFirma writes.
type Options struct {
// Hash is the digest of the signature, SHA-256 by default.
// Hash is the digest of the signature, SHA-256 by default; SHA-1 writes
// ecdsa-with-SHA1 for an ECDSA key.
Hash crypto.Hash
// PSS signs with RSASSA-PSS instead of PKCS #1 v1.5.
PSS bool
// SKI names the signer by subjectKeyIdentifier instead of by issuer
// and serial.
// PSS signs with RSASSA-PSS instead of PKCS #1 v1.5, and PSSTrailer
// writes trailerField [3] 1 in its parameters, which is its default and
// DER does not write.
PSS, PSSTrailer bool
// SKI names the signer by subjectKeyIdentifier instead of by issuer and
// serial.
SKI bool
// Token, when not nil, is the time-stamp token of the signature that
// Build wants as an unsigned attribute: it receives the signature value.
Token func(signature []byte) []byte
// Version is the version of each SignerInfo; 0 writes 1 with
// issuerAndSerialNumber and 3 with subjectKeyIdentifier (RFC 5652 5.3).
Version int
// DigestAlg, when not nil, is written as the digestAlgorithm of each
// SignerInfo instead of that of Hash.
DigestAlg []byte
// SigAlg, when not nil, is written as the signatureAlgorithm instead of
// the one of the key; the key still signs with its own scheme.
SigAlg []byte
// CorruptSignature flips a bit of each signature value, after signing.
CorruptSignature bool
// Message is what the message-digest covers, when not the signed message.
Message []byte
// ContentType2 adds a second content-type attribute, the same as the
// first, and NoMessageDigest leaves the message-digest out.
ContentType2, NoMessageDigest bool
// SigCertV1 adds a signing-certificate attribute (RFC 2634) beside the
// v2. SigCertV2 writes signing-certificate-v2 in a token, which writes
// signing-certificate by default; NoSigCertV2 leaves it out of a
// signature.
SigCertV1, SigCertV2, NoSigCertV2 bool
// ESSHashAlg, when not nil, is written as the hashAlgorithm of the
// ESSCertIDv2, which DER leaves out for SHA-256, its default; certHash is
// the hash that it names. ESSCert, when not nil, is the certificate whose
// hash certHash gives, instead of that of the signer.
ESSHashAlg, ESSCert []byte
// ExtraAttrs are added to the signed attributes, as the DER of each.
ExtraAttrs [][]byte
// UnsortedAttrs writes the signed attributes in descending order: not a
// SET OF of DER. They are signed as written.
UnsortedAttrs bool
// Mutate edits the signedAttrs, as a list of the DER of each attribute,
// before they are signed.
Mutate func(attrs [][]byte) [][]byte
// Token2 puts two signature-time-stamp attributes, to break the profile.
Token2 bool
// OCSP adds this response in crls.
OCSP []byte
// OmitCert leaves the certificate of the signer out of certificates.
OmitCert bool
// PSSTrailer writes trailerField [3] 1 in the PSS parameters, which is
// its default and DER does not write it.
PSSTrailer bool
// Token, when not nil, is the time-stamp token of the signature that
// Signature puts as an unsigned attribute: it receives the signature
// value. Token2 puts it in one attribute with a second value, and
// TimeStamps2 adds a second signature-time-stamp attribute with a token
// of its own.
Token func(signature []byte) []byte
Token2, TimeStamps2 bool
// Junk adds an unsigned attribute of this many bytes, which decides
// nothing, to make a signature as large as a chain of certificates.
Junk int
// SigCertV1 adds a signing-certificate attribute beside the v2.
SigCertV1 bool
// ExtraAttrs are added to the signed attributes, as the DER of each.
ExtraAttrs [][]byte
// ExtraUnsigned are added to the unsigned attributes, as the DER of each.
ExtraUnsigned [][]byte
// OmitCert leaves the certificate of each signer out of certificates,
// and ExtraCerts adds these, as the DER of each: the certificate of a
// signer for a repetition, a certificate that breaks the profile, or
// another choice of CertificateChoices.
OmitCert bool
ExtraCerts [][]byte
// OCSP adds this response in crls, as an OtherRevocationInfoFormat of
// id-ri-ocsp-response, and CRLs adds these elements in crls as given.
OCSP []byte
CRLs [][]byte
// SignerInfoTwice writes each SignerInfo twice, Unsorted writes
// signerInfos in descending order, and BER writes the ContentInfo with
// an indefinite length.
SignerInfoTwice, Unsorted, BER bool
// EditSignerInfo edits the fields of each SignerInfo after it is signed,
// and EditSignedData the fields of the SignedData.
EditSignerInfo, EditSignedData func(fields [][]byte) [][]byte
}
func attr(oid asn1.ObjectIdentifier, values ...[]byte) []byte {
// Attr is an Attribute of the type with the values, in DER order.
func Attr(oid asn1.ObjectIdentifier, values ...[]byte) []byte {
return Seq(OID(oid), Set(0x31, values...))
}
// BigArcAttr is an attribute whose type has an arc of 2^31: an identifier
// that the profile does not name, and decides nothing (spec §29.10).
func BigArcAttr() []byte {
// 1.2.840.113549.1.9.2147483648: the last arc is 2^31.
return Seq(OIDBytes([]byte{0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x88, 0x80, 0x80, 0x80, 0x00}), Set(0x31, Null()))
}
// Signature returns the detached CMS signature of message by the signers, in
// DER, as AutoFirma writes it: one SignerInfo each, with content-type,
// message-digest and signing-certificate-v2 as signed attributes.
@ -222,7 +390,18 @@ func Signature(message []byte, opts Options, signers ...Signer) []byte {
return build(message, opts, false, signers)
}
func build(message []byte, o Options, token bool, signers []Signer) []byte {
// TokenRaw returns a token that tsa signs over the given TSTInfo, as it is:
// for tests that need a TSTInfo that Token would not write.
func TokenRaw(tstInfo []byte, tsa Signer) []byte {
return build(tstInfo, Options{Hash: crypto.SHA256}, true, []Signer{tsa})
}
// TokenRawWith is TokenRaw with the options of the SignedData of the token.
func TokenRawWith(tstInfo []byte, o Options, tsa Signer) []byte {
return build(tstInfo, o, true, []Signer{tsa})
}
func build(content []byte, o Options, token bool, signers []Signer) []byte {
if o.Hash == 0 {
o.Hash = crypto.SHA256
}
@ -231,136 +410,233 @@ func build(message []byte, o Options, token bool, signers []Signer) []byte {
if !o.OmitCert {
certs = append(certs, s.Cert.Raw)
}
infos = append(infos, signerInfo(message, o, token, s))
info := signerInfo(content, o, token, s)
infos = append(infos, info)
if o.SignerInfoTwice {
infos = append(infos, info)
}
}
var body []byte
body = append(body, Int(1)...)
body = append(body, Set(0x31, hashAlg(o.Hash))...)
body = append(body, encap(message, token)...)
certs = append(certs, o.ExtraCerts...)
fields := [][]byte{Int(1), Set(0x31, HashAlg(o.Hash)), encap(content, token)}
if len(certs) > 0 {
body = append(body, Set(0xa0, certs...)...)
fields = append(fields, Set(0xa0, certs...))
}
var crls [][]byte
if o.OCSP != nil {
body = append(body, Set(0xa1, tlv(0xa1, OID(oidOCSP), o.OCSP))...)
crls = append(crls, tlv(0xa1, OID(OIDOCSP), o.OCSP))
}
if crls = append(crls, o.CRLs...); len(crls) > 0 {
fields = append(fields, Set(0xa1, crls...))
}
body = append(body, Set(0x31, infos...)...)
return Seq(OID(OIDSignedData), tlv(0xa0, Seq(body)))
if o.Unsorted {
slices.SortFunc(infos, func(a, b []byte) int { return bytes.Compare(b, a) })
fields = append(fields, tlv(0x31, infos...))
} else {
fields = append(fields, Set(0x31, infos...))
}
if o.EditSignedData != nil {
fields = o.EditSignedData(fields)
}
ci := Seq(OID(OIDSignedData), tlv(0xa0, Seq(fields...)))
if o.BER {
return Indefinite(ci)
}
return ci
}
func encap(content []byte, token bool) []byte {
if !token {
return Seq(OID(OIDData))
}
return Seq(OID(oidTSTInfo), tlv(0xa0, Octets(content)))
return Seq(OID(OIDTSTInfo), tlv(0xa0, Octets(content)))
}
// essCertID returns the SigningCertificate (v1, SHA-1) or the
// SigningCertificateV2 of a certificate.
func essCertID(cert []byte, o Options, v2 bool) []byte {
if o.ESSCert != nil {
cert = o.ESSCert
}
if !v2 {
h := sha1.Sum(cert)
return Seq(Seq(Seq(Octets(h[:]))))
}
if o.ESSHashAlg == nil {
h := sha256.Sum256(cert)
return Seq(Seq(Seq(Octets(h[:]))))
}
return Seq(Seq(Seq(o.ESSHashAlg, Octets(sum(hashNamed(o.ESSHashAlg), cert)))))
}
func signerInfo(message []byte, o Options, token bool, s Signer) []byte {
var sid []byte
sid := Seq(s.Cert.RawIssuer, s.Cert.Serial)
if o.SKI {
sid = tlv(0x80, s.Cert.SubjectKeyId)
} else {
sid = Seq(s.Cert.RawIssuer, mustMarshal(s.Cert.SerialNumber))
}
contentType := OIDData
if token {
contentType = oidTSTInfo
contentType = OIDTSTInfo
}
md := message
if o.Message != nil {
md = o.Message
}
essHash := sha256.Sum256(s.Cert.Raw)
attrs := [][]byte{
attr(oidContent, OID(contentType)),
attr(oidDigest, Octets(sum(o.Hash, md))),
attrs := [][]byte{Attr(OIDContentType, OID(contentType))}
if o.ContentType2 {
attrs = append(attrs, attrs[0])
}
if token {
h := sha1.Sum(s.Cert.Raw)
attrs = append(attrs, attr(oidSigCertV1, Seq(Seq(Seq(Octets(h[:]))))))
} else {
attrs = append(attrs, attr(OIDSigCertV2, Seq(Seq(Seq(Octets(essHash[:]))))))
if !o.NoMessageDigest {
attrs = append(attrs, Attr(OIDMessageDigest, Octets(sum(o.Hash, md))))
}
switch {
case token && !o.SigCertV2:
attrs = append(attrs, Attr(OIDSigCertV1, essCertID(s.Cert.Raw, o, false)))
case !o.NoSigCertV2:
attrs = append(attrs, Attr(OIDSigCertV2, essCertID(s.Cert.Raw, o, true)))
}
if o.SigCertV1 {
h := sha1.Sum(s.Cert.Raw)
attrs = append(attrs, attr(oidSigCertV1, Seq(Seq(Seq(Octets(h[:]))))))
attrs = append(attrs, Attr(OIDSigCertV1, essCertID(s.Cert.Raw, Options{}, false)))
}
attrs = append(attrs, o.ExtraAttrs...)
if o.Mutate != nil {
attrs = o.Mutate(attrs)
}
signed := Set(0xa0, attrs...)
forSig := append([]byte{0x31}, signed[1:]...)
digest := sum(o.Hash, forSig)
var sigAlg, sig []byte
switch k := s.Key.(type) {
case *rsa.PrivateKey:
if o.PSS {
params := Seq(tlv(0xa0, hashAlg(o.Hash)), tlv(0xa1, Seq(OID(oidMGF1), hashAlg(o.Hash))), tlv(0xa2, Int(int64(o.Hash.Size()))))
if o.PSSTrailer {
params = Seq(tlv(0xa0, hashAlg(o.Hash)), tlv(0xa1, Seq(OID(oidMGF1), hashAlg(o.Hash))), tlv(0xa2, Int(int64(o.Hash.Size()))), tlv(0xa3, Int(1)))
}
sigAlg = Seq(OID(oidPSS), params)
sig, _ = rsa.SignPSS(rand.Reader, k, o.Hash, digest, &rsa.PSSOptions{SaltLength: o.Hash.Size(), Hash: o.Hash})
} else {
sigAlg = Seq(OID(oidRSA), []byte{5, 0})
sig, _ = rsa.SignPKCS1v15(rand.Reader, k, o.Hash, digest)
}
case *ecdsa.PrivateKey:
oid := oidECDSA256
switch o.Hash {
case crypto.SHA384:
oid = oidECDSA384
case crypto.SHA512:
oid = oidECDSA512
}
sigAlg = Seq(OID(oid))
sig, _ = ecdsa.SignASN1(rand.Reader, k, digest)
var signed []byte
if o.UnsortedAttrs {
attrs = slices.Clone(attrs)
slices.SortFunc(attrs, func(a, b []byte) int { return bytes.Compare(b, a) })
signed = tlv(0xa0, attrs...)
} else {
signed = Set(0xa0, attrs...)
}
// The signature covers the signedAttrs with the tag of a SET.
digest := sum(o.Hash, append([]byte{0x31}, signed[1:]...))
sigAlg, sig := sign(s.Key, o, digest)
if o.SigAlg != nil {
sigAlg = o.SigAlg
}
if o.CorruptSignature {
sig[len(sig)/2] ^= 1
}
version := int64(1)
if o.SKI {
version = 3
}
f := [][]byte{Int(version), sid, hashAlg(o.Hash), signed, sigAlg, Octets(sig)}
if o.Version != 0 {
version = int64(o.Version)
}
digestAlg := HashAlg(o.Hash)
if o.DigestAlg != nil {
digestAlg = o.DigestAlg
}
f := [][]byte{Int(version), sid, digestAlg, signed, sigAlg, Octets(sig)}
var unsigned [][]byte
if o.Junk > 0 {
unsigned = append(unsigned, attr(asn1.ObjectIdentifier{1, 2, 3, 4, 5}, Octets(make([]byte, o.Junk))))
unsigned = append(unsigned, Attr(asn1.ObjectIdentifier{1, 2, 3, 4, 5}, Octets(make([]byte, o.Junk))))
}
if o.Token != nil {
t := o.Token(sig)
v := attr(OIDTimeStamp, t)
if o.Token2 {
v = Seq(OID(OIDTimeStamp), Set(0x31, t, Seq(OID(OIDData))))
unsigned = append(unsigned, Seq(OID(OIDTimeStamp), Set(0x31, t, Seq(OID(OIDData)))))
} else {
unsigned = append(unsigned, Attr(OIDTimeStamp, t))
}
if o.TimeStamps2 {
unsigned = append(unsigned, Attr(OIDTimeStamp, o.Token(sig)))
}
unsigned = append(unsigned, v)
}
unsigned = append(unsigned, o.ExtraUnsigned...)
if len(unsigned) > 0 {
f = append(f, Set(0xa1, unsigned...))
}
if o.EditSignerInfo != nil {
f = o.EditSignerInfo(f)
}
return Seq(f...)
}
func mustMarshal(v any) []byte {
b, err := asn1.Marshal(v)
// sign signs digest with key and returns the signatureAlgorithm of the key
// and the signature value.
func sign(key crypto.Signer, o Options, digest []byte) (sigAlg, sig []byte) {
var err error
switch k := key.(type) {
case *rsa.PrivateKey:
if o.PSS {
params := [][]byte{tlv(0xa0, HashAlg(o.Hash)), tlv(0xa1, AlgID(OIDMGF1, HashAlg(o.Hash))), tlv(0xa2, Int(int64(o.Hash.Size())))}
if o.PSSTrailer {
params = append(params, tlv(0xa3, Int(1)))
}
sigAlg = AlgID(OIDPSS, Seq(params...))
sig, err = rsa.SignPSS(rand.Reader, k, o.Hash, digest, &rsa.PSSOptions{SaltLength: o.Hash.Size(), Hash: o.Hash})
} else {
sigAlg = AlgID(OIDRSA, Null())
sig, err = rsa.SignPKCS1v15(rand.Reader, k, o.Hash, digest)
}
case *ecdsa.PrivateKey:
sigAlg = AlgID(ecdsaOID(o.Hash))
sig, err = ecdsa.SignASN1(rand.Reader, k, digest)
default:
panic(fmt.Sprintf("cmstest: a key of type %T", key))
}
if err != nil {
panic(err)
}
return b
return sigAlg, sig
}
// TokenOptions changes what Token writes.
type TokenOptions struct {
Hash crypto.Hash // the hash of the messageImprint, SHA-256 by default
Accuracy time.Duration // whole seconds; zero for none
Version int // the version of the TSTInfo, 1 by default
// Imprint, when not nil, is written as the hashed message instead of
// the hash of the subject.
// Hash is the hash of the messageImprint, SHA-256 by default.
Hash crypto.Hash
// Accuracy is written as its seconds, millis and micros, each only when
// it is not zero; zero writes no accuracy. AccuracyRaw, when not nil, is
// the element of the accuracy as given: negative, not minimal, 0 or 1000.
Accuracy time.Duration
AccuracyRaw []byte
// Version is the version of the TSTInfo, 1 by default.
Version int
// Imprint, when not nil, is written as the hashed message instead of the
// hash of the subject, of any length.
Imprint []byte
// GenTimeRaw, when not nil, is written as genTime instead of the
// GeneralizedTime of the time given: free text, or another type.
GenTimeRaw []byte
// OrderingFalse writes ordering FALSE, its default, which DER does not
// write; After are elements written after the accuracy and the ordering:
// nonce, tsa and extensions, or anything after the last field.
OrderingFalse bool
After [][]byte
// SigCertV2 signs with signing-certificate-v2 instead of
// signing-certificate (ESSCertID).
SigCertV2 bool
// NoMessageDigest leaves the message-digest out of the token, CRL puts
// this element in its crls, and TSATwice writes the certificate of the
// authority twice in its certificates.
NoMessageDigest, TSATwice bool
CRL []byte
// CMS are the options of the SignedData of the token; its Hash is the
// digest of the signature of the authority, SHA-256 by default.
CMS Options
}
// Token returns the RFC 3161 time-stamp token that tsa issues over subject
// at genTime.
func Token(subject []byte, genTime time.Time, o TokenOptions, tsa Signer) []byte {
// AccuracyOf is the Accuracy element of d: its seconds, millis and micros,
// each only when it is not zero.
func AccuracyOf(d time.Duration) []byte {
var f [][]byte
if s := d / time.Second; s != 0 {
f = append(f, Int(int64(s)))
}
if ms := d % time.Second / time.Millisecond; ms != 0 {
f = append(f, tlv(0x80, Int(int64(ms))[2:]))
}
if us := d % time.Millisecond / time.Microsecond; us != 0 {
f = append(f, tlv(0x81, Int(int64(us))[2:]))
}
return Seq(f...)
}
// TSTInfo returns the TSTInfo that Token signs.
func TSTInfo(subject []byte, genTime time.Time, o TokenOptions) []byte {
if o.Hash == 0 {
o.Hash = crypto.SHA256
}
@ -371,19 +647,34 @@ func Token(subject []byte, genTime time.Time, o TokenOptions, tsa Signer) []byte
if o.Imprint != nil {
imprint = o.Imprint
}
gt, err := asn1.MarshalWithParams(genTime.UTC(), "generalized")
if err != nil {
panic(err)
gt := GeneralizedTimeOf(genTime)
if o.GenTimeRaw != nil {
gt = o.GenTimeRaw
}
info := [][]byte{Int(int64(o.Version)), OID(asn1.ObjectIdentifier{1, 2, 3, 4}), Seq(HashAlg(o.Hash), Octets(imprint)), Int(42), gt}
switch {
case o.AccuracyRaw != nil:
info = append(info, o.AccuracyRaw)
case o.Accuracy != 0:
info = append(info, AccuracyOf(o.Accuracy))
}
if o.OrderingFalse {
info = append(info, Bool(false))
}
return Seq(append(info, o.After...)...)
}
// Token returns the RFC 3161 time-stamp token that tsa issues over subject
// at genTime.
func Token(subject []byte, genTime time.Time, o TokenOptions, tsa Signer) []byte {
cms := o.CMS
cms.SigCertV2 = cms.SigCertV2 || o.SigCertV2
cms.NoMessageDigest = cms.NoMessageDigest || o.NoMessageDigest
if o.CRL != nil {
cms.CRLs = append(slices.Clone(cms.CRLs), o.CRL)
}
if o.TSATwice {
cms.ExtraCerts = append(slices.Clone(cms.ExtraCerts), tsa.Cert.Raw)
}
info := []byte{}
info = append(info, Int(int64(o.Version))...)
info = append(info, OID(asn1.ObjectIdentifier{1, 2, 3, 4})...)
info = append(info, Seq(hashAlg(o.Hash), Octets(imprint))...)
info = append(info, Int(42)...)
info = append(info, gt...)
if o.Accuracy != 0 {
info = append(info, Seq(Int(int64(o.Accuracy/time.Second)))...)
}
tst := Seq(info)
return build(tst, Options{Hash: crypto.SHA256}, true, []Signer{tsa})
return build(TSTInfo(subject, genTime, o), cms, true, []Signer{tsa})
}

@ -0,0 +1,152 @@
package cmstest
import (
"bytes"
"slices"
"g.activething.com/go/DateKeys/internal/der"
)
// The edits of the DER of a signature, a token or a certificate: what an
// attacker, or a signing application of another country, does to the bytes
// after they are signed. A path lists the index of a child at each level,
// from the element given: in a signature or a token, 1, 0 is the SignedData.
// Children returns the encodings of the children of the constructed element b.
func Children(b []byte) [][]byte {
_, kids, err := der.Split(b)
if err != nil {
panic(err)
}
return kids
}
// At returns the element at path in b.
func At(b []byte, path ...int) []byte {
for _, i := range path {
b = Children(b)[i]
}
return b
}
// Edit returns b with the element at path replaced by what f returns for it,
// and the lengths of its ancestors written again. f may return nil, to remove
// the element, or several elements one after the other.
func Edit(b []byte, f func(old []byte) []byte, path ...int) []byte {
if len(path) == 0 {
return f(b)
}
kids := slices.Clone(Children(b))
kids[path[0]] = Edit(kids[path[0]], f, path[1:]...)
return tlv(b[0], kids...)
}
// Retag returns an edit that changes the identifier octet of an element.
func Retag(tag byte) func([]byte) []byte {
return func(b []byte) []byte { return append([]byte{tag}, b[1:]...) }
}
// Replace returns an edit that puts elems in the place of an element.
func Replace(elems ...[]byte) func([]byte) []byte {
return func([]byte) []byte { return bytes.Join(elems, nil) }
}
// Append returns an edit that adds elems at the end of the children of a
// constructed element.
func Append(elems ...[]byte) func([]byte) []byte {
return func(b []byte) []byte { return tlv(b[0], append(slices.Clone(Children(b)), elems...)...) }
}
// Indefinite returns the constructed element b with an indefinite length:
// BER, which DER forbids (X.690 10.1).
func Indefinite(b []byte) []byte {
c, err := der.Content(b)
if err != nil {
panic(err)
}
out := append([]byte{b[0], 0x80}, c...)
return append(out, 0, 0)
}
// SignedDataPath is the path of the SignedData in a signature or a token.
var SignedDataPath = []int{1, 0}
// SignerInfosPath returns the path of the signerInfos of the signature or the
// token b: the last field of its SignedData.
func SignerInfosPath(b []byte) []int {
return append(slices.Clone(SignedDataPath), len(Children(At(b, SignedDataPath...)))-1)
}
// sid returns the two forms of the SignerIdentifier of s.
func sid(s Signer) (issuerAndSerial, ski []byte) {
return Seq(s.Cert.RawIssuer, s.Cert.Serial), tlv(0x80, s.Cert.SubjectKeyId)
}
// editSigner applies f to the SignerInfo of s in the signature b, and removes
// it when f returns nil.
func editSigner(b []byte, s Signer, f func(info []byte) []byte) []byte {
ias, ski := sid(s)
return Edit(b, func(set []byte) []byte {
var out [][]byte
for _, info := range Children(set) {
if id := Children(info)[1]; bytes.Equal(id, ias) || s.Cert.SubjectKeyId != nil && bytes.Equal(id, ski) {
if info = f(info); info == nil {
continue
}
}
out = append(out, info)
}
return Set(0x31, out...)
}, SignerInfosPath(b)...)
}
// Withdraw returns the signature b without the SignerInfo of s: a signature
// withdrawn. Its certificate stays.
func Withdraw(b []byte, s Signer) []byte {
return editSigner(b, s, func([]byte) []byte { return nil })
}
// WithoutTimeStamp returns the signature b with the unsigned attributes of
// the SignerInfo of s removed: its CAdES-T withdrawn. What the SignerInfo
// signs does not change.
func WithoutTimeStamp(b []byte, s Signer) []byte {
return editSigner(b, s, func(info []byte) []byte {
f := Children(info)
if n := len(f); n > 0 && f[n-1][0] == 0xa1 {
f = f[:n-1]
}
return Seq(f...)
})
}
// Merge returns the co-signature that joins the signatures sigs of one
// message, as a signing application adds a SignerInfo to a signature: the
// digest algorithms and the certificates of all, each once, and their
// SignerInfo, each in DER order.
func Merge(sigs ...[]byte) []byte {
var algs, certs, infos [][]byte
var version, encap []byte
for _, s := range sigs {
f := Children(At(s, SignedDataPath...))
version, encap = f[0], f[2]
algs = append(algs, Children(f[1])...)
for _, x := range f[3:] {
switch x[0] {
case 0xa0:
certs = append(certs, Children(x)...)
case 0x31:
infos = append(infos, Children(x)...)
}
}
}
once := func(e [][]byte) [][]byte {
slices.SortFunc(e, bytes.Compare)
return slices.CompactFunc(e, bytes.Equal)
}
fields := [][]byte{version, Set(0x31, once(algs)...), encap}
if len(certs) > 0 {
fields = append(fields, Set(0xa0, once(certs)...))
}
fields = append(fields, Set(0x31, infos...))
return Seq(OID(OIDSignedData), tlv(0xa0, Seq(fields...)))
}

@ -0,0 +1,85 @@
package cms_test
import (
"crypto/elliptic"
"crypto/sha1"
"errors"
"testing"
"g.activething.com/go/DateKeys/internal/cms"
"g.activething.com/go/DateKeys/internal/cms/cmstest"
"g.activething.com/go/DateKeys/internal/der"
)
// The keys and the certificates of the tests of the reader, made once: an
// RSA key takes time to generate. The certificates are made field by field,
// with the profile of spec §29.10, and are valid from 2020 to 2040.
var (
ecKey = cmstest.ECKey(elliptic.P256())
ecKey2 = cmstest.ECKey(elliptic.P256())
rsaKey = cmstest.RSAKey(2048)
ana = cmstest.NewCert(cmstest.CertSpec{CN: "Ana López"}, ecKey)
luis = cmstest.NewCert(cmstest.CertSpec{CN: "Luis Gómez"}, rsaKey)
tsa = cmstest.NewCert(cmstest.CertSpec{CN: "TSA de prueba"}, ecKey2)
)
// path joins paths of cmstest.Edit.
func path(parts ...any) []int {
var out []int
for _, p := range parts {
switch x := p.(type) {
case int:
out = append(out, x)
case []int:
out = append(out, x...)
}
}
return out
}
// firstSignerInfo is the path of the first SignerInfo of a signature.
func firstSignerInfo(b []byte) []int { return path(cmstest.SignerInfosPath(b), 0) }
// wantForm checks that the signature b breaks the profile (F1).
func wantForm(t *testing.T, name string, b []byte) {
t.Helper()
if _, err := cms.ParseSignature(b); !errors.Is(err, cms.ErrForm) {
t.Errorf("%s: %v, want a form error", name, err)
}
}
// signerOf parses the signature b, which must meet the profile, and returns
// its only SignerInfo.
func signerOf(t *testing.T, name string, b []byte) *cms.SignerInfo {
t.Helper()
sd, err := cms.ParseSignature(b)
if err != nil || len(sd.Signers) != 1 {
t.Fatalf("%s: %v", name, err)
}
return sd.Signers[0]
}
// resultOf is the result of the only SignerInfo of the signature b over msg.
func resultOf(t *testing.T, name string, b []byte) cms.Result {
t.Helper()
return signerOf(t, name, b).Check(msg)
}
func isForm(err error) bool { return errors.Is(err, cms.ErrForm) }
func isAlgorithm(err error) bool { return errors.Is(err, cms.ErrAlgorithm) }
// contentOf returns the content octets of the DER element b.
func contentOf(b []byte) []byte {
c, err := der.Content(b)
if err != nil {
panic(err)
}
return c
}
func sha1Sum(b []byte) []byte {
s := sha1.Sum(b)
return s[:]
}

@ -0,0 +1,348 @@
package cms_test
import (
"bytes"
"crypto"
"crypto/sha256"
"encoding/asn1"
"slices"
"testing"
"time"
"g.activething.com/go/DateKeys/internal/cms"
"g.activething.com/go/DateKeys/internal/cms/cmstest"
)
// Spec §29.10, "Forma", rules 1 to 4, and the rules that follow them: each
// case breaks one check of the reader, and the signature is F1.
func TestSignatureFormRules(t *testing.T) {
good := cmstest.Signature(msg, cmstest.Options{}, ana)
both := cmstest.Signature(msg, cmstest.Options{}, ana, luis)
tok := func(sig []byte) []byte { return cmstest.Token(sig, now, cmstest.TokenOptions{}, tsa) }
sealed := cmstest.Signature(msg, cmstest.Options{Token: tok}, ana)
sd := cmstest.SignedDataPath
si := firstSignerInfo(good)
attrsOf := func(mutate func(attrs [][]byte) [][]byte) []byte {
return cmstest.Signature(msg, cmstest.Options{Mutate: mutate}, ana)
}
extra := func(attrs ...[]byte) []byte { return cmstest.Signature(msg, cmstest.Options{ExtraAttrs: attrs}, ana) }
h := sha256.Sum256(ana.Cert.Raw)
v2 := func(value []byte) func([][]byte) [][]byte {
return func(a [][]byte) [][]byte {
a[2] = cmstest.Seq(cmstest.OID(cmstest.OIDSigCertV2), cmstest.Set(0x31, value))
return a
}
}
unknown := asn1.ObjectIdentifier{1, 2, 3, 4}
reversed := func(b []byte) []byte {
k := slices.Clone(cmstest.Children(b))
slices.Reverse(k)
return cmstest.TLV(b[0], k...)
}
same := func(c1, c2 cmstest.CertSpec) []byte {
a, b := cmstest.NewCert(c1, ecKey), cmstest.NewCert(c2, ecKey2)
return cmstest.Signature(msg, cmstest.Options{ExtraCerts: [][]byte{b.Cert.Raw}}, a)
}
ocsp := func(n int64) []byte {
return cmstest.TLV(0xa1, cmstest.OID(cmstest.OIDOCSP), cmstest.Seq(cmstest.Int(n)))
}
twoOCSP := cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{ocsp(1), ocsp(2)}}, ana)
for name, b := range map[string][]byte{
// Rule 1: DER, the ContentInfo and the SignedData.
"BER": cmstest.Signature(msg, cmstest.Options{BER: true}, ana),
"a ContentInfo with a third element": cmstest.Edit(good, cmstest.Append(cmstest.Null())),
"a ContentInfo of only its type": cmstest.Seq(cmstest.OID(cmstest.OIDSignedData)),
"the content as [1]": cmstest.Edit(good, cmstest.Retag(0xa1), 1),
"the content type id-data": cmstest.Edit(good, cmstest.Replace(cmstest.OID(cmstest.OIDData)), 0),
"the content type an INTEGER": cmstest.Edit(good, cmstest.Replace(cmstest.Int(1)), 0),
"[0] with an element more": cmstest.Edit(good, cmstest.Append(cmstest.Null()), 1),
"[0] empty": cmstest.Edit(good, cmstest.Replace(cmstest.TLV(0xa0)), 1),
"[0] holding a SET": cmstest.Edit(good, cmstest.Retag(0x31), sd...),
"a SignedData of two fields": cmstest.Edit(good, func(b []byte) []byte { return cmstest.Seq(cmstest.Children(b)[:2]...) }, sd...),
"a SignedData without signerInfos": cmstest.Edit(good, func(b []byte) []byte { k := cmstest.Children(b); return cmstest.Seq(k[:len(k)-1]...) }, sd...),
"a field after signerInfos": cmstest.Edit(good, cmstest.Append(cmstest.Set(0x31)), sd...),
"the version as an OCTET STRING": cmstest.Edit(good, cmstest.Retag(0x04), path(sd, 0)...),
"digestAlgorithms as a SEQUENCE": cmstest.Edit(good, cmstest.Retag(0x30), path(sd, 1)...),
"digestAlgorithms out of order": cmstest.Edit(good, cmstest.Replace(cmstest.TLV(0x31, cmstest.HashAlg(crypto.SHA512), cmstest.HashAlg(crypto.SHA256))), path(sd, 1)...),
"a digestAlgorithm that is an INTEGER": cmstest.Edit(good, cmstest.Replace(cmstest.Set(0x31, cmstest.Int(1))), path(sd, 1)...),
"a digestAlgorithm that is a SET": cmstest.Edit(good, cmstest.Replace(cmstest.Set(0x31, cmstest.TLV(0x31, cmstest.OID(cmstest.OIDSHA256)))), path(sd, 1)...),
"a digestAlgorithm without an OID": cmstest.Edit(good, cmstest.Replace(cmstest.Set(0x31, cmstest.Seq(cmstest.Int(1)))), path(sd, 1)...),
"an empty digestAlgorithm": cmstest.Edit(good, cmstest.Replace(cmstest.Set(0x31, cmstest.Seq())), path(sd, 1)...),
"a digestAlgorithm of three fields": cmstest.Edit(good, cmstest.Replace(cmstest.Set(0x31, cmstest.AlgID(cmstest.OIDSHA256, cmstest.Null(), cmstest.Null()))), path(sd, 1)...),
"encapContentInfo as a SET": cmstest.Edit(good, cmstest.Retag(0x31), path(sd, 2)...),
"an empty encapContentInfo": cmstest.Edit(good, cmstest.Replace(cmstest.Seq()), path(sd, 2)...),
"encapContentInfo of three fields": cmstest.Edit(good, cmstest.Append(cmstest.TLV(0xa0, cmstest.Octets(msg)), cmstest.Null()), path(sd, 2)...),
"eContentType an INTEGER": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.Int(1))), path(sd, 2)...),
"eContentType id-ct-TSTInfo": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo))), path(sd, 2)...),
"rule 2: an eContent in the signature": cmstest.Edit(good, cmstest.Append(cmstest.TLV(0xa0, cmstest.Octets(msg))), path(sd, 2)...),
"certificates out of order": cmstest.Edit(both, reversed, path(sd, 3)...),
"a CertificateChoice [4]": cmstest.Signature(msg, cmstest.Options{ExtraCerts: [][]byte{cmstest.TLV(0xa4, cmstest.Null())}}, ana),
"a CertificateChoice that is a SET": cmstest.Signature(msg, cmstest.Options{ExtraCerts: [][]byte{cmstest.Set(0x31, cmstest.Null())}}, ana),
"rule 3: a CRL in crls": cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{cmstest.Seq(cmstest.Int(1))}}, ana),
"rule 3: a CRL of the shape of an OCSP": cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{cmstest.Seq(cmstest.OID(cmstest.OIDOCSP), cmstest.Seq(cmstest.Int(0)))}}, ana),
"rule 3: crls out of order": cmstest.Edit(twoOCSP, reversed, path(sd, 4)...),
"rule 3: another revocation format": cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{cmstest.TLV(0xa1, cmstest.OID(unknown), cmstest.Null())}}, ana),
"rule 3: a revocation format of one": cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{cmstest.TLV(0xa1, cmstest.OID(cmstest.OIDOCSP))}}, ana),
"rule 3: a revocation format by number": cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{cmstest.TLV(0xa1, cmstest.Int(2), cmstest.Null())}}, ana),
"signerInfos as a SEQUENCE": cmstest.Edit(good, cmstest.Retag(0x30), cmstest.SignerInfosPath(good)...),
"no SignerInfo": cmstest.Edit(good, cmstest.Replace(cmstest.TLV(0x31)), cmstest.SignerInfosPath(good)...),
"signerInfos out of order": cmstest.Signature(msg, cmstest.Options{Unsorted: true}, ana, luis),
// Rule 3: the SignerInfo and its sid.
"a SignerInfo without signedAttrs": cmstest.Edit(good, func(b []byte) []byte { k := cmstest.Children(b); return cmstest.Seq(k[0], k[1], k[2], k[4], k[5]) }, si...),
"signedAttrs as [1]": cmstest.Edit(good, cmstest.Retag(0xa1), path(si, 3)...),
"a SignerInfo without its signature": cmstest.Edit(good, func(b []byte) []byte { return cmstest.Seq(cmstest.Children(b)[:5]...) }, si...),
"the version of a SignerInfo as OCTETS": cmstest.Edit(good, cmstest.Retag(0x04), path(si, 0)...),
"the version 2": cmstest.Signature(msg, cmstest.Options{Version: 2}, ana),
"the version 3 with issuerAndSerialNumber": cmstest.Signature(msg, cmstest.Options{Version: 3}, ana),
"the version 1 with subjectKeyIdentifier": cmstest.Signature(msg, cmstest.Options{Version: 1, SKI: true}, ana),
"the version 4 with subjectKeyIdentifier": cmstest.Signature(msg, cmstest.Options{Version: 4, SKI: true}, ana),
"the version 257": cmstest.Edit(good, cmstest.Replace(cmstest.Int(257)), path(si, 0)...),
"the digestAlgorithm as a SET": cmstest.Edit(good, cmstest.Retag(0x31), path(si, 2)...),
"the signatureAlgorithm as a SET": cmstest.Edit(good, cmstest.Retag(0x31), path(si, 4)...),
"the digestAlgorithm of a SignerInfo, no OID": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.Int(1))), path(si, 2)...),
"a signatureAlgorithm without an OID": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.Int(1))), path(si, 4)...),
"an empty signatureAlgorithm": cmstest.Edit(good, cmstest.Replace(cmstest.Seq()), path(si, 4)...),
"the signature as a BIT STRING": cmstest.Edit(good, func(b []byte) []byte { return cmstest.BitString(contentOf(b)) }, path(si, 5)...),
"a field after the unsigned attributes": cmstest.Signature(msg, cmstest.Options{Token: tok, EditSignerInfo: func(f [][]byte) [][]byte { return append(f, cmstest.Null()) }}, ana),
"a field [2] after the signature": cmstest.Signature(msg, cmstest.Options{EditSignerInfo: func(f [][]byte) [][]byte { return append(f, cmstest.TLV(0xa2, cmstest.BigArcAttr())) }}, ana),
"a sid of three elements": cmstest.Edit(good, cmstest.Append(cmstest.Null()), path(si, 1)...),
"a sid of one element": cmstest.Edit(good, func(b []byte) []byte { return cmstest.Seq(cmstest.Children(b)[0]) }, path(si, 1)...),
"the issuer of the sid as a SET": cmstest.Edit(good, cmstest.Retag(0x31), path(si, 1, 0)...),
"the serial of the sid as an OCTET STRING": cmstest.Edit(good, cmstest.Retag(0x04), path(si, 1, 1)...),
"the serial of another certificate": cmstest.Edit(good, cmstest.Replace(cmstest.Int(12345)), path(si, 1, 1)...),
"a sid of another choice": cmstest.Edit(good, cmstest.Retag(0x81), path(si, 1)...),
"a subjectKeyIdentifier of no certificate": cmstest.Edit(cmstest.Signature(msg, cmstest.Options{SKI: true}, ana), cmstest.Replace(cmstest.TLV(0x80, []byte("other"))), path(si, 1)...),
"no certificate of the signer": cmstest.Signature(msg, cmstest.Options{OmitCert: true}, ana),
"two certificates of one issuer and serial": same(cmstest.CertSpec{CN: "A", Serial: cmstest.Int(7)}, cmstest.CertSpec{CN: "B", Serial: cmstest.Int(7), Issuer: cmstest.Name(cmstest.ATV(cmstest.OIDCommonName, cmstest.UTF8("A")))}),
"two certificates of one keyIdentifier": cmstest.Signature(msg, cmstest.Options{SKI: true, ExtraCerts: [][]byte{cmstest.NewCert(cmstest.CertSpec{CN: "B", SKI: ana.Cert.SubjectKeyId}, ecKey2).Cert.Raw}}, ana),
"an empty keyIdentifier, a certificate of none": cmstest.Signature(msg, cmstest.Options{SKI: true}, cmstest.NewCert(cmstest.CertSpec{CN: "Sin SKI", NoExtensions: true}, ecKey)),
"two SignerInfo of one certificate": cmstest.Signature(msg, cmstest.Options{}, ana, ana),
"one SignerInfo twice": cmstest.Signature(msg, cmstest.Options{SignerInfoTwice: true}, ana),
"the signer's certificate breaks the profile": cmstest.Signature(msg, cmstest.Options{}, cmstest.NewCert(cmstest.CertSpec{CN: "Ana", NoVersion: true}, ecKey)),
// Rule 4 and the rules after it: the attributes.
"signedAttrs out of order": cmstest.Signature(msg, cmstest.Options{UnsortedAttrs: true}, ana),
"an attribute as a SET": extra(cmstest.TLV(0x31, cmstest.OID(unknown), cmstest.Set(0x31, cmstest.Null()))),
"an attribute of three fields": extra(cmstest.Seq(cmstest.OID(unknown), cmstest.Set(0x31, cmstest.Null()), cmstest.Null())),
"an attribute of one field": extra(cmstest.Seq(cmstest.OID(unknown))),
"an attribute that is an INTEGER": extra(cmstest.Int(5)),
"the values of an attribute as a SEQUENCE": extra(cmstest.Seq(cmstest.OID(unknown), cmstest.Seq(cmstest.Null()))),
"an attribute whose type is an INTEGER": extra(cmstest.Seq(cmstest.Int(1), cmstest.Set(0x31, cmstest.Null()))),
"an unknown attribute without a value": extra(cmstest.Seq(cmstest.OID(unknown), cmstest.Set(0x31))),
"values of an attribute out of order": extra(cmstest.Seq(cmstest.OID(unknown), cmstest.TLV(0x31, cmstest.Int(2), cmstest.Int(1)))),
"two content-type attributes": cmstest.Signature(msg, cmstest.Options{ContentType2: true}, ana),
// Two values, id-data first in DER order: one value is required, not
// the first of several.
"a content-type of two values": attrsOf(func(a [][]byte) [][]byte {
a[0] = cmstest.Attr(cmstest.OIDContentType, cmstest.OID(cmstest.OIDData), cmstest.OID(cmstest.OIDTSTInfo))
return a
}),
"a message-digest of two values": attrsOf(func(a [][]byte) [][]byte {
s := sha256.Sum256(msg)
a[1] = cmstest.Attr(cmstest.OIDMessageDigest, cmstest.Octets(s[:]), cmstest.Octets(append(s[:], 0)))
return a
}),
"a second content-type without a value": extra(cmstest.Seq(cmstest.OID(cmstest.OIDContentType), cmstest.Set(0x31))),
"no content-type": attrsOf(func(a [][]byte) [][]byte { return a[1:] }),
"the content-type id-signedData": attrsOf(func(a [][]byte) [][]byte {
a[0] = cmstest.Attr(cmstest.OIDContentType, cmstest.OID(cmstest.OIDSignedData))
return a
}),
"the content-type id-ct-TSTInfo": attrsOf(func(a [][]byte) [][]byte {
a[0] = cmstest.Attr(cmstest.OIDContentType, cmstest.OID(cmstest.OIDTSTInfo))
return a
}),
"a content-type that is OCTETS": attrsOf(func(a [][]byte) [][]byte {
a[0] = cmstest.Attr(cmstest.OIDContentType, cmstest.Octets([]byte("data")))
return a
}),
"no message-digest": cmstest.Signature(msg, cmstest.Options{NoMessageDigest: true}, ana),
"two message-digest attributes": attrsOf(func(a [][]byte) [][]byte { return append(a, a[1]) }),
"a message-digest that is [0]": attrsOf(func(a [][]byte) [][]byte {
s := sha256.Sum256(msg)
a[1] = cmstest.Attr(cmstest.OIDMessageDigest, cmstest.TLV(0x80, s[:]))
return a
}),
"no signing-certificate-v2": cmstest.Signature(msg, cmstest.Options{NoSigCertV2: true}, ana),
"only a signing-certificate": cmstest.Signature(msg, cmstest.Options{NoSigCertV2: true, SigCertV1: true}, ana),
"two signing-certificate-v2 attributes": attrsOf(func(a [][]byte) [][]byte { return append(a, a[2]) }),
"a signing-certificate-v2 of two values": v2Values(h[:]),
"a SigningCertificateV2 as a SET": attrsOf(v2(cmstest.TLV(0x31, cmstest.Seq(cmstest.Seq(cmstest.Octets(h[:])))))),
"an empty SigningCertificateV2": attrsOf(v2(cmstest.Seq())),
"its certs as a SET": attrsOf(v2(cmstest.Seq(cmstest.TLV(0x31, cmstest.Seq(cmstest.Octets(h[:])))))),
"its certs empty": attrsOf(v2(cmstest.Seq(cmstest.Seq()))),
"an ESSCertIDv2 as a SET": attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.TLV(0x31, cmstest.Octets(h[:])))))),
"an empty ESSCertIDv2": attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.Seq())))),
"an ESSCertIDv2 of only its algorithm": attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.HashAlg(crypto.SHA256)))))),
"a certHash as [0]": attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.TLV(0x80, h[:])))))),
"a hashAlgorithm without an OID": attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Int(1)), cmstest.Octets(h[:])))))),
"an ESSCertIDv2 of SHA-1": cmstest.Signature(msg, cmstest.Options{ESSHashAlg: cmstest.HashAlg(crypto.SHA1)}, ana),
"an ESSCertIDv2 of SHA-256 with an INTEGER": cmstest.Signature(msg, cmstest.Options{ESSHashAlg: cmstest.AlgID(cmstest.OIDSHA256, cmstest.Int(0))}, ana),
"the hash of another certificate": cmstest.Signature(msg, cmstest.Options{ESSCert: luis.Cert.Raw}, ana),
"two signature-time-stamp attributes": cmstest.Signature(msg, cmstest.Options{Token: tok, TimeStamps2: true}, ana),
"a signature-time-stamp of two values": cmstest.Signature(msg, cmstest.Options{Token: tok, Token2: true}, ana),
"unsigned attributes out of order": cmstest.Edit(sealed, func(b []byte) []byte {
k := append(slices.Clone(cmstest.Children(b)), cmstest.Attr(asn1.ObjectIdentifier{1, 2, 3, 4}, cmstest.Null()))
slices.SortFunc(k, func(x, y []byte) int { return bytes.Compare(y, x) })
return cmstest.TLV(0xa1, k...)
}, path(firstSignerInfo(sealed), 6)...),
"an unsigned attribute without a value": cmstest.Signature(msg, cmstest.Options{ExtraUnsigned: [][]byte{cmstest.Seq(cmstest.OID(unknown), cmstest.Set(0x31))}}, ana),
} {
wantForm(t, name, b)
}
}
// v2Values is a signature whose signing-certificate-v2 has two values, the
// first of them in DER order the right one: a longer certHash sorts after.
func v2Values(h []byte) []byte {
return cmstest.Signature(msg, cmstest.Options{Mutate: func(a [][]byte) [][]byte {
a[2] = cmstest.Seq(cmstest.OID(cmstest.OIDSigCertV2), cmstest.Set(0x31, cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(h)))), cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(make([]byte, 33)))))))
return a
}}, ana)
}
// What decides nothing (spec §29.10): another choice of CertificateChoices, a
// certificate twice or one that breaks the profile, OCSP responses, the other
// attributes, signed or not, a signing-certificate beside the v2, and an
// ESSCertIDv2 with its hashAlgorithm written.
func TestSignatureDecidesNothing(t *testing.T) {
tok := func(sig []byte) []byte { return cmstest.Token(sig, now, cmstest.TokenOptions{}, tsa) }
v1 := cmstest.NewCert(cmstest.CertSpec{CN: "Intermedia v1", NoVersion: true}, ecKey2)
for name, o := range map[string]cmstest.Options{
"an attribute certificate [1]": {ExtraCerts: [][]byte{cmstest.TLV(0xa1, cmstest.Seq(cmstest.Int(1)))}},
"the other choices [0], [2] and [3]": {ExtraCerts: [][]byte{cmstest.TLV(0xa0, cmstest.Null()), cmstest.TLV(0xa2, cmstest.Null()), cmstest.TLV(0xa3, cmstest.Null())}},
"the certificate twice": {ExtraCerts: [][]byte{ana.Cert.Raw}},
"a certificate of version 1": {ExtraCerts: [][]byte{v1.Cert.Raw}},
"a certificate that is not one": {ExtraCerts: [][]byte{cmstest.Seq(cmstest.Int(1))}},
"two OCSP responses": {OCSP: cmstest.Seq(cmstest.Int(0)), CRLs: [][]byte{cmstest.TLV(0xa1, cmstest.OID(cmstest.OIDOCSP), cmstest.Seq(cmstest.Int(9)))}},
"an attribute with an arc of 2^31": {ExtraAttrs: [][]byte{cmstest.BigArcAttr()}},
"a signing-time": {ExtraAttrs: [][]byte{cmstest.Attr(cmstest.OIDSigningTime, cmstest.UTCTime("260930120000Z"))}},
"an unknown attribute of two values": {ExtraAttrs: [][]byte{cmstest.Attr(asn1.ObjectIdentifier{1, 2, 3, 4}, cmstest.Int(1), cmstest.Int(2))}},
"a signing-certificate beside the v2": {SigCertV1: true},
"a wrong signing-certificate, and v2": {ExtraAttrs: [][]byte{cmstest.Attr(cmstest.OIDSigCertV1, cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(make([]byte, 20))))))}},
"an ESSCertIDv2 of SHA-256 written": {ESSHashAlg: cmstest.HashAlg(crypto.SHA256)},
"an ESSCertIDv2 of SHA-256 with NULL": {ESSHashAlg: cmstest.AlgID(cmstest.OIDSHA256, cmstest.Null())},
"an ESSCertIDv2 of SHA-384": {ESSHashAlg: cmstest.HashAlg(crypto.SHA384)},
"an ESSCertIDv2 of SHA-512": {ESSHashAlg: cmstest.HashAlg(crypto.SHA512)},
"an unknown unsigned attribute": {Token: tok, ExtraUnsigned: [][]byte{cmstest.BigArcAttr()}},
"unsigned attributes, no time-stamp": {ExtraUnsigned: [][]byte{cmstest.BigArcAttr()}},
"a sid by subjectKeyIdentifier": {SKI: true},
"the version written 1, as by default": {Version: 1},
} {
sd, err := cms.ParseSignature(cmstest.Signature(msg, o, ana))
if err != nil || len(sd.Signers) != 1 || sd.Signers[0].Cert.Hash != sha256.Sum256(ana.Cert.Raw) || sd.Signers[0].Check(msg) != cms.Valid {
t.Errorf("%s: %v", name, err)
continue
}
if o.Token != nil && sd.Signers[0].Token == nil {
t.Errorf("%s: the token is lost", name)
}
}
// The certificates of the profile, each once, and the OCSP responses.
sd, err := cms.ParseSignature(cmstest.Signature(msg, cmstest.Options{OCSP: cmstest.Seq(cmstest.Int(0)), ExtraCerts: [][]byte{ana.Cert.Raw, v1.Cert.Raw}}, ana, luis))
if err != nil || len(sd.Certs) != 2 || len(sd.OCSP) != 1 || !bytes.Equal(sd.OCSP[0], cmstest.Seq(cmstest.Int(0))) || sd.EContent != nil {
t.Errorf("certificates and OCSP: %v %+v", err, sd)
}
}
// A co-signature finds each certificate by the bytes of its issuer and its
// serial, both, or by its keyIdentifier: two certificates that share one of
// them are two signers, not an ambiguity.
func TestCoSignatureIdentifiers(t *testing.T) {
issuer := cmstest.Name(cmstest.ATV(cmstest.OIDCommonName, cmstest.UTF8("CA de prueba")))
for name, pair := range map[string][2]cmstest.CertSpec{
"one issuer, two serials": {{CN: "A", Issuer: issuer, Serial: cmstest.Int(1)}, {CN: "B", Issuer: issuer, Serial: cmstest.Int(2)}},
"one serial, two issuers": {{CN: "A", Serial: cmstest.Int(5)}, {CN: "B", Serial: cmstest.Int(5)}},
"one serial, two issuers by SKI": {{CN: "A", Serial: cmstest.Int(5)}, {CN: "B", Serial: cmstest.Int(5)}},
} {
a, b := cmstest.NewCert(pair[0], ecKey), cmstest.NewCert(pair[1], ecKey2)
sd, err := cms.ParseSignature(cmstest.Signature(msg, cmstest.Options{SKI: name == "one serial, two issuers by SKI"}, a, b))
if err != nil || len(sd.Signers) != 2 || sd.Signers[0].Cert == sd.Signers[1].Cert {
t.Errorf("%s: %v", name, err)
continue
}
for _, s := range sd.Signers {
if s.Check(msg) != cms.Valid {
t.Errorf("%s: %s does not verify", name, s.Cert.Holder())
}
}
}
}
// The tokens have the form of §29.11, step 1: one SignerInfo, the TSTInfo in
// its eContent, its content-type, its message-digest and the certificate of
// the authority named by signing-certificate or signing-certificate-v2.
func TestTokenFormRules(t *testing.T) {
subject := []byte("seal subject")
good := cmstest.Token(subject, now, cmstest.TokenOptions{}, tsa)
info := cmstest.TSTInfo(subject, now, cmstest.TokenOptions{})
encap := path(cmstest.SignedDataPath, 2)
other := cmstest.NewCert(cmstest.CertSpec{CN: "Otra TSA"}, ecKey)
withAttrs := func(mutate func(a [][]byte) [][]byte) []byte {
return cmstest.Token(subject, now, cmstest.TokenOptions{CMS: cmstest.Options{Mutate: mutate}}, tsa)
}
ess1 := func(c []byte) []byte {
return cmstest.Attr(cmstest.OIDSigCertV1, cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(sha1Sum(c))))))
}
ess2 := func(c []byte) []byte {
h := sha256.Sum256(c)
return cmstest.Attr(cmstest.OIDSigCertV2, cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(h[:])))))
}
twoCRLs := cmstest.Token(subject, now, cmstest.TokenOptions{CMS: cmstest.Options{CRLs: [][]byte{cmstest.Seq(cmstest.Int(1)), cmstest.Seq(cmstest.Int(2))}}}, tsa)
for name, b := range map[string][]byte{
"two SignerInfo": cmstest.Merge(cmstest.TokenRaw(info, tsa), cmstest.TokenRaw(info, other)),
"BER": cmstest.Token(subject, now, cmstest.TokenOptions{CMS: cmstest.Options{BER: true}}, tsa),
"id-data": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDData), cmstest.TLV(0xa0, cmstest.Octets(info)))), encap...),
"no eContent": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo))), encap...),
"an eContent [1]": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo), cmstest.TLV(0xa1, cmstest.Octets(info)))), encap...),
"an eContent of two OCTET STRINGs": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo), cmstest.TLV(0xa0, cmstest.Octets(info), cmstest.Octets(info)))), encap...),
"an empty eContent": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo), cmstest.TLV(0xa0))), encap...),
"an eContent that is a SEQUENCE": cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo), cmstest.TLV(0xa0, cmstest.Seq(info)))), encap...),
"the content-type id-data": withAttrs(func(a [][]byte) [][]byte {
a[0] = cmstest.Attr(cmstest.OIDContentType, cmstest.OID(cmstest.OIDData))
return a
}),
"no message-digest": cmstest.Token(subject, now, cmstest.TokenOptions{NoMessageDigest: true}, tsa),
"no signing certificate": withAttrs(func(a [][]byte) [][]byte { return a[:2] }),
"two signing-certificate attributes": withAttrs(func(a [][]byte) [][]byte { return append(a, a[2]) }),
"a signing-certificate of two values": withAttrs(func(a [][]byte) [][]byte {
// The right value first in DER order, and a longer one after it.
a[2] = cmstest.Seq(cmstest.OID(cmstest.OIDSigCertV1), cmstest.Set(0x31, cmstest.Children(cmstest.Children(a[2])[1])[0], cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(make([]byte, 21)))))))
return a
}),
"two signing-certificate-v2 and a v1": withAttrs(func(a [][]byte) [][]byte { return append(a, ess2(tsa.Cert.Raw), ess2(tsa.Cert.Raw)) }),
"a signing-certificate of another TSA": withAttrs(func(a [][]byte) [][]byte { a[2] = ess1(other.Cert.Raw); return a }),
"a signing-certificate-v2 of another": cmstest.Token(subject, now, cmstest.TokenOptions{SigCertV2: true, CMS: cmstest.Options{ESSCert: other.Cert.Raw}}, tsa),
"no certificate of the authority": cmstest.Token(subject, now, cmstest.TokenOptions{CMS: cmstest.Options{OmitCert: true}}, tsa),
"crls out of order": cmstest.Edit(twoCRLs, func(b []byte) []byte {
k := slices.Clone(cmstest.Children(b))
slices.Reverse(k)
return cmstest.TLV(0xa1, k...)
}, path(cmstest.SignedDataPath, 4)...),
} {
if _, err := cms.ParseToken(b); err == nil || !isForm(err) {
t.Errorf("%s: %v, want a form error", name, err)
}
}
// What decides nothing in a token: crls, its certificate twice, and the
// signing-certificate-v2 in the place of signing-certificate.
for name, o := range map[string]cmstest.TokenOptions{
"a CRL": {CRL: cmstest.Seq(cmstest.Int(1))},
"two CRLs": {CMS: cmstest.Options{CRLs: [][]byte{cmstest.Seq(cmstest.Int(1)), cmstest.Seq(cmstest.Int(2))}}},
"the certificate twice": {TSATwice: true},
"signing-certificate-v2": {SigCertV2: true},
"v2 beside a wrong v1": {SigCertV2: true, CMS: cmstest.Options{ExtraAttrs: [][]byte{ess1(other.Cert.Raw)}}},
"a sid by keyIdentifier": {CMS: cmstest.Options{SKI: true}},
"a signature of SHA-512": {CMS: cmstest.Options{Hash: crypto.SHA512}},
"an imprint of SHA-512": {Hash: crypto.SHA512},
"an accuracy of 1.5 s": {Accuracy: 1500 * time.Millisecond},
"an accuracy of 2 s and 3µs": {Accuracy: 2*time.Second + 3*time.Microsecond},
} {
tok, err := cms.ParseToken(cmstest.Token(subject, now, o, tsa))
if err != nil || !tok.Check(subject) || tok.Accuracy != o.Accuracy || tok.TSA.Hash != sha256.Sum256(tsa.Cert.Raw) {
t.Errorf("%s: %v", name, err)
}
}
}

@ -0,0 +1,186 @@
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
}

@ -19,13 +19,17 @@ var oidSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1}
// tstInfo builds a TSTInfo of RFC 3161 over subject, with the fields after
// genTime that the test gives.
func tstInfo(subject []byte, genTime []byte, after ...[]byte) []byte {
return cmstest.Seq(append(tstFields(subject, genTime), after...)...)
}
// tstFields are the five required fields of a TSTInfo.
func tstFields(subject []byte, genTime []byte) [][]byte {
h := sha256.Sum256(subject)
fields := [][]byte{
return [][]byte{
cmstest.Int(1), cmstest.OID(asn1.ObjectIdentifier{1, 2, 3, 4}),
cmstest.Seq(cmstest.Seq(cmstest.OID(oidSHA256)), cmstest.Octets(h[:])),
cmstest.Int(42), genTime,
}
return cmstest.Seq(append(fields, after...)...)
}
func parses(t *testing.T, tsa cmstest.Signer, info []byte, subject []byte) (*cms.Token, error) {
@ -44,28 +48,99 @@ func TestTSTInfoStrict(t *testing.T) {
tsa := cmstest.NewECDSA("TSA", elliptic.P256(), from, to)
subject := []byte("seal subject")
good := cmstest.GeneralizedTime("20260930120000Z")
if tok, err := parses(t, tsa, tstInfo(subject, good), subject); err != nil || !tok.GenTime.Equal(now) {
if tok, err := parses(t, tsa, tstInfo(subject, good), subject); err != nil || !tok.GenTime.Equal(now) || tok.Accuracy != 0 {
t.Fatalf("the baseline: %v", err)
}
if tok, err := parses(t, tsa, tstInfo(subject, cmstest.GeneralizedTime("20260930120000.5Z"), cmstest.Seq(cmstest.Int(2), cmstest.TLV(0x80, []byte{5}))), subject); err != nil ||
tok.GenTime.Sub(now) != 500*time.Millisecond || tok.Accuracy != 2*time.Second+5*time.Millisecond {
t.Fatalf("a fraction and an accuracy: %v", err)
}
// The optional fields, in their order, each once.
tsaName := cmstest.TLV(0xa0, cmstest.TLV(0xa4, cmstest.Seq()))
exts := cmstest.TLV(0xa1, cmstest.Seq(cmstest.OID(asn1.ObjectIdentifier{1, 2, 3}), cmstest.Octets(nil)))
ms := func(c ...byte) []byte { return cmstest.TLV(0x80, c) }
us := func(c ...byte) []byte { return cmstest.TLV(0x81, c) }
for name, tc := range map[string]struct {
after [][]byte
accuracy time.Duration
}{
"an empty accuracy": {[][]byte{cmstest.Seq()}, 0},
"only millis": {[][]byte{cmstest.Seq(ms(5))}, 5 * time.Millisecond},
"only micros": {[][]byte{cmstest.Seq(us(7))}, 7 * time.Microsecond},
"millis and micros of 999": {[][]byte{cmstest.Seq(ms(0x03, 0xe7), us(0x03, 0xe7))}, 999*time.Millisecond + 999*time.Microsecond},
"millis of 128": {[][]byte{cmstest.Seq(ms(0, 0x80))}, 128 * time.Millisecond},
"seconds, millis and micros": {[][]byte{cmstest.Seq(cmstest.Int(1), ms(5), us(7))}, time.Second + 5*time.Millisecond + 7*time.Microsecond},
"2^31 - 1 seconds": {[][]byte{cmstest.Seq(cmstest.Int(1<<31 - 1))}, (1<<31 - 1) * time.Second},
"ordering TRUE": {[][]byte{cmstest.Bool(true)}, 0},
"an accuracy and ordering TRUE": {[][]byte{cmstest.Seq(cmstest.Int(1)), cmstest.Bool(true)}, time.Second},
"a nonce": {[][]byte{cmstest.Int(99)}, 0},
"ordering TRUE and a nonce": {[][]byte{cmstest.Bool(true), cmstest.Int(99)}, 0},
"a tsa": {[][]byte{tsaName}, 0},
"extensions": {[][]byte{exts}, 0},
"every field": {[][]byte{cmstest.Seq(cmstest.Int(3)), cmstest.Bool(true), cmstest.Int(99), tsaName, exts}, 3 * time.Second},
} {
tok, err := parses(t, tsa, tstInfo(subject, good, tc.after...), subject)
if err != nil || tok.Accuracy != tc.accuracy {
t.Errorf("%s: %v", name, err)
}
}
fields := tstFields(subject, good)
with := func(i int, v []byte) []byte {
f := append([][]byte(nil), fields...)
f[i] = v
return cmstest.Seq(f...)
}
h := sha256.Sum256(subject)
big := []byte{1, 0, 0, 0, 0, 0, 0, 0, 5} // 2^64 + 5, which wraps around to 5 in 64 bits
for name, info := range map[string][]byte{
"a negative accuracy": tstInfo(subject, good, cmstest.Seq(cmstest.Int(-31536000))),
"an accuracy that overflows": tstInfo(subject, good, cmstest.Seq(cmstest.Int(9223372037))),
"millis of 0": tstInfo(subject, good, cmstest.Seq(cmstest.TLV(0x80, []byte{0}))),
"millis of 5000": tstInfo(subject, good, cmstest.Seq(cmstest.TLV(0x80, []byte{0x13, 0x88}))),
"genTime with an offset": tstInfo(subject, cmstest.GeneralizedTime("20260930130000+0100")),
"genTime with a trailing zero": tstInfo(subject, cmstest.GeneralizedTime("20260930120000.50Z")),
"genTime without seconds": tstInfo(subject, cmstest.GeneralizedTime("202609301200Z")),
"ordering FALSE written": tstInfo(subject, good, cmstest.TLV(0x01, []byte{0})),
"an extra INTEGER at the end": tstInfo(subject, good, cmstest.Int(7), cmstest.Int(8), cmstest.Int(9)),
"a field out of order": tstInfo(subject, good, cmstest.Int(7), cmstest.Seq(cmstest.Int(1))),
"a reserved tag in the extensions": tstInfo(subject, good, cmstest.TLV(0xa1, cmstest.TLV(0x0e, []byte{0x41}))),
"an unused-bits BIT STRING inside": tstInfo(subject, good, cmstest.TLV(0xa1, cmstest.TLV(0x03, []byte{7, 0xff}))),
"version 2": cmstest.Seq(cmstest.Int(2)),
"not a SEQUENCE": cmstest.Int(1),
"a negative accuracy": tstInfo(subject, good, cmstest.Seq(cmstest.Int(-31536000))),
"an accuracy of -1": tstInfo(subject, good, cmstest.Seq(cmstest.Int(-1))),
"an accuracy that overflows": tstInfo(subject, good, cmstest.Seq(cmstest.Int(9223372037))),
"an accuracy of 2^31 seconds": tstInfo(subject, good, cmstest.Seq(cmstest.Int(1<<31))),
"an accuracy of 2^64 + 5 seconds": tstInfo(subject, good, cmstest.Seq(cmstest.IntBytes(big))),
"millis of 0": tstInfo(subject, good, cmstest.Seq(ms(0))),
"millis of 1000": tstInfo(subject, good, cmstest.Seq(ms(0x03, 0xe8))),
"millis of 5000": tstInfo(subject, good, cmstest.Seq(ms(0x13, 0x88))),
"millis of -1": tstInfo(subject, good, cmstest.Seq(ms(0xff))),
"millis of 5 in two bytes": tstInfo(subject, good, cmstest.Seq(ms(0, 5))),
"millis of 2^64 + 5": tstInfo(subject, good, cmstest.Seq(ms(big...))),
"empty millis": tstInfo(subject, good, cmstest.Seq(ms())),
"millis constructed": tstInfo(subject, good, cmstest.Seq(cmstest.TLV(0xa0, cmstest.Int(5)))),
"micros of 0": tstInfo(subject, good, cmstest.Seq(us(0))),
"micros of 1000": tstInfo(subject, good, cmstest.Seq(us(0x03, 0xe8))),
"micros before millis": tstInfo(subject, good, cmstest.Seq(us(1), ms(1))),
"a field after the micros": tstInfo(subject, good, cmstest.Seq(us(1), cmstest.TLV(0x82, []byte{1}))),
"millis as an INTEGER": tstInfo(subject, good, cmstest.Seq(cmstest.Int(1), cmstest.Int(5))),
"genTime with an offset": tstInfo(subject, cmstest.GeneralizedTime("20260930130000+0100")),
"genTime with a trailing zero": tstInfo(subject, cmstest.GeneralizedTime("20260930120000.50Z")),
"genTime without seconds": tstInfo(subject, cmstest.GeneralizedTime("202609301200Z")),
"genTime as a UTCTime": tstInfo(subject, cmstest.UTCTime("260930120000Z")),
"ordering FALSE written": tstInfo(subject, good, cmstest.Bool(false)),
"ordering of two bytes": tstInfo(subject, good, cmstest.TLV(0x01, []byte{0xff, 0xff})),
"two accuracies": tstInfo(subject, good, cmstest.Seq(cmstest.Int(1)), cmstest.Seq(cmstest.Int(1))),
"two orderings": tstInfo(subject, good, cmstest.Bool(true), cmstest.Bool(true)),
"an extra INTEGER at the end": tstInfo(subject, good, cmstest.Int(7), cmstest.Int(8), cmstest.Int(9)),
"two tsa fields": tstInfo(subject, good, tsaName, tsaName),
"two extensions": tstInfo(subject, good, exts, exts),
"a field after the extensions": tstInfo(subject, good, exts, cmstest.Int(1)),
"a field out of order": tstInfo(subject, good, cmstest.Int(7), cmstest.Seq(cmstest.Int(1))),
"a reserved tag in the extensions": tstInfo(subject, good, cmstest.TLV(0xa1, cmstest.TLV(0x0e, []byte{0x41}))),
"an unused-bits BIT STRING inside": tstInfo(subject, good, cmstest.TLV(0xa1, cmstest.TLV(0x03, []byte{7, 0xff}))),
"version 2": cmstest.Seq(cmstest.Int(2)),
"version 2, the fields complete": with(0, cmstest.Int(2)),
"version 2^64 + 1": with(0, cmstest.IntBytes([]byte{1, 0, 0, 0, 0, 0, 0, 0, 1})),
"the version as an ENUMERATED": with(0, cmstest.TLV(0x0a, []byte{1})),
"the policy as an INTEGER": with(1, cmstest.Int(1)),
"the serialNumber as OCTETS": with(3, cmstest.Octets([]byte{42})),
"the messageImprint as a SET": with(2, cmstest.TLV(0x31, cmstest.Seq(cmstest.OID(oidSHA256)), cmstest.Octets(h[:]))),
"a messageImprint of three fields": with(2, cmstest.Seq(cmstest.Seq(cmstest.OID(oidSHA256)), cmstest.Octets(h[:]), cmstest.Null())),
"a messageImprint of one field": with(2, cmstest.Seq(cmstest.Seq(cmstest.OID(oidSHA256)))),
"a messageImprint algorithm as a SET": with(2, cmstest.Seq(cmstest.TLV(0x31, cmstest.OID(oidSHA256)), cmstest.Octets(h[:]))),
"a hashedMessage as a BIT STRING": with(2, cmstest.Seq(cmstest.Seq(cmstest.OID(oidSHA256)), cmstest.BitString(h[:]))),
"four fields": cmstest.Seq(fields[:4]...),
"a SET": cmstest.TLV(0x31, fields...),
"not a SEQUENCE": cmstest.Int(1),
"not DER": append(tstInfo(subject, good), 0),
} {
if _, err := cms.ParseToken(cmstest.TokenRaw(info, tsa)); !errors.Is(err, cms.ErrForm) {
t.Errorf("%s: %v", name, err)

@ -0,0 +1,266 @@
package cms_test
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"encoding/asn1"
"math/big"
"testing"
"time"
"g.activething.com/go/DateKeys/internal/cms"
"g.activething.com/go/DateKeys/internal/cms/cmstest"
)
var (
p384Key = cmstest.ECKey(elliptic.P384())
p521Key = cmstest.ECKey(elliptic.P521())
)
// pss is the AlgorithmIdentifier of RSASSA-PSS with the fields of its
// parameters given.
func pss(fields ...[]byte) []byte { return cmstest.AlgID(cmstest.OIDPSS, cmstest.Seq(fields...)) }
// Spec §29.10, "Algoritmos" and step 2 of "Verificación": the closed table of
// algorithms, and a key of another scheme than its algorithm, which is
// invalid and not outside the table (step 3).
func TestAlgorithmTable(t *testing.T) {
alg, null := cmstest.AlgID, cmstest.Null
h0 := cmstest.TLV(0xa0, cmstest.HashAlg(crypto.SHA256))
m1 := cmstest.TLV(0xa1, alg(cmstest.OIDMGF1, cmstest.HashAlg(crypto.SHA256)))
s2 := cmstest.TLV(0xa2, cmstest.Int(32))
p384 := cmstest.NewCert(cmstest.CertSpec{CN: "P-384"}, p384Key)
p521 := cmstest.NewCert(cmstest.CertSpec{CN: "P-521"}, p521Key)
for name, tc := range map[string]struct {
s cmstest.Signer
o cmstest.Options
want cms.Result
}{
"SHA-1, ECDSA": {ana, cmstest.Options{Hash: crypto.SHA1}, cms.NotVerifiable},
"SHA-1, RSA": {luis, cmstest.Options{Hash: crypto.SHA1}, cms.NotVerifiable},
"SHA-1, and another message": {ana, cmstest.Options{Hash: crypto.SHA1, Message: []byte("other")}, cms.NotVerifiable},
"SHA-256 with NULL": {ana, cmstest.Options{DigestAlg: alg(cmstest.OIDSHA256, null())}, cms.Valid},
"SHA-256 with an INTEGER": {ana, cmstest.Options{DigestAlg: alg(cmstest.OIDSHA256, cmstest.Int(0))}, cms.NotVerifiable},
"P-256 with SHA-384": {ana, cmstest.Options{Hash: crypto.SHA384}, cms.Valid},
"P-256 with SHA-512": {ana, cmstest.Options{Hash: crypto.SHA512}, cms.Valid},
"P-384 with SHA-384": {p384, cmstest.Options{Hash: crypto.SHA384}, cms.Valid},
"P-521 with SHA-512": {p521, cmstest.Options{Hash: crypto.SHA512}, cms.Valid},
"P-521 with SHA-256": {p521, cmstest.Options{}, cms.Valid},
"rsaEncryption": {luis, cmstest.Options{}, cms.Valid},
"rsaEncryption without parameters": {luis, cmstest.Options{SigAlg: alg(cmstest.OIDRSA)}, cms.Valid},
"rsaEncryption with an INTEGER": {luis, cmstest.Options{SigAlg: alg(cmstest.OIDRSA, cmstest.Int(0))}, cms.NotVerifiable},
"rsaEncryption with SHA-512": {luis, cmstest.Options{Hash: crypto.SHA512}, cms.Valid},
"sha256WithRSAEncryption": {luis, cmstest.Options{SigAlg: alg(cmstest.OIDSHA256RSA, null())}, cms.Valid},
"sha256WithRSAEncryption without NULL": {luis, cmstest.Options{SigAlg: alg(cmstest.OIDSHA256RSA)}, cms.Valid},
"sha256WithRSAEncryption, INTEGER": {luis, cmstest.Options{SigAlg: alg(cmstest.OIDSHA256RSA, cmstest.Int(0))}, cms.NotVerifiable},
"sha256WithRSAEncryption, SHA-384": {luis, cmstest.Options{Hash: crypto.SHA384, SigAlg: alg(cmstest.OIDSHA256RSA, null())}, cms.NotVerifiable},
"sha384WithRSAEncryption": {luis, cmstest.Options{Hash: crypto.SHA384, SigAlg: alg(cmstest.OIDSHA384RSA, null())}, cms.Valid},
"sha384WithRSAEncryption, SHA-256": {luis, cmstest.Options{SigAlg: alg(cmstest.OIDSHA384RSA, null())}, cms.NotVerifiable},
"sha384WithRSAEncryption, INTEGER": {luis, cmstest.Options{Hash: crypto.SHA384, SigAlg: alg(cmstest.OIDSHA384RSA, cmstest.Int(0))}, cms.NotVerifiable},
"sha512WithRSAEncryption": {luis, cmstest.Options{Hash: crypto.SHA512, SigAlg: alg(cmstest.OIDSHA512RSA, null())}, cms.Valid},
"sha512WithRSAEncryption, SHA-256": {luis, cmstest.Options{SigAlg: alg(cmstest.OIDSHA512RSA, null())}, cms.NotVerifiable},
"sha512WithRSAEncryption, INTEGER": {luis, cmstest.Options{Hash: crypto.SHA512, SigAlg: alg(cmstest.OIDSHA512RSA, cmstest.Int(0))}, cms.NotVerifiable},
"ecdsa-with-SHA256 with NULL": {ana, cmstest.Options{SigAlg: alg(cmstest.OIDECDSA256, null())}, cms.NotVerifiable},
"ecdsa-with-SHA256, SHA-384": {ana, cmstest.Options{Hash: crypto.SHA384, SigAlg: alg(cmstest.OIDECDSA256)}, cms.NotVerifiable},
"ecdsa-with-SHA384, SHA-256": {ana, cmstest.Options{SigAlg: alg(cmstest.OIDECDSA384)}, cms.NotVerifiable},
"ecdsa-with-SHA384 with NULL": {ana, cmstest.Options{Hash: crypto.SHA384, SigAlg: alg(cmstest.OIDECDSA384, null())}, cms.NotVerifiable},
"ecdsa-with-SHA512, SHA-256": {ana, cmstest.Options{SigAlg: alg(cmstest.OIDECDSA512)}, cms.NotVerifiable},
"ecdsa-with-SHA512 with NULL": {ana, cmstest.Options{Hash: crypto.SHA512, SigAlg: alg(cmstest.OIDECDSA512, null())}, cms.NotVerifiable},
"an algorithm outside the table": {ana, cmstest.Options{SigAlg: alg(asn1.ObjectIdentifier{1, 3, 101, 112})}, cms.NotVerifiable},
"another algorithm, an RSA key": {luis, cmstest.Options{SigAlg: alg(asn1.ObjectIdentifier{1, 3, 101, 112})}, cms.NotVerifiable},
"another algorithm with PSS parameters": {luis, cmstest.Options{PSS: true, SigAlg: alg(asn1.ObjectIdentifier{1, 2, 3, 4}, cmstest.Seq(h0, m1, s2))}, cms.NotVerifiable},
"PSS": {luis, cmstest.Options{PSS: true}, cms.Valid},
"PSS with SHA-384": {luis, cmstest.Options{PSS: true, Hash: crypto.SHA384}, cms.Valid},
"PSS, its fields written again": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, s2)}, cms.Valid},
"PSS with NULL in its hashes": {luis, cmstest.Options{PSS: true, SigAlg: pss(cmstest.TLV(0xa0, alg(cmstest.OIDSHA256, null())), cmstest.TLV(0xa1, alg(cmstest.OIDMGF1, alg(cmstest.OIDSHA256, null()))), s2)}, cms.Valid},
"PSS without parameters": {luis, cmstest.Options{PSS: true, SigAlg: alg(cmstest.OIDPSS)}, cms.NotVerifiable},
"PSS with NULL parameters": {luis, cmstest.Options{PSS: true, SigAlg: alg(cmstest.OIDPSS, null())}, cms.NotVerifiable},
"PSS parameters as a SET": {luis, cmstest.Options{PSS: true, SigAlg: alg(cmstest.OIDPSS, cmstest.TLV(0x31, h0, m1, s2))}, cms.NotVerifiable},
"PSS with [0] of two elements": {luis, cmstest.Options{PSS: true, SigAlg: pss(cmstest.TLV(0xa0, cmstest.HashAlg(crypto.SHA256), null()), m1, s2)}, cms.NotVerifiable},
"PSS with [0] primitive": {luis, cmstest.Options{PSS: true, SigAlg: pss(cmstest.TLV(0x80, cmstest.HashAlg(crypto.SHA256)), m1, s2)}, cms.NotVerifiable},
"PSS with [1] before [0]": {luis, cmstest.Options{PSS: true, SigAlg: pss(m1, h0, s2)}, cms.NotVerifiable},
"PSS with [0] twice": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, h0, m1, s2)}, cms.NotVerifiable},
"PSS of SHA-512 with SHA-256": {luis, cmstest.Options{PSS: true, SigAlg: pss(cmstest.TLV(0xa0, cmstest.HashAlg(crypto.SHA512)), m1, s2)}, cms.NotVerifiable},
"PSS with a hash of an INTEGER": {luis, cmstest.Options{PSS: true, SigAlg: pss(cmstest.TLV(0xa0, alg(cmstest.OIDSHA256, cmstest.Int(0))), m1, s2)}, cms.NotVerifiable},
"PSS with a hash without an OID": {luis, cmstest.Options{PSS: true, SigAlg: pss(cmstest.TLV(0xa0, cmstest.Seq(cmstest.Int(0))), m1, s2)}, cms.NotVerifiable},
"PSS with another mask": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, alg(asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 9}, cmstest.HashAlg(crypto.SHA256))), s2)}, cms.NotVerifiable},
"PSS with MGF1 without its hash": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, alg(cmstest.OIDMGF1)), s2)}, cms.NotVerifiable},
"PSS with MGF1 not an algorithm": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, cmstest.Seq(cmstest.Int(1))), s2)}, cms.NotVerifiable},
"PSS with MGF1 of SHA-512": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, alg(cmstest.OIDMGF1, cmstest.HashAlg(crypto.SHA512))), s2)}, cms.NotVerifiable},
"PSS with MGF1 of a hash with INTEGER": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, alg(cmstest.OIDMGF1, alg(cmstest.OIDSHA256, cmstest.Int(0)))), s2)}, cms.NotVerifiable},
"PSS with MGF1 of a hash without OID": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, alg(cmstest.OIDMGF1, cmstest.Seq(cmstest.Int(0)))), s2)}, cms.NotVerifiable},
"PSS with a salt of 20": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, cmstest.TLV(0xa2, cmstest.Int(20)))}, cms.NotVerifiable},
"PSS with a salt in OCTETS": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, cmstest.TLV(0xa2, cmstest.Octets([]byte{32})))}, cms.NotVerifiable},
"PSS with a salt of 2^64 + 32": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, cmstest.TLV(0xa2, cmstest.IntBytes([]byte{1, 0, 0, 0, 0, 0, 0, 0, 32})))}, cms.NotVerifiable},
"PSS with a negative salt": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, cmstest.TLV(0xa2, cmstest.Int(-224)))}, cms.NotVerifiable},
"PSS with trailerField": {luis, cmstest.Options{PSS: true, PSSTrailer: true}, cms.NotVerifiable},
"PSS with a field [4]": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, s2, cmstest.TLV(0xa4, cmstest.Int(1)))}, cms.NotVerifiable},
"PSS without [0]": {luis, cmstest.Options{PSS: true, SigAlg: pss(m1, s2)}, cms.NotVerifiable},
"PSS without [1]": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, s2)}, cms.NotVerifiable},
"PSS without [2]": {luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1)}, cms.NotVerifiable},
"step 3: an RSA key with ECDSA": {luis, cmstest.Options{SigAlg: alg(cmstest.OIDECDSA256)}, cms.Invalid},
"step 3: an EC key with PKCS #1": {ana, cmstest.Options{SigAlg: alg(cmstest.OIDRSA, null())}, cms.Invalid},
"step 3: an EC key with PSS": {ana, cmstest.Options{SigAlg: pss(h0, m1, s2)}, cms.Invalid},
"step 3: PKCS #1, a bit flipped": {luis, cmstest.Options{CorruptSignature: true}, cms.Invalid},
"step 3: PSS, a bit flipped": {luis, cmstest.Options{PSS: true, CorruptSignature: true}, cms.Invalid},
"step 3: ECDSA, a bit flipped": {ana, cmstest.Options{CorruptSignature: true}, cms.Invalid},
"step 3: the digest of another message": {ana, cmstest.Options{Message: []byte("other")}, cms.Invalid},
} {
if got := resultOf(t, name, cmstest.Signature(msg, tc.o, tc.s)); got != tc.want {
t.Errorf("%s: %v, want %v", name, got, tc.want)
}
}
}
// The keys of the table (spec §29.10): RSA with NULL parameters, exactly a
// modulus and an exponent, the modulus odd of 2048 to 4096 bits and the
// exponent odd from 3 to 2^31 - 1; EC on P-256, P-384 or P-521, the point
// uncompressed and on the curve. Any other is not verifiable; a key of the
// table that does not verify the signature is invalid.
func TestKeyTable(t *testing.T) {
n, e := rsaKey.N, big.NewInt(65537)
two := func(bits uint) *big.Int {
return new(big.Int).Add(new(big.Int).Lsh(big.NewInt(1), bits), big.NewInt(1))
}
key := func(fields ...[]byte) []byte { return cmstest.BitString(cmstest.Seq(fields...)) }
rsaAlg := cmstest.AlgID(cmstest.OIDRSA, cmstest.Null())
ecAlg := cmstest.AlgID(cmstest.OIDECPublicKey, cmstest.OID(cmstest.OIDP256))
point := cmstest.Uncompressed(&ecKey.PublicKey)
off := bytes.Clone(point)
off[len(off)-1] ^= 1
even := evenPointKey()
evenPoint := cmstest.Uncompressed(&even.PublicKey)
for name, tc := range map[string]struct {
spki []byte
key crypto.Signer
want cms.Result
}{
"RSA of 2048 bits": {cmstest.SPKIRSA(n, e), rsaKey, cms.Valid},
"RSA of 2047 bits": {cmstest.SPKIRSA(two(2046), e), rsaKey, cms.NotVerifiable},
"RSA of 4096 bits that is another": {cmstest.SPKIRSA(two(4095), e), rsaKey, cms.Invalid},
"RSA of 4097 bits": {cmstest.SPKIRSA(two(4096), e), rsaKey, cms.NotVerifiable},
"an even modulus": {cmstest.SPKIRSA(new(big.Int).Add(n, big.NewInt(1)), e), rsaKey, cms.NotVerifiable},
"a negative modulus": {cmstest.SPKIRSA(new(big.Int).Neg(n), e), rsaKey, cms.NotVerifiable},
"an exponent of 1": {cmstest.SPKIRSA(n, big.NewInt(1)), rsaKey, cms.NotVerifiable},
"an exponent of 3": {cmstest.SPKIRSA(n, big.NewInt(3)), rsaKey, cms.Invalid},
"an even exponent": {cmstest.SPKIRSA(n, big.NewInt(65536)), rsaKey, cms.NotVerifiable},
"an exponent of 2^31 - 1": {cmstest.SPKIRSA(n, big.NewInt(1<<31-1)), rsaKey, cms.Invalid},
"an exponent of 2^64 + 65537": {cmstest.SPKIRSA(n, new(big.Int).Add(new(big.Int).Lsh(big.NewInt(1), 64), e)), rsaKey, cms.NotVerifiable},
"a negative exponent": {cmstest.SPKIRSA(n, big.NewInt(-1)), rsaKey, cms.NotVerifiable},
"an RSAPublicKey with a byte more": {cmstest.Seq(rsaAlg, cmstest.BitString(append(cmstest.Seq(cmstest.BigInt(n), cmstest.BigInt(e)), 0))), rsaKey, cms.NotVerifiable},
"an RSAPublicKey as a SET": {cmstest.Seq(rsaAlg, cmstest.BitString(cmstest.TLV(0x31, cmstest.BigInt(n), cmstest.BigInt(e)))), rsaKey, cms.NotVerifiable},
"an RSAPublicKey of three INTEGERs": {cmstest.Seq(rsaAlg, key(cmstest.BigInt(n), cmstest.BigInt(e), cmstest.Int(1))), rsaKey, cms.NotVerifiable},
"an RSAPublicKey that is an INTEGER": {cmstest.Seq(rsaAlg, cmstest.BitString(cmstest.BigInt(n))), rsaKey, cms.NotVerifiable},
"a modulus in OCTETS": {cmstest.Seq(rsaAlg, key(cmstest.Octets(append([]byte{0}, n.Bytes()...)), cmstest.BigInt(e))), rsaKey, cms.NotVerifiable},
"an exponent in OCTETS": {cmstest.Seq(rsaAlg, key(cmstest.BigInt(n), cmstest.Octets(e.Bytes()))), rsaKey, cms.NotVerifiable},
"rsaEncryption without NULL": {cmstest.Seq(cmstest.AlgID(cmstest.OIDRSA), key(cmstest.BigInt(n), cmstest.BigInt(e))), rsaKey, cms.NotVerifiable},
"an RSA key of id-RSASSA-PSS": {cmstest.Seq(cmstest.AlgID(cmstest.OIDPSS, cmstest.Null()), key(cmstest.BigInt(n), cmstest.BigInt(e))), rsaKey, cms.NotVerifiable},
"an SPKI of three elements": {cmstest.Seq(rsaAlg, key(cmstest.BigInt(n), cmstest.BigInt(e)), cmstest.Null()), rsaKey, cms.NotVerifiable},
"an SPKI whose key is OCTETS": {cmstest.Seq(rsaAlg, cmstest.Octets(append([]byte{0}, cmstest.Seq(cmstest.BigInt(n), cmstest.BigInt(e))...))), rsaKey, cms.NotVerifiable},
"an SPKI whose algorithm is a SET": {cmstest.Seq(cmstest.TLV(0x31, cmstest.OID(cmstest.OIDRSA), cmstest.Null()), key(cmstest.BigInt(n), cmstest.BigInt(e))), rsaKey, cms.NotVerifiable},
"an SPKI of no algorithm": {cmstest.Seq(cmstest.Seq(cmstest.Int(1)), key(cmstest.BigInt(n), cmstest.BigInt(e))), rsaKey, cms.NotVerifiable},
"P-256": {cmstest.SPKIEC(cmstest.OID(cmstest.OIDP256), point), ecKey, cms.Valid},
"a compressed point": {cmstest.SPKICompressed(&ecKey.PublicKey), ecKey, cms.NotVerifiable},
"a point off the curve": {cmstest.SPKIEC(cmstest.OID(cmstest.OIDP256), off), ecKey, cms.NotVerifiable},
"a point of P-256 said P-384": {cmstest.SPKIEC(cmstest.OID(cmstest.OIDP384), point), ecKey, cms.NotVerifiable},
"brainpoolP256r1": {cmstest.SPKIEC(cmstest.OID(cmstest.OIDBrainpoolP256), point), ecKey, cms.NotVerifiable},
"a point of P-521, another curve": {cmstest.SPKIEC(cmstest.OID(cmstest.OIDBrainpoolP256), cmstest.Uncompressed(&p521Key.PublicKey)), p521Key, cms.NotVerifiable},
"id-ecPublicKey without a curve": {cmstest.Seq(cmstest.AlgID(cmstest.OIDECPublicKey), cmstest.BitString(point)), ecKey, cms.NotVerifiable},
"id-ecPublicKey with NULL": {cmstest.SPKIEC(cmstest.Null(), point), ecKey, cms.NotVerifiable},
"an EC key of id-ecDH": {cmstest.Seq(cmstest.AlgID(asn1.ObjectIdentifier{1, 3, 132, 1, 12}, cmstest.OID(cmstest.OIDP256)), cmstest.BitString(point)), ecKey, cms.NotVerifiable},
"a BIT STRING of 1 unused bit": {cmstest.Seq(ecAlg, cmstest.TLV(0x03, append([]byte{1}, evenPoint...))), even, cms.NotVerifiable},
"an empty BIT STRING": {cmstest.Seq(ecAlg, cmstest.TLV(0x03, []byte{0})), ecKey, cms.NotVerifiable},
} {
s := cmstest.NewCert(cmstest.CertSpec{CN: "Clave", SPKI: tc.spki}, tc.key)
if got := resultOf(t, name, cmstest.Signature(msg, cmstest.Options{}, s)); got != tc.want {
t.Errorf("%s: %v, want %v", name, got, tc.want)
}
}
}
// evenPointKey returns a P-256 key whose point ends in an even byte: a BIT
// STRING with one unused bit holds it in DER.
func evenPointKey() *ecdsa.PrivateKey {
for {
k := cmstest.ECKey(elliptic.P256())
if p := cmstest.Uncompressed(&k.PublicKey); p[len(p)-1]&1 == 0 {
return k
}
}
}
// Spec §29.11: the token in the order of its profile, form (S2), algorithms
// (S1) and verification (S3).
func TestTokenProfile(t *testing.T) {
subject := []byte("seal subject")
tok := func(o cmstest.TokenOptions, s cmstest.Signer) []byte { return cmstest.Token(subject, now, o, s) }
small := cmstest.NewCert(cmstest.CertSpec{CN: "TSA 1024"}, cmstest.RSAKey(1024))
compressed := cmstest.NewCert(cmstest.CertSpec{CN: "TSA comprimida", SPKI: cmstest.SPKICompressed(&ecKey2.PublicKey)}, ecKey2)
notYet := cmstest.NewCert(cmstest.CertSpec{CN: "TSA futura", From: now.Add(time.Second)}, ecKey2)
expired := cmstest.NewCert(cmstest.CertSpec{CN: "TSA caducada", To: now.Add(-time.Second)}, ecKey2)
exact := cmstest.NewCert(cmstest.CertSpec{CN: "TSA justa", From: now, To: now}, ecKey2)
badImprintAlg := func() []byte {
info := cmstest.Seq(cmstest.Int(1), cmstest.OID(asn1.ObjectIdentifier{1, 2, 3, 4}), cmstest.Seq(cmstest.Seq(cmstest.Int(1)), cmstest.Octets(make([]byte, 32))), cmstest.Int(42), cmstest.GeneralizedTimeOf(now))
return cmstest.TokenRaw(info, tsa)
}
for name, tc := range map[string]struct {
token []byte
form bool // S2, else S1
}{
"S1: an imprint of SHA-1": {tok(cmstest.TokenOptions{Hash: crypto.SHA1}, tsa), false},
"S1: a signature of SHA-1": {tok(cmstest.TokenOptions{CMS: cmstest.Options{Hash: crypto.SHA1}}, tsa), false},
"S1: a key of 1024 bits": {tok(cmstest.TokenOptions{}, small), false},
"S1: a compressed key": {tok(cmstest.TokenOptions{}, compressed), false},
"S1: PSS with trailerField": {tok(cmstest.TokenOptions{CMS: cmstest.Options{PSS: true, PSSTrailer: true}}, luis), false},
"S2 before S1: SHA-1 and version 2": {tok(cmstest.TokenOptions{Hash: crypto.SHA1, Version: 2}, tsa), true},
"S2 before S1: SHA-1 and no digest": {tok(cmstest.TokenOptions{Hash: crypto.SHA1, NoMessageDigest: true}, tsa), true},
"S2: an imprint algorithm that is no one": {badImprintAlg(), true},
} {
_, err := cms.ParseToken(tc.token)
if tc.form && !isForm(err) || !tc.form && !isAlgorithm(err) {
t.Errorf("%s: %v", name, err)
}
}
// S3: it reads, and does not verify.
for name, b := range map[string][]byte{
"the signature of the authority, a bit flipped": tok(cmstest.TokenOptions{CMS: cmstest.Options{CorruptSignature: true}}, tsa),
"the message-digest of another TSTInfo": tok(cmstest.TokenOptions{CMS: cmstest.Options{Message: []byte("other")}}, tsa),
"an imprint of 33 bytes": tok(cmstest.TokenOptions{Imprint: append(sha256Of(subject), 0)}, tsa),
"an imprint of 31 bytes": tok(cmstest.TokenOptions{Imprint: sha256Of(subject)[:31]}, tsa),
"another imprint": tok(cmstest.TokenOptions{Imprint: make([]byte, 32)}, tsa),
"an authority not yet valid": tok(cmstest.TokenOptions{}, notYet),
"an authority expired": tok(cmstest.TokenOptions{}, expired),
"an authority of PSS, a bit flipped": tok(cmstest.TokenOptions{CMS: cmstest.Options{PSS: true, CorruptSignature: true}}, luis),
} {
token, err := cms.ParseToken(b)
if err != nil || token.Check(subject) {
t.Errorf("%s: %v, valid %v", name, err, err == nil && token.Check(subject))
}
}
// Valid: at the first and the last instant of the validity of the
// authority, with RSA, PSS and SHA-512, and with the imprint of SHA-384,
// which only seal_type 2 refuses.
for name, b := range map[string][]byte{
"an authority valid exactly then": tok(cmstest.TokenOptions{}, exact),
"an authority of RSA": tok(cmstest.TokenOptions{}, luis),
"an authority of PSS": tok(cmstest.TokenOptions{CMS: cmstest.Options{PSS: true}}, luis),
"a signature of SHA-512": tok(cmstest.TokenOptions{CMS: cmstest.Options{Hash: crypto.SHA512}}, tsa),
} {
token, err := cms.ParseToken(b)
if err != nil || !token.Check(subject) || !token.ImprintIsSHA256() {
t.Errorf("%s: %v", name, err)
}
}
token, err := cms.ParseToken(tok(cmstest.TokenOptions{Hash: crypto.SHA384}, tsa))
if err != nil || !token.Check(subject) || token.ImprintIsSHA256() {
t.Errorf("an imprint of SHA-384: %v", err)
}
}
func sha256Of(b []byte) []byte {
h := crypto.SHA256.New()
h.Write(b)
return h.Sum(nil)
}

@ -1,7 +1,11 @@
package der
import (
"bytes"
"encoding/hex"
"encoding/json"
"os"
"path/filepath"
"testing"
"time"
)
@ -17,33 +21,62 @@ func TestCheck(t *testing.T) {
{"a length with a leading zero", "04820001" + "00", false},
{"a length of 128 with a leading zero", "04820080" + zeros(128), false},
{"an indefinite length", "30800000", false},
{"a length of 0xff", "04ff" + zeros(127), false},
{"a length of five bytes", "0485" + "0100000000" + "00", false},
{"a length of nine bytes that wraps around to 128", "0489" + "010000000000000080" + zeros(128), false},
{"a length whose bytes are missing", "0482" + "01", false},
{"a lone identifier octet", "04", false},
{"nothing", "", false},
{"a high tag number", "1f0100", false},
{"truncated", "0402aa", false},
{"trailing bytes", "0500" + "00", false},
{"BOOLEAN 01", "010101", false},
{"BOOLEAN 00", "010100", true},
{"BOOLEAN FF", "0101ff", true},
{"BOOLEAN of two bytes", "0102ffff", false},
{"INTEGER with a leading zero", "02020001", false},
{"INTEGER 0x80 with its zero", "02020080", true},
{"INTEGER with a leading FF", "0202ff80", false},
{"INTEGER -1 in two bytes", "0202ffff", false},
{"INTEGER -129", "0202ff7f", true},
{"empty INTEGER", "0200", false},
{"ENUMERATED with a leading zero", "0a020001", false},
{"NULL with content", "050100", false},
{"constructed OCTET STRING", "2404" + "0402aabb", false},
{"constructed INTEGER", "2203" + "020101", false},
{"BIT STRING with unused bits set", "03020701", false},
{"BIT STRING with unused bits clear", "03020780", true},
{"BIT STRING of 4 bits", "030204f0", true},
{"an empty BIT STRING", "030100", true},
{"a BIT STRING without its first octet", "0300", false},
{"a BIT STRING of eight unused bits", "03020800", false},
{"a BIT STRING of no bits with unused bits", "030101", false},
{"OID", "06032a0304", true},
{"OID with 0x80 inside a subidentifier", "0604" + "2a818001", true},
{"OID with a leading 0x80", "06038001" + "02", false},
{"OID that does not end", "06022a83", false},
{"an empty OID", "0600", false},
{"context tag, constructed", "a003020101", true},
{"context tag, primitive, any content", "8003000000", true},
{"SET in primitive form", "1100", false},
{"SEQUENCE in primitive form", "1000", false},
{"the end of contents", "0000", false},
{"a reserved universal tag", "0e0141", false},
{"a SEQUENCE of the end of contents", "30020000", false},
{"a SEQUENCE with a bad child", "3003" + "020000", false},
{"a SEQUENCE whose child does not fit", "3003" + "040500", false},
{"UTF8String", "0c026162", true},
{"UTF8String that is not UTF-8", "0c01ff", true},
{"PrintableString with an underscore", "13015f", true},
{"TeletexString", "1401e9", true},
{"IA5String", "160161", true},
{"VisibleString", "1a0161", true},
{"UniversalString", "1c0400000061", true},
{"BMPString", "1e020061", true},
// The other string types are DER too, whatever their content: a name
// may hold a NumericString, as the INN of a Russian certificate.
{"NumericString", "1204" + "31323334", true},
{"NumericString with a letter", "1201" + "41", true},
{"NumericString", "1204" + hexOf("1234"), true},
{"NumericString with a letter", "1201" + hexOf("A"), true},
{"VideotexString", "150141", true},
{"GraphicString", "190141", true},
{"GeneralString", "1b0141", true},
@ -52,18 +85,42 @@ func TestCheck(t *testing.T) {
{"REAL", "0900", false},
{"RELATIVE-OID", "0d0101", false},
// Times in the forms of DER (X.690 11.7, 11.8).
{"UTCTime", "170d" + hex.EncodeToString([]byte("250101120000Z")), true},
{"UTCTime without seconds", "170b" + hex.EncodeToString([]byte("2501011200Z")), false},
{"UTCTime with an offset", "1711" + hex.EncodeToString([]byte("250101120000+0100")), false},
{"UTCTime of 30 February", "170d" + hex.EncodeToString([]byte("250230120000Z")), false},
{"UTCTime with second 60", "170d" + hex.EncodeToString([]byte("250101235960Z")), false},
{"a UTCTime that is not a time", "170a" + hex.EncodeToString([]byte("not a time")), false},
{"GeneralizedTime", "180f" + hex.EncodeToString([]byte("20250101120000Z")), true},
{"GeneralizedTime with a fraction", "1812" + hex.EncodeToString([]byte("20250101120000.25Z")), true},
{"GeneralizedTime with a trailing zero", "1813" + hex.EncodeToString([]byte("20250101120000.250Z")), false},
{"GeneralizedTime with an empty fraction", "1810" + hex.EncodeToString([]byte("20250101120000.Z")), false},
{"GeneralizedTime without Z", "180e" + hex.EncodeToString([]byte("20250101120000")), false},
{"GeneralizedTime with a comma", "1812" + hex.EncodeToString([]byte("20250101120000,25Z")), false},
{"UTCTime", "170d" + hexOf("250101120000Z"), true},
{"an empty UTCTime", "1700", false},
{"UTCTime without seconds", "170b" + hexOf("2501011200Z"), false},
{"UTCTime with a digit more", "170e" + hexOf("2501011200001Z"), false},
{"UTCTime with an offset", "1711" + hexOf("250101120000+0100"), false},
{"UTCTime of 30 February", "170d" + hexOf("250230120000Z"), false},
{"UTCTime with second 60", "170d" + hexOf("250101235960Z"), false},
{"a UTCTime that is not a time", "170a" + hexOf("not a time"), false},
{"UTCTime with a slash in its seconds", "170d" + hexOf("2501011200/0Z"), false},
{"UTCTime with a colon in its day", "170d" + hexOf("25010:120000Z"), false},
{"GeneralizedTime", "180f" + hexOf("20250101120000Z"), true},
{"GeneralizedTime with a fraction", "1812" + hexOf("20250101120000.25Z"), true},
{"GeneralizedTime with a trailing zero", "1813" + hexOf("20250101120000.250Z"), false},
{"GeneralizedTime with an empty fraction", "1810" + hexOf("20250101120000.Z"), false},
{"GeneralizedTime with a letter in its fraction", "1812" + hexOf("20250101120000.2aZ"), false},
{"GeneralizedTime without Z", "180e" + hexOf("20250101120000"), false},
{"GeneralizedTime with a comma", "1812" + hexOf("20250101120000,25Z"), false},
{"GeneralizedTime without seconds", "180d" + hexOf("202501011200Z"), false},
{"GeneralizedTime with a slash in its seconds", "180f" + hexOf("202501011200/0Z"), false},
{"GeneralizedTime of month 0", "180f" + hexOf("20250001120000Z"), false},
{"GeneralizedTime of month 13", "180f" + hexOf("20251301120000Z"), false},
{"GeneralizedTime of day 0", "180f" + hexOf("20250100120000Z"), false},
{"GeneralizedTime of 31 April", "180f" + hexOf("20250431120000Z"), false},
{"GeneralizedTime of hour 24", "180f" + hexOf("20250101240000Z"), false},
{"GeneralizedTime of minute 60", "180f" + hexOf("20250101126000Z"), false},
{"GeneralizedTime of 29 February 2024", "180f" + hexOf("20240229235959Z"), true},
// What a check that is missing would let through: the last byte read
// as Z, a colon read as the digit 10, a slash read as a year, a tag of
// a high number read as one byte, and an indefinite length read as a
// long form.
{"UTCTime that ends in another letter", "170d" + hexOf("250101120000X"), false},
{"GeneralizedTime with a digit in the place of Z", "180f" + hexOf("202501011200000"), false},
{"GeneralizedTime with a colon in its day", "180f" + hexOf("2025010:120000Z"), false},
{"GeneralizedTime with a slash in its year", "180f" + hexOf("/0250101120000Z"), false},
{"a context tag of a high number", "9f0100", false},
{"an indefinite length and nothing after it", "3080", false},
} {
b, err := hex.DecodeString(tc.hex)
if err != nil {
@ -83,11 +140,13 @@ func zeros(n int) string {
return string(b)
}
func hexOf(s string) string { return hex.EncodeToString([]byte(s)) }
// The elements of a SET OF go in ascending order, and equal ones may repeat
// (X.690 11.6).
func TestSetOfSorted(t *testing.T) {
a, b := []byte{0x02, 0x01, 0x01}, []byte{0x02, 0x01, 0x02}
if !SetOfSorted([][]byte{a, b}) || SetOfSorted([][]byte{b, a}) || !SetOfSorted([][]byte{a, a, b}) || SetOfSorted([][]byte{a, b, a}) {
if !SetOfSorted([][]byte{a, b}) || SetOfSorted([][]byte{b, a}) || !SetOfSorted([][]byte{a, a, b}) || SetOfSorted([][]byte{a, b, a}) || !SetOfSorted(nil) {
t.Error("SetOfSorted")
}
}
@ -118,13 +177,23 @@ func TestParseTime(t *testing.T) {
{"\x17\x0d" + "491231235959Z", time.Date(2049, 12, 31, 23, 59, 59, 0, time.UTC), false},
{"\x17\x0d" + "500101000000Z", time.Date(1950, 1, 1, 0, 0, 0, 0, time.UTC), false},
{"\x18\x13" + "20240229120000.125Z", time.Date(2024, 2, 29, 12, 0, 0, 125e6, time.UTC), true},
{"\x18\x19" + "20240229120000.123456789Z", time.Date(2024, 2, 29, 12, 0, 0, 123456789, time.UTC), true},
{"\x18\x0f" + "19490101000000Z", time.Date(1949, 1, 1, 0, 0, 0, 0, time.UTC), false},
} {
got, frac, err := ParseTime([]byte(c.el))
if err != nil || !got.Equal(c.want) || frac != c.frac {
t.Errorf("%q: %v %v %v", c.el, got, frac, err)
}
}
for _, bad := range []string{"\x18\x0f" + "20230229120000Z", "\x04\x0d" + "491231235959Z", "\x17"} {
for _, bad := range []string{
"\x18\x0f" + "20230229120000Z",
"\x04\x0d" + "491231235959Z",
"\x04\x0f" + "20230228120000Z", // a GeneralizedTime in an OCTET STRING
"\x0c\x0d" + "491231235959Z", // a UTCTime in a UTF8String
"\x17",
"\x17\x0d" + "4912", // its content does not fit
"",
} {
if _, _, err := ParseTime([]byte(bad)); err == nil {
t.Errorf("%q: accepted", bad)
}
@ -140,4 +209,134 @@ func TestSplit(t *testing.T) {
if err != nil || id != 0x30 || len(kids) != 2 || len(kids[0]) != 3 || len(kids[1]) != 2 {
t.Errorf("%x %v %v", id, kids, err)
}
if c, err := Content(b); err != nil || !bytes.Equal(c, b[2:]) {
t.Errorf("Content: %x %v", c, err)
}
// Nothing, a primitive element, and constructed ones whose content does
// not fit.
for _, bad := range []string{"", "0400", "3005", "a005"} {
x, _ := hex.DecodeString(bad)
if _, _, err := Split(x); err == nil {
t.Errorf("Split(%q): no error", bad)
}
}
if _, err := Content([]byte{0x04, 0x05, 0x00}); err == nil {
t.Error("Content of a truncated element: no error")
}
if _, err := Content([]byte{0x04}); err == nil {
t.Error("Content of a lone identifier: no error")
}
}
// A child that does not fit in its parent is an error of Split, which must
// return, and quickly: the loop over the children advances by each header.
func TestSplitChildDoesNotFit(t *testing.T) {
done := make(chan error, 1)
go func() {
_, _, err := Split([]byte{0x30, 0x03, 0x04, 0x05, 0x00})
done <- err
}()
select {
case err := <-done:
if err == nil {
t.Error("a child that does not fit: no error")
}
case <-time.After(time.Second):
// The loop would never end, and fill the memory: stop here.
t.Error("Split does not return")
os.Exit(1)
}
}
// FuzzDERCheck checks that Check never panics, and that what it accepts
// Split and Content read without error, element by element: the children of
// a constructed element are its content exactly, a primitive one is not
// split, and a time of the universal class is read by ParseTime.
func FuzzDERCheck(f *testing.F) {
for _, s := range []string{
"30050201010500", "a003020101", "0603" + "2a0304", "170d" + hexOf("250101120000Z"),
"1812" + hexOf("20250101120000.25Z"), "30800000", "0489" + "010000000000000080",
} {
b, _ := hex.DecodeString(s)
f.Add(b)
}
for _, b := range vectorSeeds(f) {
f.Add(b)
}
f.Fuzz(func(t *testing.T, b []byte) {
if Check(b) != nil {
return
}
walk(t, b)
})
}
func walk(t *testing.T, b []byte) {
c, err := Content(b)
if err != nil {
t.Fatalf("Content of %x: %v", b, err)
}
if b[0]&0x20 == 0 {
if _, _, err := Split(b); err == nil {
t.Fatalf("Split of the primitive %x", b)
}
if b[0] == 0x17 || b[0] == 0x18 {
if _, _, err := ParseTime(b); err != nil {
t.Fatalf("ParseTime of %x: %v", b, err)
}
}
return
}
id, kids, err := Split(b)
if err != nil || id != b[0] || !bytes.Equal(bytes.Join(kids, nil), c) {
t.Fatalf("Split of %x: %x %x %v", b, id, kids, err)
}
for _, k := range kids {
walk(t, k)
}
}
// vectorSeeds returns the DER elements of the areas of security_cms.json: the
// outermost runs of bytes that Check accepts, which are the signatures and
// the tokens.
func vectorSeeds(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, derElements(area)...)
}
return out
}
// derElements returns the outermost runs of bytes of b that are one DER
// SEQUENCE of more than 127 bytes each.
func derElements(b []byte) [][]byte {
var out [][]byte
for i := 0; i+2 < len(b); i++ {
if b[i] != 0x30 || b[i+1] < 0x81 || b[i+1] > 0x83 {
continue
}
hl, cl, err := header(b[i:])
if err != nil || Check(b[i:i+hl+cl]) != nil {
continue
}
out = append(out, b[i:i+hl+cl])
i += hl + cl - 1
}
return out
}

Loading…
Cancel
Save

Powered by TurnKey Linux.