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:
parent
67e3aa9973
commit
63f7936ea2
48
ca/crl.go
48
ca/crl.go
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
@ -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},
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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"
|
||||||
Loading…
Reference in New Issue