mirror of https://github.com/golang/go.git
crypto/x509: add RevocationList and CreateRevocationList
The existing Certificate.CreateCRL method generates non-conformant CRLs and
as such cannot be used for implementations that require standards
compliance. This change implements a new top level method, CreateCRL, which
generates compliant CRLs, and offers an extensible API if any
extensions/fields need to be supported in the future.
Here is an example Issuer/CRL generated using this change:
-----BEGIN CERTIFICATE-----
MIIBNjCB3aADAgECAgEWMAoGCCqGSM49BAMCMBIxEDAOBgNVBAMTB3Rlc3Rpbmcw
IhgPMDAwMTAxMDEwMDAwMDBaGA8wMDAxMDEwMTAwMDAwMFowEjEQMA4GA1UEAxMH
dGVzdGluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLHrudbSM36sn1VBrmm/
OfQTyEsI4tIUV1VmneOKHL9ENBGCiec4GhQm2SGnDT/sZy2bB3c3yozh/roS6cZJ
UZqjIDAeMA4GA1UdDwEB/wQEAwIBAjAMBgNVHQ4EBQQDAQIDMAoGCCqGSM49BAMC
A0gAMEUCIQCoAYN6CGZPgd5Sw5a1rd5VexciT5MCxTfXj+ZfJNfoiAIgQVCTB8AE
Nm2xset7+HOgtQYlKNw/rGd8cFcv5Y9aUzo=
-----END CERTIFICATE-----
-----BEGIN X509 CRL-----
MIHWMH0CAQEwCgYIKoZIzj0EAwIwEjEQMA4GA1UEAxMHdGVzdGluZxgPMDAwMTAx
MDIwMDAwMDBaGA8wMDAxMDEwMzAwMDAwMFowFjAUAgECGA8wMDAxMDEwMTAxMDAw
MFqgHjAcMA4GA1UdIwQHMAWAAwECAzAKBgNVHRQEAwIBBTAKBggqhkjOPQQDAgNJ
ADBGAiEAjqfj/IG4ys5WkjrbTNpDbr+saHGO/NujLJotlLL9KzgCIQDm8VZPzj0f
NYEQgAW4nsiUzlvEUCoHMw0141VCZXv67A==
-----END X509 CRL-----
Fixes #35428
Change-Id: Id96b6f47698d0bed39d586b46bd12374ee6ff88f
GitHub-Last-Rev: c83a601716
GitHub-Pull-Request: golang/go#36945
Reviewed-on: https://go-review.googlesource.com/c/go/+/217298
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Run-TryBot: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
67c2dcbc59
commit
5d47f870a6
|
|
@ -1633,6 +1633,7 @@ var (
|
|||
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 (
|
||||
|
|
@ -2202,6 +2203,9 @@ func ParseDERCRL(derBytes []byte) (*pkix.CertificateList, error) {
|
|||
|
||||
// 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 {
|
||||
|
|
@ -2649,3 +2653,141 @@ func parseCertificateRequest(in *certificateRequest) (*CertificateRequest, error
|
|||
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},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ package x509
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
|
|
@ -2293,3 +2294,290 @@ func TestPKCS1MismatchKeyFormat(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue