smx509: add new CRL parser, deprecate old one #40

This commit is contained in:
Sun Yimin 2025-02-27 10:22:19 +08:00 committed by GitHub
parent 9ba88a32a4
commit 3d4dd002a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 600 additions and 85 deletions

View File

@ -7,6 +7,7 @@ import (
"crypto/ed25519" "crypto/ed25519"
"crypto/elliptic" "crypto/elliptic"
"crypto/rsa" "crypto/rsa"
"crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/asn1" "encoding/asn1"
"encoding/pem" "encoding/pem"
@ -162,6 +163,23 @@ func parseAI(der cryptobyte.String) (pkix.AlgorithmIdentifier, error) {
return ai, nil return ai, nil
} }
func parseTime(der *cryptobyte.String) (time.Time, error) {
var t time.Time
switch {
case der.PeekASN1Tag(cryptobyte_asn1.UTCTime):
if !der.ReadASN1UTCTime(&t) {
return t, errors.New("x509: malformed UTCTime")
}
case der.PeekASN1Tag(cryptobyte_asn1.GeneralizedTime):
if !der.ReadASN1GeneralizedTime(&t) {
return t, errors.New("x509: malformed GeneralizedTime")
}
default:
return t, errors.New("x509: unsupported time format")
}
return t, nil
}
func parseValidity(der cryptobyte.String) (time.Time, time.Time, error) { func parseValidity(der cryptobyte.String) (time.Time, time.Time, error) {
extract := func() (time.Time, error) { extract := func() (time.Time, error) {
var t time.Time var t time.Time
@ -457,6 +475,26 @@ func parseSANExtension(der cryptobyte.String) (dnsNames, emailAddresses []string
return return
} }
func parseAuthorityKeyIdentifier(e pkix.Extension) ([]byte, error) {
// RFC 5280, Section 4.2.1.1
if e.Critical {
// Conforming CAs MUST mark this extension as non-critical
return nil, errors.New("x509: authority key identifier incorrectly marked critical")
}
val := cryptobyte.String(e.Value)
var akid cryptobyte.String
if !val.ReadASN1(&akid, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: invalid authority key identifier")
}
if akid.PeekASN1Tag(cryptobyte_asn1.Tag(0).ContextSpecific()) {
if !akid.ReadASN1(&akid, cryptobyte_asn1.Tag(0).ContextSpecific()) {
return nil, errors.New("x509: invalid authority key identifier")
}
return akid, nil
}
return nil, nil
}
func parseExtKeyUsageExtension(der cryptobyte.String) ([]ExtKeyUsage, []asn1.ObjectIdentifier, error) { func parseExtKeyUsageExtension(der cryptobyte.String) ([]ExtKeyUsage, []asn1.ObjectIdentifier, error) {
var extKeyUsages []ExtKeyUsage var extKeyUsages []ExtKeyUsage
var unknownUsages []asn1.ObjectIdentifier var unknownUsages []asn1.ObjectIdentifier
@ -1068,3 +1106,191 @@ func ParseCertificatePEM(data []byte) (*Certificate, error) {
} }
return ParseCertificate(block.Bytes) return ParseCertificate(block.Bytes)
} }
// The X.509 standards confusingly 1-indexed the version names, but 0-indexed
// the actual encoded version, so the version for X.509v2 is 1.
const x509v2Version = 1
// ParseRevocationList parses a X509 v2 [Certificate] Revocation List from the given
// ASN.1 DER data.
func ParseRevocationList(der []byte) (*RevocationList, error) {
rl := &RevocationList{}
input := cryptobyte.String(der)
// we read the SEQUENCE including length and tag bytes so that
// we can populate RevocationList.Raw, before unwrapping the
// SEQUENCE so it can be operated on
if !input.ReadASN1Element(&input, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed crl")
}
rl.Raw = input
if !input.ReadASN1(&input, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed crl")
}
var tbs cryptobyte.String
// do the same trick again as above to extract the raw
// bytes for Certificate.RawTBSCertificate
if !input.ReadASN1Element(&tbs, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed tbs crl")
}
rl.RawTBSRevocationList = tbs
if !tbs.ReadASN1(&tbs, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed tbs crl")
}
var version int
if !tbs.PeekASN1Tag(cryptobyte_asn1.INTEGER) {
return nil, errors.New("x509: unsupported crl version")
}
if !tbs.ReadASN1Integer(&version) {
return nil, errors.New("x509: malformed crl")
}
if version != x509v2Version {
return nil, fmt.Errorf("x509: unsupported crl version: %d", version)
}
var sigAISeq cryptobyte.String
if !tbs.ReadASN1(&sigAISeq, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed signature algorithm identifier")
}
// Before parsing the inner algorithm identifier, extract
// the outer algorithm identifier and make sure that they
// match.
var outerSigAISeq cryptobyte.String
if !input.ReadASN1(&outerSigAISeq, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed algorithm identifier")
}
if !bytes.Equal(outerSigAISeq, sigAISeq) {
return nil, errors.New("x509: inner and outer signature algorithm identifiers don't match")
}
sigAI, err := parseAI(sigAISeq)
if err != nil {
return nil, err
}
rl.SignatureAlgorithm = getSignatureAlgorithmFromAI(sigAI)
var signature asn1.BitString
if !input.ReadASN1BitString(&signature) {
return nil, errors.New("x509: malformed signature")
}
rl.Signature = signature.RightAlign()
var issuerSeq cryptobyte.String
if !tbs.ReadASN1Element(&issuerSeq, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed issuer")
}
rl.RawIssuer = issuerSeq
issuerRDNs, err := ParseName(issuerSeq)
if err != nil {
return nil, err
}
rl.Issuer.FillFromRDNSequence(issuerRDNs)
rl.ThisUpdate, err = parseTime(&tbs)
if err != nil {
return nil, err
}
if tbs.PeekASN1Tag(cryptobyte_asn1.GeneralizedTime) || tbs.PeekASN1Tag(cryptobyte_asn1.UTCTime) {
rl.NextUpdate, err = parseTime(&tbs)
if err != nil {
return nil, err
}
}
if tbs.PeekASN1Tag(cryptobyte_asn1.SEQUENCE) {
var revokedSeq cryptobyte.String
if !tbs.ReadASN1(&revokedSeq, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed crl")
}
for !revokedSeq.Empty() {
rce := x509.RevocationListEntry{}
var certSeq cryptobyte.String
if !revokedSeq.ReadASN1Element(&certSeq, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed crl")
}
rce.Raw = certSeq
if !certSeq.ReadASN1(&certSeq, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed crl")
}
rce.SerialNumber = new(big.Int)
if !certSeq.ReadASN1Integer(rce.SerialNumber) {
return nil, errors.New("x509: malformed serial number")
}
rce.RevocationTime, err = parseTime(&certSeq)
if err != nil {
return nil, err
}
var extensions cryptobyte.String
var present bool
if !certSeq.ReadOptionalASN1(&extensions, &present, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed extensions")
}
if present {
for !extensions.Empty() {
var extension cryptobyte.String
if !extensions.ReadASN1(&extension, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed extension")
}
ext, err := parseExtension(extension)
if err != nil {
return nil, err
}
if ext.Id.Equal(oidExtensionReasonCode) {
val := cryptobyte.String(ext.Value)
if !val.ReadASN1Enum(&rce.ReasonCode) {
return nil, fmt.Errorf("x509: malformed reasonCode extension")
}
}
rce.Extensions = append(rce.Extensions, ext)
}
}
rl.RevokedCertificateEntries = append(rl.RevokedCertificateEntries, rce)
rcDeprecated := pkix.RevokedCertificate{
SerialNumber: rce.SerialNumber,
RevocationTime: rce.RevocationTime,
Extensions: rce.Extensions,
}
rl.RevokedCertificates = append(rl.RevokedCertificates, rcDeprecated)
}
}
var extensions cryptobyte.String
var present bool
if !tbs.ReadOptionalASN1(&extensions, &present, cryptobyte_asn1.Tag(0).Constructed().ContextSpecific()) {
return nil, errors.New("x509: malformed extensions")
}
if present {
if !extensions.ReadASN1(&extensions, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed extensions")
}
for !extensions.Empty() {
var extension cryptobyte.String
if !extensions.ReadASN1(&extension, cryptobyte_asn1.SEQUENCE) {
return nil, errors.New("x509: malformed extension")
}
ext, err := parseExtension(extension)
if err != nil {
return nil, err
}
if ext.Id.Equal(oidExtensionAuthorityKeyId) {
rl.AuthorityKeyId, err = parseAuthorityKeyIdentifier(ext)
if err != nil {
return nil, err
}
} else if ext.Id.Equal(oidExtensionCRLNumber) {
value := cryptobyte.String(ext.Value)
rl.Number = new(big.Int)
if !value.ReadASN1Integer(rl.Number) {
return nil, errors.New("x509: malformed crl number")
}
}
rl.Extensions = append(rl.Extensions, ext)
}
}
return rl, nil
}

View File

@ -885,6 +885,7 @@ func checkSignature(algo SignatureAlgorithm, signed, signature []byte, publicKey
} }
// CheckCRLSignature checks that the signature in crl is from c. // CheckCRLSignature checks that the signature in crl is from c.
// Deprecated: Use RevocationList.CheckSignatureFrom instead.
func (c *Certificate) CheckCRLSignature(crl *pkix.CertificateList) error { func (c *Certificate) CheckCRLSignature(crl *pkix.CertificateList) error {
algo := getSignatureAlgorithmFromAI(crl.SignatureAlgorithm) algo := getSignatureAlgorithmFromAI(crl.SignatureAlgorithm)
return c.CheckSignature(algo, crl.TBSCertList.Raw, crl.SignatureValue.RightAlign()) return c.CheckSignature(algo, crl.TBSCertList.Raw, crl.SignatureValue.RightAlign())
@ -1641,11 +1642,15 @@ func toCertificate(in any) (*x509.Certificate, error) {
// encoded CRLs will appear where they should be DER encoded, so this function // encoded CRLs will appear where they should be DER encoded, so this function
// will transparently handle PEM encoding as long as there isn't any leading // will transparently handle PEM encoding as long as there isn't any leading
// garbage. // garbage.
//
// Deprecated: Use [ParseRevocationList] instead.
func ParseCRL(crlBytes []byte) (*pkix.CertificateList, error) { func ParseCRL(crlBytes []byte) (*pkix.CertificateList, error) {
return x509.ParseCRL(crlBytes) return x509.ParseCRL(crlBytes)
} }
// ParseDERCRL parses a DER encoded CRL from the given bytes. // ParseDERCRL parses a DER encoded CRL from the given bytes.
//
// Deprecated: Use [ParseRevocationList] instead.
func ParseDERCRL(derBytes []byte) (*pkix.CertificateList, error) { func ParseDERCRL(derBytes []byte) (*pkix.CertificateList, error) {
return x509.ParseDERCRL(derBytes) return x509.ParseDERCRL(derBytes)
} }
@ -1653,8 +1658,8 @@ func ParseDERCRL(derBytes []byte) (*pkix.CertificateList, error) {
// CreateCRL returns a DER encoded CRL, signed by this Certificate, that // CreateCRL returns a DER encoded CRL, signed by this Certificate, that
// contains the given list of revoked certificates. // contains the given list of revoked certificates.
// //
// Note: this method does not generate an RFC 5280 conformant X.509 v2 CRL. // Deprecated: this method does not generate an RFC 5280 conformant X.509 v2 CRL.
// To generate a standards compliant CRL, use CreateRevocationList instead. // To generate a standards compliant CRL, use [CreateRevocationList] instead.
func (c *Certificate) CreateCRL(rand io.Reader, priv any, revokedCerts []pkix.RevokedCertificate, now, expiry time.Time) (crlBytes []byte, err error) { func (c *Certificate) CreateCRL(rand io.Reader, priv any, revokedCerts []pkix.RevokedCertificate, now, expiry time.Time) (crlBytes []byte, err error) {
key, ok := priv.(crypto.Signer) key, ok := priv.(crypto.Signer)
if !ok { if !ok {
@ -2064,6 +2069,16 @@ func (c *CertificateRequest) CheckSignature() error {
return checkSignature(c.SignatureAlgorithm, c.RawTBSCertificateRequest, c.Signature, c.PublicKey, true) return checkSignature(c.SignatureAlgorithm, c.RawTBSCertificateRequest, c.Signature, c.PublicKey, true)
} }
type RevocationList x509.RevocationList
func (c *RevocationList) asX509() *x509.RevocationList {
return (*x509.RevocationList)(c)
}
func (c *RevocationList) ToX509() *x509.RevocationList {
return c.asX509()
}
// These structures reflect the ASN.1 structure of X.509 CRLs better than // These structures reflect the ASN.1 structure of X.509 CRLs better than
// the existing crypto/x509/pkix variants do. These mirror the existing // the existing crypto/x509/pkix variants do. These mirror the existing
// certificate structs in this file. // certificate structs in this file.
@ -2125,12 +2140,6 @@ func CreateRevocationList(rand io.Reader, template *x509.RevocationList, issuer
} }
var revokedCerts []pkix.RevokedCertificate var revokedCerts []pkix.RevokedCertificate
revokedCerts = make([]pkix.RevokedCertificate, len(template.RevokedCertificates))
for i, rc := range template.RevokedCertificates {
rc.RevocationTime = rc.RevocationTime.UTC()
revokedCerts[i] = rc
}
/*
// Only process the deprecated RevokedCertificates field if it is populated // Only process the deprecated RevokedCertificates field if it is populated
// and the new RevokedCertificateEntries field is not populated. // and the new RevokedCertificateEntries field is not populated.
if len(template.RevokedCertificates) > 0 && len(template.RevokedCertificateEntries) == 0 { if len(template.RevokedCertificates) > 0 && len(template.RevokedCertificateEntries) == 0 {
@ -2187,7 +2196,6 @@ func CreateRevocationList(rand io.Reader, template *x509.RevocationList, issuer
revokedCerts[i] = rc revokedCerts[i] = rc
} }
} }
*/
aki, err := asn1.Marshal(authKeyId{Id: issuer.SubjectKeyId}) aki, err := asn1.Marshal(authKeyId{Id: issuer.SubjectKeyId})
if err != nil { if err != nil {
@ -2252,3 +2260,22 @@ func CreateRevocationList(rand io.Reader, template *x509.RevocationList, issuer
SignatureValue: asn1.BitString{Bytes: signature, BitLength: len(signature) * 8}, SignatureValue: asn1.BitString{Bytes: signature, BitLength: len(signature) * 8},
}) })
} }
// CheckSignatureFrom verifies that the signature on rl is a valid signature
// from issuer.
func (rl *RevocationList) CheckSignatureFrom(parent *Certificate) error {
if parent.Version == 3 && !parent.BasicConstraintsValid ||
parent.BasicConstraintsValid && !parent.IsCA {
return x509.ConstraintViolationError{}
}
if parent.KeyUsage != 0 && parent.KeyUsage&KeyUsageCRLSign == 0 {
return x509.ConstraintViolationError{}
}
if parent.PublicKeyAlgorithm == UnknownPublicKeyAlgorithm {
return x509.ErrUnsupportedAlgorithm
}
return parent.CheckSignature(rl.SignatureAlgorithm, rl.RawTBSRevocationList, rl.Signature)
}

View File

@ -2414,6 +2414,57 @@ func TestCreateRevocationList(t *testing.T) {
NextUpdate: time.Time{}.Add(time.Hour * 48), NextUpdate: time.Time{}.Add(time.Hour * 48),
}, },
}, },
{
name: "valid, reason code",
key: sm2Priv,
issuer: &x509.Certificate{
KeyUsage: KeyUsageCRLSign,
Subject: pkix.Name{
CommonName: "testing",
},
SubjectKeyId: []byte{1, 2, 3},
},
template: &x509.RevocationList{
RevokedCertificateEntries: []x509.RevocationListEntry{
{
SerialNumber: big.NewInt(2),
RevocationTime: time.Time{}.Add(time.Hour),
ReasonCode: 1,
},
},
Number: big.NewInt(5),
ThisUpdate: time.Time{}.Add(time.Hour * 24),
NextUpdate: time.Time{}.Add(time.Hour * 48),
},
},
{
name: "valid, extra entry extension",
key: sm2Priv,
issuer: &x509.Certificate{
KeyUsage: KeyUsageCRLSign,
Subject: pkix.Name{
CommonName: "testing",
},
SubjectKeyId: []byte{1, 2, 3},
},
template: &x509.RevocationList{
RevokedCertificateEntries: []x509.RevocationListEntry{
{
SerialNumber: big.NewInt(2),
RevocationTime: time.Time{}.Add(time.Hour),
ExtraExtensions: []pkix.Extension{
{
Id: []int{2, 5, 29, 99},
Value: []byte{5, 0},
},
},
},
},
Number: big.NewInt(5),
ThisUpdate: time.Time{}.Add(time.Hour * 24),
NextUpdate: time.Time{}.Add(time.Hour * 48),
},
},
{ {
name: "valid, Ed25519 key", name: "valid, Ed25519 key",
key: ed25519Priv, key: ed25519Priv,
@ -2487,6 +2538,34 @@ func TestCreateRevocationList(t *testing.T) {
}, },
}, },
}, },
{
name: "valid, deprecated entries with extension",
key: sm2Priv,
issuer: &x509.Certificate{
KeyUsage: KeyUsageCRLSign,
Subject: pkix.Name{
CommonName: "testing",
},
SubjectKeyId: []byte{1, 2, 3},
},
template: &x509.RevocationList{
RevokedCertificates: []pkix.RevokedCertificate{
{
SerialNumber: big.NewInt(2),
RevocationTime: time.Time{}.Add(time.Hour),
Extensions: []pkix.Extension{
{
Id: []int{2, 5, 29, 99},
Value: []byte{5, 0},
},
},
},
},
Number: big.NewInt(5),
ThisUpdate: time.Time{}.Add(time.Hour * 24),
NextUpdate: time.Time{}.Add(time.Hour * 48),
},
},
{ {
name: "valid, empty list", name: "valid, empty list",
key: sm2Priv, key: sm2Priv,
@ -2533,24 +2612,45 @@ func TestCreateRevocationList(t *testing.T) {
return return
} }
parsedCRL, err := ParseDERCRL(crl) parsedCRL, err := ParseRevocationList(crl)
if err != nil { if err != nil {
t.Fatalf("Failed to parse generated CRL: %s", err) t.Fatalf("Failed to parse generated CRL: %s", err)
} }
if tc.template.SignatureAlgorithm != UnknownSignatureAlgorithm && if tc.template.SignatureAlgorithm != UnknownSignatureAlgorithm &&
parsedCRL.SignatureAlgorithm.Algorithm.Equal(signatureAlgorithmDetails[tc.template.SignatureAlgorithm].oid) { parsedCRL.SignatureAlgorithm != tc.template.SignatureAlgorithm {
t.Fatalf("SignatureAlgorithm mismatch: got %v; want %v.", parsedCRL.SignatureAlgorithm, t.Fatalf("SignatureAlgorithm mismatch: got %v; want %v.", parsedCRL.SignatureAlgorithm,
tc.template.SignatureAlgorithm) tc.template.SignatureAlgorithm)
} }
if len(tc.template.RevokedCertificates) > 0 {
if !reflect.DeepEqual(parsedCRL.TBSCertList.RevokedCertificates, tc.template.RevokedCertificates) { if !reflect.DeepEqual(parsedCRL.RevokedCertificates, tc.template.RevokedCertificates) {
t.Fatalf("RevokedCertificates mismatch: got %v; want %v.", t.Fatalf("RevokedCertificates mismatch: got %v; want %v.",
parsedCRL.TBSCertList.RevokedCertificates, tc.template.RevokedCertificates) parsedCRL.RevokedCertificates, tc.template.RevokedCertificates)
} }
} else {
if len(parsedCRL.TBSCertList.Extensions) != 2+len(tc.template.ExtraExtensions) { if len(parsedCRL.RevokedCertificateEntries) != len(tc.template.RevokedCertificateEntries) {
t.Fatalf("Generated CRL has wrong number of extensions, wanted: %d, got: %d", 2+len(tc.template.ExtraExtensions), len(parsedCRL.TBSCertList.Extensions)) t.Fatalf("RevokedCertificateEntries length mismatch: got %d; want %d.",
len(parsedCRL.RevokedCertificateEntries),
len(tc.template.RevokedCertificateEntries))
}
for i, rce := range parsedCRL.RevokedCertificateEntries {
expected := tc.template.RevokedCertificateEntries[i]
if rce.SerialNumber.Cmp(expected.SerialNumber) != 0 {
t.Fatalf("RevocationListEntry serial mismatch: got %d; want %d.",
rce.SerialNumber, expected.SerialNumber)
}
if !rce.RevocationTime.Equal(expected.RevocationTime) {
t.Fatalf("RevocationListEntry revocation time mismatch: got %v; want %v.",
rce.RevocationTime, expected.RevocationTime)
}
if rce.ReasonCode != expected.ReasonCode {
t.Fatalf("RevocationListEntry reason code mismatch: got %d; want %d.",
rce.ReasonCode, expected.ReasonCode)
}
}
}
if len(parsedCRL.Extensions) != 2+len(tc.template.ExtraExtensions) {
t.Fatalf("Generated CRL has wrong number of extensions, wanted: %d, got: %d", 2+len(tc.template.ExtraExtensions), len(parsedCRL.Extensions))
} }
expectedAKI, err := asn1.Marshal(authKeyId{Id: tc.issuer.SubjectKeyId}) expectedAKI, err := asn1.Marshal(authKeyId{Id: tc.issuer.SubjectKeyId})
if err != nil { if err != nil {
@ -2560,9 +2660,9 @@ func TestCreateRevocationList(t *testing.T) {
Id: oidExtensionAuthorityKeyId, Id: oidExtensionAuthorityKeyId,
Value: expectedAKI, Value: expectedAKI,
} }
if !reflect.DeepEqual(parsedCRL.TBSCertList.Extensions[0], akiExt) { if !reflect.DeepEqual(parsedCRL.Extensions[0], akiExt) {
t.Fatalf("Unexpected first extension: got %v, want %v", t.Fatalf("Unexpected first extension: got %v, want %v",
parsedCRL.TBSCertList.Extensions[0], akiExt) parsedCRL.Extensions[0], akiExt)
} }
expectedNum, err := asn1.Marshal(tc.template.Number) expectedNum, err := asn1.Marshal(tc.template.Number)
if err != nil { if err != nil {
@ -2572,18 +2672,57 @@ func TestCreateRevocationList(t *testing.T) {
Id: oidExtensionCRLNumber, Id: oidExtensionCRLNumber,
Value: expectedNum, Value: expectedNum,
} }
if !reflect.DeepEqual(parsedCRL.TBSCertList.Extensions[1], crlExt) { if !reflect.DeepEqual(parsedCRL.Extensions[1], crlExt) {
t.Fatalf("Unexpected second extension: got %v, want %v", t.Fatalf("Unexpected second extension: got %v, want %v",
parsedCRL.TBSCertList.Extensions[1], crlExt) parsedCRL.Extensions[1], crlExt)
} }
if len(parsedCRL.TBSCertList.Extensions[2:]) == 0 && len(tc.template.ExtraExtensions) == 0 { // With Go 1.19's updated RevocationList, we can now directly compare
// the RawSubject of the certificate to RawIssuer on the parsed CRL.
// However, this doesn't work with our hacked issuers above (that
// aren't parsed from a proper DER bundle but are instead manually
// constructed). Prefer RawSubject when it is set.
if len(tc.issuer.RawSubject) > 0 {
issuerSubj, err := subjectBytes(tc.issuer)
if err != nil {
t.Fatalf("failed to get issuer subject: %s", err)
}
if !bytes.Equal(issuerSubj, parsedCRL.RawIssuer) {
t.Fatalf("Unexpected issuer subject; wanted: %v, got: %v", hex.EncodeToString(issuerSubj), hex.EncodeToString(parsedCRL.RawIssuer))
}
} else {
// When we hack our custom Subject in the test cases above,
// we don't set the additional fields (such as Names) in the
// hacked issuer. Round-trip a parsing of pkix.Name so that
// we add these missing fields for the comparison.
issuerRDN := tc.issuer.Subject.ToRDNSequence()
var caIssuer pkix.Name
caIssuer.FillFromRDNSequence(&issuerRDN)
if !reflect.DeepEqual(caIssuer, parsedCRL.Issuer) {
t.Fatalf("Expected issuer.Subject, parsedCRL.Issuer to be the same; wanted: %#v, got: %#v", caIssuer, parsedCRL.Issuer)
}
}
if len(parsedCRL.Extensions[2:]) == 0 && len(tc.template.ExtraExtensions) == 0 {
// If we don't have anything to check return early so we don't // If we don't have anything to check return early so we don't
// hit a [] != nil false positive below. // hit a [] != nil false positive below.
return return
} }
if !reflect.DeepEqual(parsedCRL.TBSCertList.Extensions[2:], tc.template.ExtraExtensions) { if !reflect.DeepEqual(parsedCRL.Extensions[2:], tc.template.ExtraExtensions) {
t.Fatalf("Extensions mismatch: got %v; want %v.", t.Fatalf("Extensions mismatch: got %v; want %v.",
parsedCRL.TBSCertList.Extensions[2:], tc.template.ExtraExtensions) parsedCRL.Extensions[2:], tc.template.ExtraExtensions)
}
if tc.template.Number != nil && parsedCRL.Number == nil {
t.Fatalf("Generated CRL missing Number: got nil, want %s",
tc.template.Number.String())
}
if tc.template.Number != nil && tc.template.Number.Cmp(parsedCRL.Number) != 0 {
t.Fatalf("Generated CRL has wrong Number: got %s, want %s",
parsedCRL.Number.String(), tc.template.Number.String())
}
if !bytes.Equal(parsedCRL.AuthorityKeyId, tc.issuer.SubjectKeyId) {
t.Fatalf("Generated CRL has wrong AuthorityKeyId: got %x, want %x",
parsedCRL.AuthorityKeyId, tc.issuer.SubjectKeyId)
} }
}) })
} }
@ -3150,12 +3289,12 @@ func TestDisableSHA1ForCertOnly(t *testing.T) {
t.Fatalf("failed to generate test CRL: %s", err) t.Fatalf("failed to generate test CRL: %s", err)
} }
// TODO(rolandshoemaker): this should be ParseRevocationList once it lands // TODO(rolandshoemaker): this should be ParseRevocationList once it lands
crl, err := ParseCRL(crlDER) crl, err := ParseRevocationList(crlDER)
if err != nil { if err != nil {
t.Fatalf("failed to parse test CRL: %s", err) t.Fatalf("failed to parse test CRL: %s", err)
} }
if err = cert.CheckCRLSignature(crl); err != nil { if err = crl.CheckSignatureFrom(cert); err != nil {
t.Errorf("unexpected error: %s", err) t.Errorf("unexpected error: %s", err)
} }
@ -3174,6 +3313,129 @@ func TestDisableSHA1ForCertOnly(t *testing.T) {
} }
} }
func TestParseRevocationList(t *testing.T) {
derBytes := fromBase64(derCRLBase64)
certList, err := ParseRevocationList(derBytes)
if err != nil {
t.Errorf("error parsing: %s", err)
return
}
numCerts := len(certList.RevokedCertificateEntries)
numCertsDeprecated := len(certList.RevokedCertificateEntries)
expected := 88
if numCerts != expected || numCertsDeprecated != expected {
t.Errorf("bad number of revoked certificates. got: %d want: %d", numCerts, expected)
}
}
func TestRevocationListCheckSignatureFrom(t *testing.T) {
goodKey, err := sm2.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("failed to generate test key: %s", err)
}
badKey, err := sm2.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("failed to generate test key: %s", err)
}
tests := []struct {
name string
issuer *Certificate
err string
}{
{
name: "valid",
issuer: &Certificate{
Version: 3,
BasicConstraintsValid: true,
IsCA: true,
PublicKeyAlgorithm: ECDSA,
PublicKey: goodKey.Public(),
},
},
{
name: "valid, key usage set",
issuer: &Certificate{
Version: 3,
BasicConstraintsValid: true,
IsCA: true,
PublicKeyAlgorithm: ECDSA,
PublicKey: goodKey.Public(),
KeyUsage: KeyUsageCRLSign,
},
},
{
name: "invalid issuer, wrong key usage",
issuer: &Certificate{
Version: 3,
BasicConstraintsValid: true,
IsCA: true,
PublicKeyAlgorithm: ECDSA,
PublicKey: goodKey.Public(),
KeyUsage: KeyUsageCertSign,
},
err: "x509: invalid signature: parent certificate cannot sign this kind of certificate",
},
{
name: "invalid issuer, no basic constraints/ca",
issuer: &Certificate{
Version: 3,
PublicKeyAlgorithm: ECDSA,
PublicKey: goodKey.Public(),
},
err: "x509: invalid signature: parent certificate cannot sign this kind of certificate",
},
{
name: "invalid issuer, unsupported public key type",
issuer: &Certificate{
Version: 3,
BasicConstraintsValid: true,
IsCA: true,
PublicKeyAlgorithm: UnknownPublicKeyAlgorithm,
PublicKey: goodKey.Public(),
},
err: "x509: cannot verify signature: algorithm unimplemented",
},
{
name: "wrong key",
issuer: &Certificate{
Version: 3,
BasicConstraintsValid: true,
IsCA: true,
PublicKeyAlgorithm: ECDSA,
PublicKey: badKey.Public(),
},
err: "x509: SM2 verification failure",
},
}
crlIssuer := &Certificate{
BasicConstraintsValid: true,
IsCA: true,
PublicKeyAlgorithm: ECDSA,
PublicKey: goodKey.Public(),
KeyUsage: KeyUsageCRLSign,
SubjectKeyId: []byte{1, 2, 3},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
crlDER, err := CreateRevocationList(rand.Reader, &x509.RevocationList{Number: big.NewInt(1)}, crlIssuer, goodKey)
if err != nil {
t.Fatalf("failed to generate CRL: %s", err)
}
crl, err := ParseRevocationList(crlDER)
if err != nil {
t.Fatalf("failed to parse test CRL: %s", err)
}
err = crl.CheckSignatureFrom(tc.issuer)
if err != nil && err.Error() != tc.err {
t.Errorf("unexpected error: got %s, want %s", err, tc.err)
} else if err == nil && tc.err != "" {
t.Errorf("CheckSignatureFrom did not fail: want %s", tc.err)
}
})
}
}
func TestOmitEmptyExtensions(t *testing.T) { func TestOmitEmptyExtensions(t *testing.T) {
k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil { if err != nil {