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 (
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"io"
@ -16,6 +13,7 @@ import (
capb "github.com/letsencrypt/boulder/ca/proto"
"github.com/letsencrypt/boulder/core"
corepb "github.com/letsencrypt/boulder/core/proto"
"github.com/letsencrypt/boulder/crl/crl_x509"
"github.com/letsencrypt/boulder/issuance"
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 {
var issuer *issuance.Issuer
var template *x509.RevocationList
var template *crl_x509.RevocationList
var shard int64
rcs := make([]pkix.RevokedCertificate, 0)
rcs := make([]crl_x509.RevokedCertificate, 0)
for {
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)
}
// TODO: Figure out how best to include the reason code here, since it's
// slow/difficult to extract it from the already-encoded entry extension.
fmt.Fprintf(&builder, "%x,", rcs[i].SerialNumber.Bytes())
reason := 0
if rcs[i].ReasonCode != nil {
reason = *rcs[i].ReasonCode
}
fmt.Fprintf(&builder, "%x:%d,", rcs[i].SerialNumber.Bytes(), reason)
if builder.Len() != ci.maxLogLen {
ci.log.AuditInfof("%s", builder)
@ -127,7 +127,7 @@ func (ci *crlImpl) GenerateCRL(stream capb.CRLGenerator_GenerateCRLServer) error
}
template.RevokedCertificates = rcs
crlBytes, err := x509.CreateRevocationList(
crlBytes, err := crl_x509.CreateRevocationList(
rand.Reader,
template,
issuer.Cert.Certificate,
@ -162,7 +162,7 @@ func (ci *crlImpl) GenerateCRL(stream capb.CRLGenerator_GenerateCRLServer) error
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 {
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)
thisUpdate := time.Unix(0, meta.ThisUpdate)
return &x509.RevocationList{
return &crl_x509.RevocationList{
Number: number,
ThisUpdate: thisUpdate,
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)
if err != nil {
return nil, err
@ -193,29 +193,15 @@ func (ci *crlImpl) entryToRevokedCertificate(entry *corepb.CRLEntry) (*pkix.Revo
}
revokedAt := time.Unix(0, entry.RevokedAt)
// RFC 5280 Section 5.3.1 says "the reason code CRL entry extension SHOULD be
// 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
var reason *int
if entry.Reason != 0 {
reasonBytes, err := asn1.Marshal(asn1.Enumerated(entry.Reason))
if err != nil {
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,
},
}
reason = new(int)
*reason = int(entry.Reason)
}
return &pkix.RevokedCertificate{
return &crl_x509.RevokedCertificate{
SerialNumber: serial,
RevocationTime: revokedAt,
Extensions: extensions,
ReasonCode: reason,
}, nil
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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