Use forked crl x509 (#6204)

Use the new //crl/x509 library in the CA, to make handling the
ReasonCode of each CRL entry significantly easier. This also
allows us to log the reason code along with each serial in the
CRL.

Also, make a couple tiny tweaks to the //crl/x509 package that
were discovered to be useful while writing this change. These
include moving it to a //crl/crl_x509 directory so that it doesn't
have to be aliased at import time.

Fixes #6199
This commit is contained in:
Aaron Gable 2022-07-01 11:27:32 -07:00 committed by GitHub
parent 67e3aa9973
commit 63f7936ea2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 44 additions and 59 deletions

View File

@ -3,9 +3,6 @@ package ca
import ( import (
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -16,6 +13,7 @@ import (
capb "github.com/letsencrypt/boulder/ca/proto" capb "github.com/letsencrypt/boulder/ca/proto"
"github.com/letsencrypt/boulder/core" "github.com/letsencrypt/boulder/core"
corepb "github.com/letsencrypt/boulder/core/proto" corepb "github.com/letsencrypt/boulder/core/proto"
"github.com/letsencrypt/boulder/crl/crl_x509"
"github.com/letsencrypt/boulder/issuance" "github.com/letsencrypt/boulder/issuance"
blog "github.com/letsencrypt/boulder/log" blog "github.com/letsencrypt/boulder/log"
) )
@ -53,9 +51,9 @@ func NewCRLImpl(issuers []*issuance.Issuer, lifetime time.Duration, maxLogLen in
func (ci *crlImpl) GenerateCRL(stream capb.CRLGenerator_GenerateCRLServer) error { func (ci *crlImpl) GenerateCRL(stream capb.CRLGenerator_GenerateCRLServer) error {
var issuer *issuance.Issuer var issuer *issuance.Issuer
var template *x509.RevocationList var template *crl_x509.RevocationList
var shard int64 var shard int64
rcs := make([]pkix.RevokedCertificate, 0) rcs := make([]crl_x509.RevokedCertificate, 0)
for { for {
in, err := stream.Recv() in, err := stream.Recv()
@ -116,9 +114,11 @@ func (ci *crlImpl) GenerateCRL(stream capb.CRLGenerator_GenerateCRLServer) error
fmt.Fprintf(&builder, "Signing CRL: logID=[%s] entries=[", logID) fmt.Fprintf(&builder, "Signing CRL: logID=[%s] entries=[", logID)
} }
// TODO: Figure out how best to include the reason code here, since it's reason := 0
// slow/difficult to extract it from the already-encoded entry extension. if rcs[i].ReasonCode != nil {
fmt.Fprintf(&builder, "%x,", rcs[i].SerialNumber.Bytes()) reason = *rcs[i].ReasonCode
}
fmt.Fprintf(&builder, "%x:%d,", rcs[i].SerialNumber.Bytes(), reason)
if builder.Len() != ci.maxLogLen { if builder.Len() != ci.maxLogLen {
ci.log.AuditInfof("%s", builder) ci.log.AuditInfof("%s", builder)
@ -127,7 +127,7 @@ func (ci *crlImpl) GenerateCRL(stream capb.CRLGenerator_GenerateCRLServer) error
} }
template.RevokedCertificates = rcs template.RevokedCertificates = rcs
crlBytes, err := x509.CreateRevocationList( crlBytes, err := crl_x509.CreateRevocationList(
rand.Reader, rand.Reader,
template, template,
issuer.Cert.Certificate, issuer.Cert.Certificate,
@ -162,7 +162,7 @@ func (ci *crlImpl) GenerateCRL(stream capb.CRLGenerator_GenerateCRLServer) error
return nil return nil
} }
func (ci *crlImpl) metadataToTemplate(meta *capb.CRLMetadata) (*x509.RevocationList, error) { func (ci *crlImpl) metadataToTemplate(meta *capb.CRLMetadata) (*crl_x509.RevocationList, error) {
if meta.IssuerNameID == 0 || meta.ThisUpdate == 0 { if meta.IssuerNameID == 0 || meta.ThisUpdate == 0 {
return nil, errors.New("got incomplete metadata message") return nil, errors.New("got incomplete metadata message")
} }
@ -174,7 +174,7 @@ func (ci *crlImpl) metadataToTemplate(meta *capb.CRLMetadata) (*x509.RevocationL
number := big.NewInt(meta.ThisUpdate) number := big.NewInt(meta.ThisUpdate)
thisUpdate := time.Unix(0, meta.ThisUpdate) thisUpdate := time.Unix(0, meta.ThisUpdate)
return &x509.RevocationList{ return &crl_x509.RevocationList{
Number: number, Number: number,
ThisUpdate: thisUpdate, ThisUpdate: thisUpdate,
NextUpdate: thisUpdate.Add(-time.Second).Add(ci.lifetime), NextUpdate: thisUpdate.Add(-time.Second).Add(ci.lifetime),
@ -182,7 +182,7 @@ func (ci *crlImpl) metadataToTemplate(meta *capb.CRLMetadata) (*x509.RevocationL
} }
func (ci *crlImpl) entryToRevokedCertificate(entry *corepb.CRLEntry) (*pkix.RevokedCertificate, error) { func (ci *crlImpl) entryToRevokedCertificate(entry *corepb.CRLEntry) (*crl_x509.RevokedCertificate, error) {
serial, err := core.StringToSerial(entry.Serial) serial, err := core.StringToSerial(entry.Serial)
if err != nil { if err != nil {
return nil, err return nil, err
@ -193,29 +193,15 @@ func (ci *crlImpl) entryToRevokedCertificate(entry *corepb.CRLEntry) (*pkix.Revo
} }
revokedAt := time.Unix(0, entry.RevokedAt) revokedAt := time.Unix(0, entry.RevokedAt)
// RFC 5280 Section 5.3.1 says "the reason code CRL entry extension SHOULD be var reason *int
// absent instead of using the unspecified (0) reasonCode value.", so we make
// sure we only add this extension if we have a non-zero revocation reason.
var extensions []pkix.Extension
if entry.Reason != 0 { if entry.Reason != 0 {
reasonBytes, err := asn1.Marshal(asn1.Enumerated(entry.Reason)) reason = new(int)
if err != nil { *reason = int(entry.Reason)
return nil, err
}
extensions = []pkix.Extension{
// The Reason Code extension, as defined in RFC 5280 Section 5.3.1:
// https://datatracker.ietf.org/doc/html/rfc5280#section-5.3.1
{
Id: asn1.ObjectIdentifier{2, 5, 29, 21}, // id-ce-reasonCode
Value: reasonBytes,
},
}
} }
return &pkix.RevokedCertificate{ return &crl_x509.RevokedCertificate{
SerialNumber: serial, SerialNumber: serial,
RevocationTime: revokedAt, RevocationTime: revokedAt,
Extensions: extensions, ReasonCode: reason,
}, nil }, nil
} }

View File

@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package x509 parses X.509-encoded keys and certificates. // Package crl_x509 parses X.509-encoded certificate revocation lists.
package x509 package crl_x509
import ( import (
"bytes" "bytes"
@ -26,9 +26,8 @@ var (
oidExtensionReasonCode = []int{2, 5, 29, 21} oidExtensionReasonCode = []int{2, 5, 29, 21}
) )
// RevokedCertificate contains the fields used to create an entry in the revokedCertificates // RevokedCertificate represents an entry in the revokedCertificates sequence of
// list of a CRL. // a CRL.
// revoked certificates.
// NOTE: This type does not exist in upstream. // NOTE: This type does not exist in upstream.
type RevokedCertificate struct { type RevokedCertificate struct {
// SerialNumber represents the serial number of a revoked certificate. It is // SerialNumber represents the serial number of a revoked certificate. It is
@ -46,15 +45,16 @@ type RevokedCertificate struct {
// value of 0 represents a reasonCode extension containing enum value 0 (this // value of 0 represents a reasonCode extension containing enum value 0 (this
// SHOULD NOT happen, but can and does). // SHOULD NOT happen, but can and does).
ReasonCode *int ReasonCode *int
// Extensions contains all raw extensions parsed from the CRL entry, or all // When creating a CRL, ExtraExtensions should contain all extra extensions to
// extra extensions to add to the CRL entry. When creating a CRL, if // add to the CRL entry. If ExtraExtensions contains a reasonCode extension,
// Extensions contains a reasonCode extension, it will be ignored in favor of // it will be ignored in favor of the ReasonCode field above. When parsing a
// the ReasonCode field above. // CRL, ExtraExtensions contains all raw extensions parsed from the CRL entry,
Extensions []pkix.Extension // except for reasonCode which is represented by the ReasonCode field above.
ExtraExtensions []pkix.Extension
} }
// RevocationList contains the fields used to create an X.509 v2 Certificate // RevocationList represents a Certificate Revocation List (CRL) as specified
// Revocation list with CreateRevocationList. // by RFC 5280.
type RevocationList struct { type RevocationList struct {
// Raw, RawTBSRevocationList, and RawIssuer contain the raw bytes of the whole // Raw, RawTBSRevocationList, and RawIssuer contain the raw bytes of the whole
// CRL, the tbsCertList field, and the issuer field, respectively. They are // CRL, the tbsCertList field, and the issuer field, respectively. They are
@ -91,16 +91,15 @@ type RevocationList struct {
// revokedCertificates sequence will be omitted from the CRL entirely. // revokedCertificates sequence will be omitted from the CRL entirely.
RevokedCertificates []RevokedCertificate RevokedCertificates []RevokedCertificate
// Number represents the X.509 v2 CRLNumber extension, which should be a // Number represents the CRLNumber extension, which should be a monotonically
// monotonically increasing sequence number for a given CRL scope and CRL // increasing sequence number for a given CRL scope and CRL issuer. It is both
// issuer. It is both used when creating a CRL and populated when parsing a // used when creating a CRL and populated when parsing a CRL. When creating a
// CRL. When creating a CRL, it MUST NOT be nil, and MUST NOT be longer than // CRL, it MUST NOT be nil, and MUST NOT be longer than 20 bytes.
// 20 bytes.
Number *big.Int Number *big.Int
// AuthorityKeyId is populated from the X.509 v2 authorityKeyIdentifier // AuthorityKeyId is populated from the authorityKeyIdentifier extension in
// extension in the CRL. It is ignored when creating a CRL: the extension is // the CRL. It is ignored when creating a CRL: the extension is populated from
// populated from the issuer information instead. // the issuer information instead.
AuthorityKeyId []byte AuthorityKeyId []byte
// Extensions contains raw X.509 extensions. When creating a CRL, // Extensions contains raw X.509 extensions. When creating a CRL,
@ -243,7 +242,7 @@ func ParseRevocationList(der []byte) (*RevocationList, error) {
} }
continue continue
} }
rc.Extensions = append(rc.Extensions, ext) rc.ExtraExtensions = append(rc.ExtraExtensions, ext)
} }
} }
@ -337,7 +336,7 @@ func CreateRevocationList(rand io.Reader, template *RevocationList, issuer *x509
// Copy over any extra extensions, except for a Reason Code extension, // Copy over any extra extensions, except for a Reason Code extension,
// because we'll synthesize that ourselves to ensure it is correct. // because we'll synthesize that ourselves to ensure it is correct.
exts := make([]pkix.Extension, 0) exts := make([]pkix.Extension, 0)
for _, ext := range rc.Extensions { for _, ext := range rc.ExtraExtensions {
if ext.Id.Equal(oidExtensionReasonCode) { if ext.Id.Equal(oidExtensionReasonCode) {
continue continue
} }

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package x509 package crl_x509
import ( import (
"crypto" "crypto"
@ -223,7 +223,7 @@ func TestCreateRevocationList(t *testing.T) {
SerialNumber: big.NewInt(2), SerialNumber: big.NewInt(2),
RevocationTime: time.Time{}.Add(time.Hour), RevocationTime: time.Time{}.Add(time.Hour),
ReasonCode: &reasonKeyCompromise, ReasonCode: &reasonKeyCompromise,
Extensions: []pkix.Extension{ ExtraExtensions: []pkix.Extension{
{ {
Id: []int{1, 1}, Id: []int{1, 1},
Value: []byte{5, 0}, Value: []byte{5, 0},

View File

@ -1,7 +1,7 @@
// Copyright 2021 The Go Authors. All rights reserved. // Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package x509 package crl_x509
import ( import (
"crypto/x509/pkix" "crypto/x509/pkix"

View File

@ -1,7 +1,7 @@
// Copyright 2021 The Go Authors. All rights reserved. // Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package x509 package crl_x509
import ( import (
"encoding/asn1" "encoding/asn1"

View File

@ -3,7 +3,7 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package x509 parses X.509-encoded keys and certificates. // Package x509 parses X.509-encoded keys and certificates.
package x509 package crl_x509
import ( import (
"bytes" "bytes"

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package x509 package crl_x509
import ( import (
_ "crypto/sha256" _ "crypto/sha256"