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>
6 days ago
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|