oidExtensionNameConstraints = []int{2, 5, 29, 30}
oidExtensionCRLDistributionPoints = []int{2, 5, 29, 31}
oidExtensionAuthorityInfoAccess = []int{1, 3, 6, 1, 5, 5, 7, 1, 1}
+ oidExtensionCRLNumber = []int{2, 5, 29, 20}
)
var (
// CreateCRL returns a DER encoded CRL, signed by this Certificate, that
// contains the given list of revoked certificates.
+//
+// Note: this method does not generate an RFC 5280 conformant X.509 v2 CRL.
+// To generate a standards compliant CRL, use CreateRevocationList instead.
func (c *Certificate) CreateCRL(rand io.Reader, priv interface{}, revokedCerts []pkix.RevokedCertificate, now, expiry time.Time) (crlBytes []byte, err error) {
key, ok := priv.(crypto.Signer)
if !ok {
func (c *CertificateRequest) CheckSignature() error {
return checkSignature(c.SignatureAlgorithm, c.RawTBSCertificateRequest, c.Signature, c.PublicKey)
}
+
+// RevocationList contains the fields used to create an X.509 v2 Certificate
+// Revocation list with CreateRevocationList.
+type RevocationList struct {
+ // SignatureAlgorithm is used to determine the signature algorithm to be
+ // used when signing the CRL. If 0 the default algorithm for the signing
+ // key will be used.
+ SignatureAlgorithm SignatureAlgorithm
+
+ // RevokedCertificates is used to populate the revokedCertificates
+ // sequence in the CRL, it may be empty. RevokedCertificates may be nil,
+ // in which case an empty CRL will be created.
+ RevokedCertificates []pkix.RevokedCertificate
+
+ // Number is used to populate the X.509 v2 cRLNumber extension in the CRL,
+ // which should be a monotonically increasing sequence number for a given
+ // CRL scope and CRL issuer.
+ Number *big.Int
+ // ThisUpdate is used to populate the thisUpdate field in the CRL, which
+ // indicates the issuance date of the CRL.
+ ThisUpdate time.Time
+ // NextUpdate is used to populate the nextUpdate field in the CRL, which
+ // indicates the date by which the next CRL will be issued. NextUpdate
+ // must be greater than ThisUpdate.
+ NextUpdate time.Time
+ // ExtraExtensions contains any additional extensions to add directly to
+ // the CRL.
+ ExtraExtensions []pkix.Extension
+}
+
+// CreateRevocationList creates a new X.509 v2 Certificate Revocation List,
+// according to RFC 5280, based on template.
+//
+// The CRL is signed by priv which should be the private key associated with
+// the public key in the issuer certificate.
+//
+// The issuer may not be nil, and the crlSign bit must be set in KeyUsage in
+// order to use it as a CRL issuer.
+//
+// The issuer distinguished name CRL field and authority key identifier
+// extension are populated using the issuer certificate. issuer must have
+// SubjectKeyId set.
+func CreateRevocationList(rand io.Reader, template *RevocationList, issuer *Certificate, priv crypto.Signer) ([]byte, error) {
+ if template == nil {
+ return nil, errors.New("x509: template can not be nil")
+ }
+ if issuer == nil {
+ return nil, errors.New("x509: issuer can not be nil")
+ }
+ if (issuer.KeyUsage & KeyUsageCRLSign) == 0 {
+ return nil, errors.New("x509: issuer must have the crlSign key usage bit set")
+ }
+ if len(issuer.SubjectKeyId) == 0 {
+ return nil, errors.New("x509: issuer certificate doesn't contain a subject key identifier")
+ }
+ if template.NextUpdate.Before(template.ThisUpdate) {
+ return nil, errors.New("x509: template.ThisUpdate is after template.NextUpdate")
+ }
+ if template.Number == nil {
+ return nil, errors.New("x509: template contains nil Number field")
+ }
+
+ hashFunc, signatureAlgorithm, err := signingParamsForPublicKey(priv.Public(), template.SignatureAlgorithm)
+ if err != nil {
+ return nil, err
+ }
+
+ // Force revocation times to UTC per RFC 5280.
+ revokedCertsUTC := make([]pkix.RevokedCertificate, len(template.RevokedCertificates))
+ for i, rc := range template.RevokedCertificates {
+ rc.RevocationTime = rc.RevocationTime.UTC()
+ revokedCertsUTC[i] = rc
+ }
+
+ aki, err := asn1.Marshal(authKeyId{Id: issuer.SubjectKeyId})
+ if err != nil {
+ return nil, err
+ }
+ crlNum, err := asn1.Marshal(template.Number)
+ if err != nil {
+ return nil, err
+ }
+
+ tbsCertList := pkix.TBSCertificateList{
+ Version: 1, // v2
+ Signature: signatureAlgorithm,
+ Issuer: issuer.Subject.ToRDNSequence(),
+ ThisUpdate: template.ThisUpdate.UTC(),
+ NextUpdate: template.NextUpdate.UTC(),
+ Extensions: []pkix.Extension{
+ {
+ Id: oidExtensionAuthorityKeyId,
+ Value: aki,
+ },
+ {
+ Id: oidExtensionCRLNumber,
+ Value: crlNum,
+ },
+ },
+ }
+ if len(revokedCertsUTC) > 0 {
+ tbsCertList.RevokedCertificates = revokedCertsUTC
+ }
+
+ if len(template.ExtraExtensions) > 0 {
+ tbsCertList.Extensions = append(tbsCertList.Extensions, template.ExtraExtensions...)
+ }
+
+ tbsCertListContents, err := asn1.Marshal(tbsCertList)
+ if err != nil {
+ return nil, err
+ }
+
+ input := tbsCertListContents
+ if hashFunc != 0 {
+ h := hashFunc.New()
+ h.Write(tbsCertListContents)
+ input = h.Sum(nil)
+ }
+ var signerOpts crypto.SignerOpts = hashFunc
+ if template.SignatureAlgorithm.isRSAPSS() {
+ signerOpts = &rsa.PSSOptions{
+ SaltLength: rsa.PSSSaltLengthEqualsHash,
+ Hash: hashFunc,
+ }
+ }
+
+ signature, err := priv.Sign(rand, input, signerOpts)
+ if err != nil {
+ return nil, err
+ }
+
+ return asn1.Marshal(pkix.CertificateList{
+ TBSCertList: tbsCertList,
+ SignatureAlgorithm: signatureAlgorithm,
+ SignatureValue: asn1.BitString{Bytes: signature, BitLength: len(signature) * 8},
+ })
+}
import (
"bytes"
+ "crypto"
"crypto/dsa"
"crypto/ecdsa"
"crypto/ed25519"
}
}
}
+
+func TestCreateRevocationList(t *testing.T) {
+ ec256Priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatalf("Failed to generate ECDSA P256 key: %s", err)
+ }
+ _, ed25519Priv, err := ed25519.GenerateKey(rand.Reader)
+ if err != nil {
+ t.Fatalf("Failed to generate Ed25519 key: %s", err)
+ }
+ tests := []struct {
+ name string
+ key crypto.Signer
+ issuer *Certificate
+ template *RevocationList
+ expectedError string
+ }{
+ {
+ name: "nil template",
+ key: ec256Priv,
+ issuer: nil,
+ template: nil,
+ expectedError: "x509: template can not be nil",
+ },
+ {
+ name: "nil issuer",
+ key: ec256Priv,
+ issuer: nil,
+ template: &RevocationList{},
+ expectedError: "x509: issuer can not be nil",
+ },
+ {
+ name: "issuer doesn't have crlSign key usage bit set",
+ key: ec256Priv,
+ issuer: &Certificate{
+ KeyUsage: KeyUsageCertSign,
+ },
+ template: &RevocationList{},
+ expectedError: "x509: issuer must have the crlSign key usage bit set",
+ },
+ {
+ name: "issuer missing SubjectKeyId",
+ key: ec256Priv,
+ issuer: &Certificate{
+ KeyUsage: KeyUsageCRLSign,
+ },
+ template: &RevocationList{},
+ expectedError: "x509: issuer certificate doesn't contain a subject key identifier",
+ },
+ {
+ name: "nextUpdate before thisUpdate",
+ key: ec256Priv,
+ issuer: &Certificate{
+ KeyUsage: KeyUsageCRLSign,
+ Subject: pkix.Name{
+ CommonName: "testing",
+ },
+ SubjectKeyId: []byte{1, 2, 3},
+ },
+ template: &RevocationList{
+ ThisUpdate: time.Time{}.Add(time.Hour),
+ NextUpdate: time.Time{},
+ },
+ expectedError: "x509: template.ThisUpdate is after template.NextUpdate",
+ },
+ {
+ name: "nil Number",
+ key: ec256Priv,
+ issuer: &Certificate{
+ KeyUsage: KeyUsageCRLSign,
+ Subject: pkix.Name{
+ CommonName: "testing",
+ },
+ SubjectKeyId: []byte{1, 2, 3},
+ },
+ template: &RevocationList{
+ ThisUpdate: time.Time{}.Add(time.Hour * 24),
+ NextUpdate: time.Time{}.Add(time.Hour * 48),
+ },
+ expectedError: "x509: template contains nil Number field",
+ },
+ {
+ name: "invalid signature algorithm",
+ key: ec256Priv,
+ issuer: &Certificate{
+ KeyUsage: KeyUsageCRLSign,
+ Subject: pkix.Name{
+ CommonName: "testing",
+ },
+ SubjectKeyId: []byte{1, 2, 3},
+ },
+ template: &RevocationList{
+ SignatureAlgorithm: SHA256WithRSA,
+ RevokedCertificates: []pkix.RevokedCertificate{
+ {
+ SerialNumber: big.NewInt(2),
+ RevocationTime: time.Time{}.Add(time.Hour),
+ },
+ },
+ Number: big.NewInt(5),
+ ThisUpdate: time.Time{}.Add(time.Hour * 24),
+ NextUpdate: time.Time{}.Add(time.Hour * 48),
+ },
+ expectedError: "x509: requested SignatureAlgorithm does not match private key type",
+ },
+ {
+ name: "valid",
+ key: ec256Priv,
+ issuer: &Certificate{
+ KeyUsage: KeyUsageCRLSign,
+ Subject: pkix.Name{
+ CommonName: "testing",
+ },
+ SubjectKeyId: []byte{1, 2, 3},
+ },
+ template: &RevocationList{
+ RevokedCertificates: []pkix.RevokedCertificate{
+ {
+ SerialNumber: big.NewInt(2),
+ RevocationTime: time.Time{}.Add(time.Hour),
+ },
+ },
+ Number: big.NewInt(5),
+ ThisUpdate: time.Time{}.Add(time.Hour * 24),
+ NextUpdate: time.Time{}.Add(time.Hour * 48),
+ },
+ },
+ {
+ name: "valid, Ed25519 key",
+ key: ed25519Priv,
+ issuer: &Certificate{
+ KeyUsage: KeyUsageCRLSign,
+ Subject: pkix.Name{
+ CommonName: "testing",
+ },
+ SubjectKeyId: []byte{1, 2, 3},
+ },
+ template: &RevocationList{
+ RevokedCertificates: []pkix.RevokedCertificate{
+ {
+ SerialNumber: big.NewInt(2),
+ RevocationTime: time.Time{}.Add(time.Hour),
+ },
+ },
+ Number: big.NewInt(5),
+ ThisUpdate: time.Time{}.Add(time.Hour * 24),
+ NextUpdate: time.Time{}.Add(time.Hour * 48),
+ },
+ },
+ {
+ name: "valid, non-default signature algorithm",
+ key: ec256Priv,
+ issuer: &Certificate{
+ KeyUsage: KeyUsageCRLSign,
+ Subject: pkix.Name{
+ CommonName: "testing",
+ },
+ SubjectKeyId: []byte{1, 2, 3},
+ },
+ template: &RevocationList{
+ SignatureAlgorithm: ECDSAWithSHA512,
+ RevokedCertificates: []pkix.RevokedCertificate{
+ {
+ SerialNumber: big.NewInt(2),
+ RevocationTime: time.Time{}.Add(time.Hour),
+ },
+ },
+ Number: big.NewInt(5),
+ ThisUpdate: time.Time{}.Add(time.Hour * 24),
+ NextUpdate: time.Time{}.Add(time.Hour * 48),
+ },
+ },
+ {
+ name: "valid, extra extension",
+ key: ec256Priv,
+ issuer: &Certificate{
+ KeyUsage: KeyUsageCRLSign,
+ Subject: pkix.Name{
+ CommonName: "testing",
+ },
+ SubjectKeyId: []byte{1, 2, 3},
+ },
+ template: &RevocationList{
+ RevokedCertificates: []pkix.RevokedCertificate{
+ {
+ SerialNumber: big.NewInt(2),
+ RevocationTime: time.Time{}.Add(time.Hour),
+ },
+ },
+ Number: big.NewInt(5),
+ ThisUpdate: time.Time{}.Add(time.Hour * 24),
+ NextUpdate: time.Time{}.Add(time.Hour * 48),
+ ExtraExtensions: []pkix.Extension{
+ {
+ Id: []int{2, 5, 29, 99},
+ Value: []byte{5, 0},
+ },
+ },
+ },
+ },
+ {
+ name: "valid, empty list",
+ key: ec256Priv,
+ issuer: &Certificate{
+ KeyUsage: KeyUsageCRLSign,
+ Subject: pkix.Name{
+ CommonName: "testing",
+ },
+ SubjectKeyId: []byte{1, 2, 3},
+ },
+ template: &RevocationList{
+ Number: big.NewInt(5),
+ ThisUpdate: time.Time{}.Add(time.Hour * 24),
+ NextUpdate: time.Time{}.Add(time.Hour * 48),
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ crl, err := CreateRevocationList(rand.Reader, tc.template, tc.issuer, tc.key)
+ if err != nil && tc.expectedError == "" {
+ t.Fatalf("CreateRevocationList failed unexpectedly: %s", err)
+ } else if err != nil && tc.expectedError != err.Error() {
+ t.Fatalf("CreateRevocationList failed unexpectedly, wanted: %s, got: %s", tc.expectedError, err)
+ } else if err == nil && tc.expectedError != "" {
+ t.Fatalf("CreateRevocationList didn't fail, expected: %s", tc.expectedError)
+ }
+ if tc.expectedError != "" {
+ return
+ }
+
+ parsedCRL, err := ParseDERCRL(crl)
+ if err != nil {
+ t.Fatalf("Failed to parse generated CRL: %s", err)
+ }
+
+ if tc.template.SignatureAlgorithm != UnknownSignatureAlgorithm &&
+ parsedCRL.SignatureAlgorithm.Algorithm.Equal(signatureAlgorithmDetails[tc.template.SignatureAlgorithm].oid) {
+ t.Fatalf("SignatureAlgorithm mismatch: got %v; want %v.", parsedCRL.SignatureAlgorithm,
+ tc.template.SignatureAlgorithm)
+ }
+
+ if !reflect.DeepEqual(parsedCRL.TBSCertList.RevokedCertificates, tc.template.RevokedCertificates) {
+ t.Fatalf("RevokedCertificates mismatch: got %v; want %v.",
+ parsedCRL.TBSCertList.RevokedCertificates, tc.template.RevokedCertificates)
+ }
+
+ if len(parsedCRL.TBSCertList.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.TBSCertList.Extensions))
+ }
+ expectedAKI, err := asn1.Marshal(authKeyId{Id: tc.issuer.SubjectKeyId})
+ if err != nil {
+ t.Fatalf("asn1.Marshal failed: %s", err)
+ }
+ akiExt := pkix.Extension{
+ Id: oidExtensionAuthorityKeyId,
+ Value: expectedAKI,
+ }
+ if !reflect.DeepEqual(parsedCRL.TBSCertList.Extensions[0], akiExt) {
+ t.Fatalf("Unexpected first extension: got %v, want %v",
+ parsedCRL.TBSCertList.Extensions[0], akiExt)
+ }
+ expectedNum, err := asn1.Marshal(tc.template.Number)
+ if err != nil {
+ t.Fatalf("asn1.Marshal failed: %s", err)
+ }
+ crlExt := pkix.Extension{
+ Id: oidExtensionCRLNumber,
+ Value: expectedNum,
+ }
+ if !reflect.DeepEqual(parsedCRL.TBSCertList.Extensions[1], crlExt) {
+ t.Fatalf("Unexpected second extension: got %v, want %v",
+ parsedCRL.TBSCertList.Extensions[1], crlExt)
+ }
+ if len(parsedCRL.TBSCertList.Extensions[2:]) == 0 && len(tc.template.ExtraExtensions) == 0 {
+ // If we don't have anything to check return early so we don't
+ // hit a [] != nil false positive below.
+ return
+ }
+ if !reflect.DeepEqual(parsedCRL.TBSCertList.Extensions[2:], tc.template.ExtraExtensions) {
+ t.Fatalf("Extensions mismatch: got %v; want %v.",
+ parsedCRL.TBSCertList.Extensions[2:], tc.template.ExtraExtensions)
+ }
+ })
+ }
+}