Refactor CA configs for more modularity (#5087)
The CA is the only service which still defines its json config format in the package itself, rather than in its corresponding boulder-ca cmd package. This has allowed the CA's constructor interface to hide arbitrary complexity inside its first argument, the whole config blob. This change moves the CA's config to boulder-ca/main.go, to match the other Boulder components. In the process, it makes a host of other improvements: It refactors the issuance package to have a cleaner configuration interface. It also separates the config into a high-level profile (which applies equally to all issuers), and issuer-level profiles (which apply only to a single issuer). This does involve some code duplication, but that will be removed when CFSSL goes away. It adds helper functions to the issuance package to make it easier to construct a new issuer, and takes advantage of these in the boulder-ca package. As a result, the CA now receives fully-formed Issuers at construction time, rather than constructing them from nearly-complete configs during its own initialization. It adds a Linter struct to the lint package, so that an issuer can simply carry around a Linter, rather than a separate lint signing key and registry of lints to run. It makes CFSSL-specific code more clearly marked as such, making future removal easier and cleaner. Fixes #5070 Fixes #5076
This commit is contained in:
parent
f0b54a8968
commit
2d10cce1a3
138
ca/ca.go
138
ca/ca.go
|
@ -31,7 +31,6 @@ import (
|
|||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
|
||||
ca_config "github.com/letsencrypt/boulder/ca/config"
|
||||
capb "github.com/letsencrypt/boulder/ca/proto"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
corepb "github.com/letsencrypt/boulder/core/proto"
|
||||
|
@ -127,25 +126,25 @@ type issuerMaps struct {
|
|||
// CertificateAuthorityImpl represents a CA that signs certificates, CRLs, and
|
||||
// OCSP responses.
|
||||
type CertificateAuthorityImpl struct {
|
||||
rsaProfile string
|
||||
ecdsaProfile string
|
||||
issuers issuerMaps
|
||||
sa certificateStorage
|
||||
pa core.PolicyAuthority
|
||||
keyPolicy goodkey.KeyPolicy
|
||||
clk clock.Clock
|
||||
log blog.Logger
|
||||
issuers issuerMaps
|
||||
cfsslRSAProfile string
|
||||
cfsslECDSAProfile string
|
||||
prefix int // Prepended to the serial number
|
||||
validityPeriod time.Duration
|
||||
backdate time.Duration
|
||||
maxNames int
|
||||
ocspLifetime time.Duration
|
||||
keyPolicy goodkey.KeyPolicy
|
||||
orphanQueue *goque.Queue
|
||||
clk clock.Clock
|
||||
log blog.Logger
|
||||
signatureCount *prometheus.CounterVec
|
||||
csrExtensionCount *prometheus.CounterVec
|
||||
orphanCount *prometheus.CounterVec
|
||||
adoptedOrphanCount *prometheus.CounterVec
|
||||
signErrorCounter *prometheus.CounterVec
|
||||
orphanQueue *goque.Queue
|
||||
ocspLifetime time.Duration
|
||||
}
|
||||
|
||||
// Issuer represents a single issuer certificate, along with its key.
|
||||
|
@ -163,6 +162,7 @@ type localSigner interface {
|
|||
|
||||
// internalIssuer represents the fully initialized internal state for a single
|
||||
// issuer, including the cfssl signer and OCSP signer objects.
|
||||
// TODO(#5086): Remove the ocsp-specific pieces of this as we factor OCSP out.
|
||||
type internalIssuer struct {
|
||||
cert *x509.Certificate
|
||||
ocspSigner crypto.Signer
|
||||
|
@ -172,37 +172,27 @@ type internalIssuer struct {
|
|||
boulderIssuer *issuance.Issuer
|
||||
}
|
||||
|
||||
func makeInternalIssuers(configs []issuance.IssuerConfig, lifespanOCSP time.Duration) (issuerMaps, error) {
|
||||
func makeInternalIssuers(issuers []*issuance.Issuer, lifespanOCSP time.Duration) (issuerMaps, error) {
|
||||
issuersByAlg := make(map[x509.PublicKeyAlgorithm]*internalIssuer, 2)
|
||||
issuersByName := make(map[string]*internalIssuer, len(configs))
|
||||
issuersByID := make(map[int64]*internalIssuer, len(configs))
|
||||
for _, config := range configs {
|
||||
issuer, err := issuance.New(config)
|
||||
if err != nil {
|
||||
return issuerMaps{}, err
|
||||
}
|
||||
issuersByName := make(map[string]*internalIssuer, len(issuers))
|
||||
issuersByID := make(map[int64]*internalIssuer, len(issuers))
|
||||
for _, issuer := range issuers {
|
||||
ii := &internalIssuer{
|
||||
cert: config.Cert,
|
||||
ocspSigner: config.Signer,
|
||||
cert: issuer.Cert,
|
||||
ocspSigner: issuer.Signer,
|
||||
boulderIssuer: issuer,
|
||||
}
|
||||
if config.Profile.UseForRSALeaves {
|
||||
if issuersByAlg[x509.RSA] != nil {
|
||||
return issuerMaps{}, errors.New("Multiple issuer certs for RSA are not allowed")
|
||||
for _, alg := range issuer.Algs() {
|
||||
if issuersByAlg[alg] != nil {
|
||||
return issuerMaps{}, fmt.Errorf("Multiple issuer certs for %s are not allowed", alg)
|
||||
}
|
||||
issuersByAlg[x509.RSA] = ii
|
||||
issuersByAlg[alg] = ii
|
||||
}
|
||||
if config.Profile.UseForECDSALeaves {
|
||||
if issuersByAlg[x509.ECDSA] != nil {
|
||||
return issuerMaps{}, errors.New("Multiple issuer certs for ECDSA are not allowed")
|
||||
}
|
||||
issuersByAlg[x509.ECDSA] = ii
|
||||
}
|
||||
if issuersByName[config.Cert.Subject.CommonName] != nil {
|
||||
if issuersByName[issuer.Name()] != nil {
|
||||
return issuerMaps{}, errors.New("Multiple issuer certs with the same CommonName are not supported")
|
||||
}
|
||||
issuersByName[config.Cert.Subject.CommonName] = ii
|
||||
issuersByID[idForCert(config.Cert)] = ii
|
||||
issuersByName[issuer.Name()] = ii
|
||||
issuersByID[issuer.ID()] = ii
|
||||
}
|
||||
return issuerMaps{issuersByAlg, issuersByName, issuersByID}, nil
|
||||
}
|
||||
|
@ -262,38 +252,48 @@ func idForCert(cert *x509.Certificate) int64 {
|
|||
// from a single issuer (the first first in the issuers slice), and can sign OCSP
|
||||
// for any of the issuer certificates provided.
|
||||
func NewCertificateAuthorityImpl(
|
||||
config ca_config.CAConfig,
|
||||
sa certificateStorage,
|
||||
pa core.PolicyAuthority,
|
||||
clk clock.Clock,
|
||||
stats prometheus.Registerer,
|
||||
cfsslProfiles cfsslConfig.Config,
|
||||
cfsslRSAProfile string,
|
||||
cfsslECDSAProfile string,
|
||||
cfsslIssuers []Issuer,
|
||||
boulderIssuers []issuance.IssuerConfig,
|
||||
boulderIssuers []*issuance.Issuer,
|
||||
certExpiry time.Duration,
|
||||
certBackdate time.Duration,
|
||||
serialPrefix int,
|
||||
maxNames int,
|
||||
ocspLifetime time.Duration,
|
||||
keyPolicy goodkey.KeyPolicy,
|
||||
logger blog.Logger,
|
||||
orphanQueue *goque.Queue,
|
||||
logger blog.Logger,
|
||||
stats prometheus.Registerer,
|
||||
clk clock.Clock,
|
||||
) (*CertificateAuthorityImpl, error) {
|
||||
var ca *CertificateAuthorityImpl
|
||||
var err error
|
||||
|
||||
if config.SerialPrefix <= 0 || config.SerialPrefix >= 256 {
|
||||
// TODO(briansmith): Make the backdate setting mandatory after the
|
||||
// production ca.json has been updated to include it. Until then, manually
|
||||
// default to 1h, which is the backdating duration we currently use.
|
||||
if certBackdate == 0 {
|
||||
certBackdate = time.Hour
|
||||
}
|
||||
|
||||
if serialPrefix <= 0 || serialPrefix >= 256 {
|
||||
err = errors.New("Must have a positive non-zero serial prefix less than 256 for CA.")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var issuers issuerMaps
|
||||
// rsaProfile and ecdsaProfile are unused when using the boulder signer
|
||||
// instead of the CFSSL signer
|
||||
var rsaProfile, ecdsaProfile string
|
||||
if features.Enabled(features.NonCFSSLSigner) {
|
||||
issuers, err = makeInternalIssuers(boulderIssuers, config.LifespanOCSP.Duration)
|
||||
issuers, err = makeInternalIssuers(boulderIssuers, ocspLifetime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// CFSSL requires processing JSON configs through its own LoadConfig, so we
|
||||
// serialize and then deserialize.
|
||||
cfsslJSON, err := json.Marshal(config.CFSSL)
|
||||
cfsslJSON, err := json.Marshal(cfsslProfiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -302,7 +302,7 @@ func NewCertificateAuthorityImpl(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if config.LifespanOCSP.Duration == 0 {
|
||||
if ocspLifetime == 0 {
|
||||
return nil, errors.New("Config must specify an OCSP lifespan period.")
|
||||
}
|
||||
|
||||
|
@ -312,17 +312,12 @@ func NewCertificateAuthorityImpl(
|
|||
}
|
||||
}
|
||||
|
||||
issuers, err = makeCFSSLInternalIssuers(
|
||||
cfsslIssuers,
|
||||
cfsslConfigObj.Signing,
|
||||
config.LifespanOCSP.Duration)
|
||||
issuers, err = makeCFSSLInternalIssuers(cfsslIssuers, cfsslConfigObj.Signing, ocspLifetime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rsaProfile, ecdsaProfile = config.RSAProfile, config.ECDSAProfile
|
||||
|
||||
if rsaProfile == "" || ecdsaProfile == "" {
|
||||
if cfsslRSAProfile == "" || cfsslECDSAProfile == "" {
|
||||
return nil, errors.New("must specify rsaProfile and ecdsaProfile")
|
||||
}
|
||||
}
|
||||
|
@ -369,39 +364,24 @@ func NewCertificateAuthorityImpl(
|
|||
sa: sa,
|
||||
pa: pa,
|
||||
issuers: issuers,
|
||||
rsaProfile: rsaProfile,
|
||||
ecdsaProfile: ecdsaProfile,
|
||||
prefix: config.SerialPrefix,
|
||||
clk: clk,
|
||||
log: logger,
|
||||
cfsslRSAProfile: cfsslRSAProfile,
|
||||
cfsslECDSAProfile: cfsslECDSAProfile,
|
||||
validityPeriod: certExpiry,
|
||||
backdate: certBackdate,
|
||||
prefix: serialPrefix,
|
||||
maxNames: maxNames,
|
||||
ocspLifetime: ocspLifetime,
|
||||
keyPolicy: keyPolicy,
|
||||
orphanQueue: orphanQueue,
|
||||
log: logger,
|
||||
signatureCount: signatureCount,
|
||||
csrExtensionCount: csrExtensionCount,
|
||||
orphanCount: orphanCount,
|
||||
adoptedOrphanCount: adoptedOrphanCount,
|
||||
orphanQueue: orphanQueue,
|
||||
ocspLifetime: config.LifespanOCSP.Duration,
|
||||
signErrorCounter: signErrorCounter,
|
||||
clk: clk,
|
||||
}
|
||||
|
||||
if config.Expiry == "" {
|
||||
return nil, errors.New("Config must specify an expiry period.")
|
||||
}
|
||||
ca.validityPeriod, err = time.ParseDuration(config.Expiry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(briansmith): Make the backdate setting mandatory after the
|
||||
// production ca.json has been updated to include it. Until then, manually
|
||||
// default to 1h, which is the backdating duration we currently use.
|
||||
ca.backdate = config.Backdate.Duration
|
||||
if ca.backdate == 0 {
|
||||
ca.backdate = time.Hour
|
||||
}
|
||||
|
||||
ca.maxNames = config.MaxNames
|
||||
|
||||
return ca, nil
|
||||
}
|
||||
|
||||
|
@ -821,9 +801,9 @@ func (ca *CertificateAuthorityImpl) issuePrecertificateInner(ctx context.Context
|
|||
var profile string
|
||||
switch csr.PublicKey.(type) {
|
||||
case *rsa.PublicKey:
|
||||
profile = ca.rsaProfile
|
||||
profile = ca.cfsslRSAProfile
|
||||
case *ecdsa.PublicKey:
|
||||
profile = ca.ecdsaProfile
|
||||
profile = ca.cfsslECDSAProfile
|
||||
default:
|
||||
err = berrors.InternalServerError("unsupported key type %T", csr.PublicKey)
|
||||
ca.log.AuditErr(err.Error())
|
||||
|
|
515
ca/ca_test.go
515
ca/ca_test.go
|
@ -27,10 +27,9 @@ import (
|
|||
cttls "github.com/google/certificate-transparency-go/tls"
|
||||
"github.com/jmhodges/clock"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/zmap/zlint/v2/lint"
|
||||
zlint "github.com/zmap/zlint/v2/lint"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
|
||||
ca_config "github.com/letsencrypt/boulder/ca/config"
|
||||
capb "github.com/letsencrypt/boulder/ca/proto"
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
|
@ -39,6 +38,7 @@ import (
|
|||
"github.com/letsencrypt/boulder/features"
|
||||
"github.com/letsencrypt/boulder/goodkey"
|
||||
"github.com/letsencrypt/boulder/issuance"
|
||||
"github.com/letsencrypt/boulder/lint"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/metrics"
|
||||
"github.com/letsencrypt/boulder/policy"
|
||||
|
@ -132,14 +132,21 @@ func mustRead(path string) []byte {
|
|||
}
|
||||
|
||||
type testCtx struct {
|
||||
caConfig ca_config.CAConfig
|
||||
pa core.PolicyAuthority
|
||||
issuers []Issuer
|
||||
issuerConfigs []issuance.IssuerConfig
|
||||
keyPolicy goodkey.KeyPolicy
|
||||
fc clock.FakeClock
|
||||
stats prometheus.Registerer
|
||||
logger *blog.Mock
|
||||
pa core.PolicyAuthority
|
||||
certExpiry time.Duration
|
||||
certBackdate time.Duration
|
||||
serialPrefix int
|
||||
maxNames int
|
||||
ocspLifetime time.Duration
|
||||
cfsslProfiles cfsslConfig.Config
|
||||
cfsslRSAProfile string
|
||||
cfsslECDSAProfile string
|
||||
cfsslIssuers []Issuer
|
||||
boulderIssuers []*issuance.Issuer
|
||||
keyPolicy goodkey.KeyPolicy
|
||||
fc clock.FakeClock
|
||||
stats prometheus.Registerer
|
||||
logger *blog.Mock
|
||||
}
|
||||
|
||||
type mockSA struct {
|
||||
|
@ -194,94 +201,87 @@ func setup(t *testing.T) *testCtx {
|
|||
cfsslConfig.OID(OIDExtensionCTPoison),
|
||||
}
|
||||
|
||||
// Create a CA
|
||||
caConfig := ca_config.CAConfig{
|
||||
RSAProfile: rsaProfileName,
|
||||
ECDSAProfile: ecdsaProfileName,
|
||||
SerialPrefix: 17,
|
||||
Expiry: "8760h",
|
||||
// TODO(briansmith): When the defaulting of Backdate is removed, this
|
||||
// will need to be uncommented. Leave it commented for now to test the
|
||||
// defaulting logic.
|
||||
// Backdate: cmd.ConfigDuration{Duration: time.Hour},
|
||||
LifespanOCSP: cmd.ConfigDuration{Duration: 45 * time.Minute},
|
||||
MaxNames: 2,
|
||||
CFSSL: cfsslConfig.Config{
|
||||
Signing: &cfsslConfig.Signing{
|
||||
Profiles: map[string]*cfsslConfig.SigningProfile{
|
||||
rsaProfileName: {
|
||||
Usage: []string{"digital signature", "key encipherment", "server auth"},
|
||||
IssuerURL: []string{"http://not-example.com/issuer-url"},
|
||||
OCSP: "http://not-example.com/ocsp",
|
||||
CRL: "http://not-example.com/crl",
|
||||
cfsslProfiles := cfsslConfig.Config{
|
||||
Signing: &cfsslConfig.Signing{
|
||||
Profiles: map[string]*cfsslConfig.SigningProfile{
|
||||
rsaProfileName: {
|
||||
Usage: []string{"digital signature", "key encipherment", "server auth"},
|
||||
IssuerURL: []string{"http://not-example.com/issuer-url"},
|
||||
OCSP: "http://not-example.com/ocsp",
|
||||
CRL: "http://not-example.com/crl",
|
||||
|
||||
Policies: []cfsslConfig.CertificatePolicy{
|
||||
{
|
||||
ID: cfsslConfig.OID(asn1.ObjectIdentifier{2, 23, 140, 1, 2, 1}),
|
||||
},
|
||||
Policies: []cfsslConfig.CertificatePolicy{
|
||||
{
|
||||
ID: cfsslConfig.OID(asn1.ObjectIdentifier{2, 23, 140, 1, 2, 1}),
|
||||
},
|
||||
ExpiryString: "8760h",
|
||||
Backdate: time.Hour,
|
||||
CSRWhitelist: &cfsslConfig.CSRWhitelist{
|
||||
PublicKeyAlgorithm: true,
|
||||
PublicKey: true,
|
||||
SignatureAlgorithm: true,
|
||||
},
|
||||
ClientProvidesSerialNumbers: true,
|
||||
AllowedExtensions: allowedExtensions,
|
||||
},
|
||||
ecdsaProfileName: {
|
||||
Usage: []string{"digital signature", "server auth"},
|
||||
IssuerURL: []string{"http://not-example.com/issuer-url"},
|
||||
OCSP: "http://not-example.com/ocsp",
|
||||
CRL: "http://not-example.com/crl",
|
||||
|
||||
Policies: []cfsslConfig.CertificatePolicy{
|
||||
{
|
||||
ID: cfsslConfig.OID(asn1.ObjectIdentifier{2, 23, 140, 1, 2, 1}),
|
||||
},
|
||||
},
|
||||
ExpiryString: "8760h",
|
||||
Backdate: time.Hour,
|
||||
CSRWhitelist: &cfsslConfig.CSRWhitelist{
|
||||
PublicKeyAlgorithm: true,
|
||||
PublicKey: true,
|
||||
SignatureAlgorithm: true,
|
||||
},
|
||||
ClientProvidesSerialNumbers: true,
|
||||
AllowedExtensions: allowedExtensions,
|
||||
},
|
||||
},
|
||||
Default: &cfsslConfig.SigningProfile{
|
||||
ExpiryString: "8760h",
|
||||
Backdate: time.Hour,
|
||||
CSRWhitelist: &cfsslConfig.CSRWhitelist{
|
||||
PublicKeyAlgorithm: true,
|
||||
PublicKey: true,
|
||||
SignatureAlgorithm: true,
|
||||
},
|
||||
ClientProvidesSerialNumbers: true,
|
||||
AllowedExtensions: allowedExtensions,
|
||||
},
|
||||
ecdsaProfileName: {
|
||||
Usage: []string{"digital signature", "server auth"},
|
||||
IssuerURL: []string{"http://not-example.com/issuer-url"},
|
||||
OCSP: "http://not-example.com/ocsp",
|
||||
CRL: "http://not-example.com/crl",
|
||||
|
||||
Policies: []cfsslConfig.CertificatePolicy{
|
||||
{
|
||||
ID: cfsslConfig.OID(asn1.ObjectIdentifier{2, 23, 140, 1, 2, 1}),
|
||||
},
|
||||
},
|
||||
ExpiryString: "8760h",
|
||||
Backdate: time.Hour,
|
||||
CSRWhitelist: &cfsslConfig.CSRWhitelist{
|
||||
PublicKeyAlgorithm: true,
|
||||
PublicKey: true,
|
||||
SignatureAlgorithm: true,
|
||||
},
|
||||
ClientProvidesSerialNumbers: true,
|
||||
AllowedExtensions: allowedExtensions,
|
||||
},
|
||||
},
|
||||
Default: &cfsslConfig.SigningProfile{
|
||||
ExpiryString: "8760h",
|
||||
},
|
||||
},
|
||||
}
|
||||
cfsslIssuers := []Issuer{{caKey, caCert}}
|
||||
|
||||
issuers := []Issuer{{caKey, caCert}}
|
||||
|
||||
issuerConfigs := []issuance.IssuerConfig{
|
||||
{
|
||||
Cert: caCert,
|
||||
Signer: caKey,
|
||||
Clk: fc,
|
||||
Profile: issuance.ProfileConfig{
|
||||
UseForECDSALeaves: true,
|
||||
UseForRSALeaves: true,
|
||||
AllowMustStaple: true,
|
||||
AllowCTPoison: true,
|
||||
AllowSCTList: true,
|
||||
AllowCommonName: true,
|
||||
IssuerURL: "http://not-example.com/issuer-url",
|
||||
OCSPURL: "http://not-example.com/ocsp",
|
||||
CRLURL: "http://not-example.com/crl",
|
||||
Policies: []issuance.PolicyInformation{
|
||||
{OID: "2.23.140.1.2.1"},
|
||||
},
|
||||
MaxValidityPeriod: cmd.ConfigDuration{Duration: time.Hour * 8760},
|
||||
MaxValidityBackdate: cmd.ConfigDuration{Duration: time.Hour},
|
||||
boulderProfile, _ := issuance.NewProfile(
|
||||
issuance.ProfileConfig{
|
||||
AllowMustStaple: true,
|
||||
AllowCTPoison: true,
|
||||
AllowSCTList: true,
|
||||
AllowCommonName: true,
|
||||
Policies: []issuance.PolicyInformation{
|
||||
{OID: "2.23.140.1.2.1"},
|
||||
},
|
||||
MaxValidityPeriod: cmd.ConfigDuration{Duration: time.Hour * 8760},
|
||||
MaxValidityBackdate: cmd.ConfigDuration{Duration: time.Hour},
|
||||
},
|
||||
issuance.IssuerConfig{
|
||||
UseForECDSALeaves: true,
|
||||
UseForRSALeaves: true,
|
||||
IssuerURL: "http://not-example.com/issuer-url",
|
||||
OCSPURL: "http://not-example.com/ocsp",
|
||||
CRLURL: "http://not-example.com/crl",
|
||||
},
|
||||
)
|
||||
boulderLinter, _ := lint.NewLinter(caKey, nil)
|
||||
boulderIssuers := []*issuance.Issuer{
|
||||
{
|
||||
Cert: caCert,
|
||||
Signer: caKey,
|
||||
Profile: boulderProfile,
|
||||
Linter: boulderLinter,
|
||||
Clk: fc,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -291,35 +291,46 @@ func setup(t *testing.T) *testCtx {
|
|||
AllowECDSANISTP384: true,
|
||||
}
|
||||
|
||||
logger := blog.NewMock()
|
||||
|
||||
return &testCtx{
|
||||
caConfig,
|
||||
pa,
|
||||
issuers,
|
||||
issuerConfigs,
|
||||
keyPolicy,
|
||||
fc,
|
||||
metrics.NoopRegisterer,
|
||||
logger,
|
||||
pa: pa,
|
||||
certExpiry: 8760 * time.Hour,
|
||||
certBackdate: time.Hour,
|
||||
serialPrefix: 17,
|
||||
maxNames: 2,
|
||||
ocspLifetime: time.Hour,
|
||||
cfsslProfiles: cfsslProfiles,
|
||||
cfsslRSAProfile: rsaProfileName,
|
||||
cfsslECDSAProfile: ecdsaProfileName,
|
||||
cfsslIssuers: cfsslIssuers,
|
||||
boulderIssuers: boulderIssuers,
|
||||
keyPolicy: keyPolicy,
|
||||
fc: fc,
|
||||
stats: metrics.NoopRegisterer,
|
||||
logger: blog.NewMock(),
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailNoSerial(t *testing.T) {
|
||||
func TestFailNoSerialPrefix(t *testing.T) {
|
||||
testCtx := setup(t)
|
||||
|
||||
testCtx.caConfig.SerialPrefix = 0
|
||||
_, err := NewCertificateAuthorityImpl(
|
||||
testCtx.caConfig,
|
||||
nil,
|
||||
nil,
|
||||
testCtx.fc,
|
||||
testCtx.stats,
|
||||
testCtx.issuers,
|
||||
testCtx.cfsslProfiles,
|
||||
testCtx.cfsslRSAProfile,
|
||||
testCtx.cfsslECDSAProfile,
|
||||
testCtx.cfsslIssuers,
|
||||
nil,
|
||||
testCtx.certExpiry,
|
||||
testCtx.certBackdate,
|
||||
0,
|
||||
testCtx.maxNames,
|
||||
testCtx.ocspLifetime,
|
||||
testCtx.keyPolicy,
|
||||
nil,
|
||||
testCtx.logger,
|
||||
nil)
|
||||
testCtx.stats,
|
||||
testCtx.fc)
|
||||
test.AssertError(t, err, "CA should have failed with no SerialPrefix")
|
||||
}
|
||||
|
||||
|
@ -404,25 +415,32 @@ func TestIssuePrecertificate(t *testing.T) {
|
|||
func issueCertificateSubTestSetup(t *testing.T, boulderIssuer bool) (*CertificateAuthorityImpl, *mockSA) {
|
||||
testCtx := setup(t)
|
||||
sa := &mockSA{}
|
||||
var issuers []Issuer
|
||||
var issuerConfigs []issuance.IssuerConfig
|
||||
var cfsslIssuers []Issuer
|
||||
var boulderIssuers []*issuance.Issuer
|
||||
if boulderIssuer {
|
||||
issuerConfigs = testCtx.issuerConfigs
|
||||
boulderIssuers = testCtx.boulderIssuers
|
||||
_ = features.Set(map[string]bool{"NonCFSSLSigner": true})
|
||||
} else {
|
||||
issuers = testCtx.issuers
|
||||
cfsslIssuers = testCtx.cfsslIssuers
|
||||
}
|
||||
ca, err := NewCertificateAuthorityImpl(
|
||||
testCtx.caConfig,
|
||||
sa,
|
||||
testCtx.pa,
|
||||
testCtx.fc,
|
||||
testCtx.stats,
|
||||
issuers,
|
||||
issuerConfigs,
|
||||
testCtx.cfsslProfiles,
|
||||
testCtx.cfsslRSAProfile,
|
||||
testCtx.cfsslECDSAProfile,
|
||||
cfsslIssuers,
|
||||
boulderIssuers,
|
||||
testCtx.certExpiry,
|
||||
testCtx.certBackdate,
|
||||
testCtx.serialPrefix,
|
||||
testCtx.maxNames,
|
||||
testCtx.ocspLifetime,
|
||||
testCtx.keyPolicy,
|
||||
nil,
|
||||
testCtx.logger,
|
||||
nil)
|
||||
testCtx.stats,
|
||||
testCtx.fc)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
|
||||
return ca, sa
|
||||
|
@ -468,16 +486,23 @@ func TestMultipleIssuers(t *testing.T) {
|
|||
}
|
||||
sa := &mockSA{}
|
||||
ca, err := NewCertificateAuthorityImpl(
|
||||
testCtx.caConfig,
|
||||
sa,
|
||||
testCtx.pa,
|
||||
testCtx.fc,
|
||||
testCtx.stats,
|
||||
testCtx.cfsslProfiles,
|
||||
testCtx.cfsslRSAProfile,
|
||||
testCtx.cfsslECDSAProfile,
|
||||
newIssuers,
|
||||
nil,
|
||||
testCtx.certExpiry,
|
||||
testCtx.certBackdate,
|
||||
testCtx.serialPrefix,
|
||||
testCtx.maxNames,
|
||||
testCtx.ocspLifetime,
|
||||
testCtx.keyPolicy,
|
||||
nil,
|
||||
testCtx.logger,
|
||||
nil)
|
||||
testCtx.stats,
|
||||
testCtx.fc)
|
||||
test.AssertNotError(t, err, "Failed to remake CA")
|
||||
|
||||
issuedCert, err := ca.IssuePrecertificate(ctx, &capb.IssueCertificateRequest{Csr: CNandSANCSR, RegistrationID: arbitraryRegID})
|
||||
|
@ -494,16 +519,23 @@ func TestOCSP(t *testing.T) {
|
|||
testCtx := setup(t)
|
||||
sa := &mockSA{}
|
||||
ca, err := NewCertificateAuthorityImpl(
|
||||
testCtx.caConfig,
|
||||
sa,
|
||||
testCtx.pa,
|
||||
testCtx.fc,
|
||||
testCtx.stats,
|
||||
testCtx.issuers,
|
||||
testCtx.cfsslProfiles,
|
||||
testCtx.cfsslRSAProfile,
|
||||
testCtx.cfsslECDSAProfile,
|
||||
testCtx.cfsslIssuers,
|
||||
nil,
|
||||
testCtx.certExpiry,
|
||||
testCtx.certBackdate,
|
||||
testCtx.serialPrefix,
|
||||
testCtx.maxNames,
|
||||
testCtx.ocspLifetime,
|
||||
testCtx.keyPolicy,
|
||||
nil,
|
||||
testCtx.logger,
|
||||
nil)
|
||||
testCtx.stats,
|
||||
testCtx.fc)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
|
||||
issueReq := capb.IssueCertificateRequest{Csr: CNandSANCSR, RegistrationID: arbitraryRegID}
|
||||
|
@ -546,16 +578,23 @@ func TestOCSP(t *testing.T) {
|
|||
},
|
||||
}
|
||||
ca, err = NewCertificateAuthorityImpl(
|
||||
testCtx.caConfig,
|
||||
sa,
|
||||
testCtx.pa,
|
||||
testCtx.fc,
|
||||
testCtx.stats,
|
||||
testCtx.cfsslProfiles,
|
||||
testCtx.cfsslRSAProfile,
|
||||
testCtx.cfsslECDSAProfile,
|
||||
newIssuers,
|
||||
nil,
|
||||
testCtx.certExpiry,
|
||||
testCtx.certBackdate,
|
||||
testCtx.serialPrefix,
|
||||
testCtx.maxNames,
|
||||
testCtx.ocspLifetime,
|
||||
testCtx.keyPolicy,
|
||||
nil,
|
||||
testCtx.logger,
|
||||
nil)
|
||||
testCtx.stats,
|
||||
testCtx.fc)
|
||||
test.AssertNotError(t, err, "Failed to remake CA")
|
||||
|
||||
// Now issue a new precert, signed by newIssuerCert
|
||||
|
@ -644,16 +683,23 @@ func TestInvalidCSRs(t *testing.T) {
|
|||
testCtx := setup(t)
|
||||
sa := &mockSA{}
|
||||
ca, err := NewCertificateAuthorityImpl(
|
||||
testCtx.caConfig,
|
||||
sa,
|
||||
testCtx.pa,
|
||||
testCtx.fc,
|
||||
testCtx.stats,
|
||||
testCtx.issuers,
|
||||
testCtx.cfsslProfiles,
|
||||
testCtx.cfsslRSAProfile,
|
||||
testCtx.cfsslECDSAProfile,
|
||||
testCtx.cfsslIssuers,
|
||||
nil,
|
||||
testCtx.certExpiry,
|
||||
testCtx.certBackdate,
|
||||
testCtx.serialPrefix,
|
||||
testCtx.maxNames,
|
||||
testCtx.ocspLifetime,
|
||||
testCtx.keyPolicy,
|
||||
nil,
|
||||
testCtx.logger,
|
||||
nil)
|
||||
testCtx.stats,
|
||||
testCtx.fc)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
|
@ -676,16 +722,23 @@ func TestRejectValidityTooLong(t *testing.T) {
|
|||
testCtx := setup(t)
|
||||
sa := &mockSA{}
|
||||
ca, err := NewCertificateAuthorityImpl(
|
||||
testCtx.caConfig,
|
||||
sa,
|
||||
testCtx.pa,
|
||||
testCtx.fc,
|
||||
testCtx.stats,
|
||||
testCtx.issuers,
|
||||
testCtx.cfsslProfiles,
|
||||
testCtx.cfsslRSAProfile,
|
||||
testCtx.cfsslECDSAProfile,
|
||||
testCtx.cfsslIssuers,
|
||||
nil,
|
||||
testCtx.certExpiry,
|
||||
testCtx.certBackdate,
|
||||
testCtx.serialPrefix,
|
||||
testCtx.maxNames,
|
||||
testCtx.ocspLifetime,
|
||||
testCtx.keyPolicy,
|
||||
nil,
|
||||
testCtx.logger,
|
||||
nil)
|
||||
testCtx.stats,
|
||||
testCtx.fc)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
|
||||
// This time is a few minutes before the notAfter in testdata/ca_cert.pem
|
||||
|
@ -704,44 +757,47 @@ func TestSingleAIAEnforcement(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Couldn't create PA")
|
||||
|
||||
_, err = NewCertificateAuthorityImpl(
|
||||
ca_config.CAConfig{
|
||||
SerialPrefix: 1,
|
||||
LifespanOCSP: cmd.ConfigDuration{Duration: time.Second},
|
||||
CFSSL: cfsslConfig.Config{
|
||||
Signing: &cfsslConfig.Signing{
|
||||
Profiles: map[string]*cfsslConfig.SigningProfile{
|
||||
rsaProfileName: {
|
||||
IssuerURL: []string{"http://not-example.com/issuer-url", "bad"},
|
||||
Usage: []string{"digital signature", "key encipherment", "server auth"},
|
||||
OCSP: "http://not-example.com/ocsp",
|
||||
CRL: "http://not-example.com/crl",
|
||||
Policies: []cfsslConfig.CertificatePolicy{
|
||||
{
|
||||
ID: cfsslConfig.OID(asn1.ObjectIdentifier{2, 23, 140, 1, 2, 1}),
|
||||
},
|
||||
&mockSA{},
|
||||
pa,
|
||||
cfsslConfig.Config{
|
||||
Signing: &cfsslConfig.Signing{
|
||||
Profiles: map[string]*cfsslConfig.SigningProfile{
|
||||
rsaProfileName: {
|
||||
IssuerURL: []string{"http://not-example.com/issuer-url", "bad"},
|
||||
Usage: []string{"digital signature", "key encipherment", "server auth"},
|
||||
OCSP: "http://not-example.com/ocsp",
|
||||
CRL: "http://not-example.com/crl",
|
||||
Policies: []cfsslConfig.CertificatePolicy{
|
||||
{
|
||||
ID: cfsslConfig.OID(asn1.ObjectIdentifier{2, 23, 140, 1, 2, 1}),
|
||||
},
|
||||
ExpiryString: "8760h",
|
||||
Backdate: time.Hour,
|
||||
CSRWhitelist: &cfsslConfig.CSRWhitelist{
|
||||
PublicKeyAlgorithm: true,
|
||||
PublicKey: true,
|
||||
SignatureAlgorithm: true,
|
||||
},
|
||||
ClientProvidesSerialNumbers: true,
|
||||
},
|
||||
ExpiryString: "8760h",
|
||||
Backdate: time.Hour,
|
||||
CSRWhitelist: &cfsslConfig.CSRWhitelist{
|
||||
PublicKeyAlgorithm: true,
|
||||
PublicKey: true,
|
||||
SignatureAlgorithm: true,
|
||||
},
|
||||
ClientProvidesSerialNumbers: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&mockSA{},
|
||||
pa,
|
||||
clock.New(),
|
||||
metrics.NoopRegisterer,
|
||||
rsaProfileName,
|
||||
ecdsaProfileName,
|
||||
nil,
|
||||
nil,
|
||||
8760*time.Hour,
|
||||
time.Hour,
|
||||
1,
|
||||
1,
|
||||
time.Second,
|
||||
goodkey.KeyPolicy{},
|
||||
&blog.Mock{},
|
||||
nil,
|
||||
&blog.Mock{},
|
||||
metrics.NoopRegisterer,
|
||||
clock.New(),
|
||||
)
|
||||
test.AssertError(t, err, "NewCertificateAuthorityImpl allowed a profile with multiple issuer_urls")
|
||||
test.AssertEquals(t, err.Error(), "only one issuer_url supported")
|
||||
|
@ -841,16 +897,23 @@ func TestIssueCertificateForPrecertificate(t *testing.T) {
|
|||
for _, nonCFSSL := range []bool{true, false} {
|
||||
_ = features.Set(map[string]bool{"NonCFSSLSigner": nonCFSSL})
|
||||
ca, err := NewCertificateAuthorityImpl(
|
||||
testCtx.caConfig,
|
||||
sa,
|
||||
testCtx.pa,
|
||||
testCtx.fc,
|
||||
testCtx.stats,
|
||||
testCtx.issuers,
|
||||
testCtx.issuerConfigs,
|
||||
testCtx.cfsslProfiles,
|
||||
testCtx.cfsslRSAProfile,
|
||||
testCtx.cfsslECDSAProfile,
|
||||
testCtx.cfsslIssuers,
|
||||
testCtx.boulderIssuers,
|
||||
testCtx.certExpiry,
|
||||
testCtx.certBackdate,
|
||||
testCtx.serialPrefix,
|
||||
testCtx.maxNames,
|
||||
testCtx.ocspLifetime,
|
||||
testCtx.keyPolicy,
|
||||
nil,
|
||||
testCtx.logger,
|
||||
nil)
|
||||
testCtx.stats,
|
||||
testCtx.fc)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
|
||||
issueReq := capb.IssueCertificateRequest{Csr: CNandSANCSR, RegistrationID: arbitraryRegID, OrderID: 0}
|
||||
|
@ -924,16 +987,23 @@ func TestIssueCertificateForPrecertificateDuplicateSerial(t *testing.T) {
|
|||
testCtx := setup(t)
|
||||
sa := &dupeSA{}
|
||||
ca, err := NewCertificateAuthorityImpl(
|
||||
testCtx.caConfig,
|
||||
sa,
|
||||
testCtx.pa,
|
||||
testCtx.fc,
|
||||
testCtx.stats,
|
||||
testCtx.issuers,
|
||||
testCtx.cfsslProfiles,
|
||||
testCtx.cfsslRSAProfile,
|
||||
testCtx.cfsslECDSAProfile,
|
||||
testCtx.cfsslIssuers,
|
||||
nil,
|
||||
testCtx.certExpiry,
|
||||
testCtx.certBackdate,
|
||||
testCtx.serialPrefix,
|
||||
testCtx.maxNames,
|
||||
testCtx.ocspLifetime,
|
||||
testCtx.keyPolicy,
|
||||
nil,
|
||||
testCtx.logger,
|
||||
nil)
|
||||
testCtx.stats,
|
||||
testCtx.fc)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
|
||||
sctBytes, err := makeSCTs()
|
||||
|
@ -961,16 +1031,23 @@ func TestIssueCertificateForPrecertificateDuplicateSerial(t *testing.T) {
|
|||
// for the duplicate.
|
||||
errorsa := &getCertErrorSA{}
|
||||
errorca, err := NewCertificateAuthorityImpl(
|
||||
testCtx.caConfig,
|
||||
errorsa,
|
||||
testCtx.pa,
|
||||
testCtx.fc,
|
||||
testCtx.stats,
|
||||
testCtx.issuers,
|
||||
testCtx.cfsslProfiles,
|
||||
testCtx.cfsslRSAProfile,
|
||||
testCtx.cfsslECDSAProfile,
|
||||
testCtx.cfsslIssuers,
|
||||
nil,
|
||||
testCtx.certExpiry,
|
||||
testCtx.certBackdate,
|
||||
testCtx.serialPrefix,
|
||||
testCtx.maxNames,
|
||||
testCtx.ocspLifetime,
|
||||
testCtx.keyPolicy,
|
||||
nil,
|
||||
testCtx.logger,
|
||||
nil)
|
||||
testCtx.stats,
|
||||
testCtx.fc)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
|
||||
_, err = errorca.IssueCertificateForPrecertificate(ctx, &capb.IssueCertificateForPrecertificateRequest{
|
||||
|
@ -1035,16 +1112,23 @@ func TestPrecertOrphanQueue(t *testing.T) {
|
|||
fakeNow := time.Date(2019, 9, 20, 0, 0, 0, 0, time.UTC)
|
||||
testCtx.fc.Set(fakeNow)
|
||||
ca, err := NewCertificateAuthorityImpl(
|
||||
testCtx.caConfig,
|
||||
qsa,
|
||||
testCtx.pa,
|
||||
testCtx.fc,
|
||||
testCtx.stats,
|
||||
testCtx.issuers,
|
||||
testCtx.cfsslProfiles,
|
||||
testCtx.cfsslRSAProfile,
|
||||
testCtx.cfsslECDSAProfile,
|
||||
testCtx.cfsslIssuers,
|
||||
nil,
|
||||
testCtx.certExpiry,
|
||||
testCtx.certBackdate,
|
||||
testCtx.serialPrefix,
|
||||
testCtx.maxNames,
|
||||
testCtx.ocspLifetime,
|
||||
testCtx.keyPolicy,
|
||||
orphanQueue,
|
||||
testCtx.logger,
|
||||
orphanQueue)
|
||||
testCtx.stats,
|
||||
testCtx.fc)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
|
||||
err = ca.integrateOrphan()
|
||||
|
@ -1098,16 +1182,23 @@ func TestOrphanQueue(t *testing.T) {
|
|||
}
|
||||
testCtx.fc.Set(fakeNow)
|
||||
ca, err := NewCertificateAuthorityImpl(
|
||||
testCtx.caConfig,
|
||||
qsa,
|
||||
testCtx.pa,
|
||||
testCtx.fc,
|
||||
testCtx.stats,
|
||||
testCtx.issuers,
|
||||
testCtx.cfsslProfiles,
|
||||
testCtx.cfsslRSAProfile,
|
||||
testCtx.cfsslECDSAProfile,
|
||||
testCtx.cfsslIssuers,
|
||||
nil,
|
||||
testCtx.certExpiry,
|
||||
testCtx.certBackdate,
|
||||
testCtx.serialPrefix,
|
||||
testCtx.maxNames,
|
||||
testCtx.ocspLifetime,
|
||||
testCtx.keyPolicy,
|
||||
orphanQueue,
|
||||
testCtx.logger,
|
||||
orphanQueue)
|
||||
testCtx.stats,
|
||||
testCtx.fc)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
|
||||
err = ca.integrateOrphan()
|
||||
|
@ -1211,16 +1302,23 @@ func TestIssuePrecertificateLinting(t *testing.T) {
|
|||
testCtx := setup(t)
|
||||
sa := &mockSA{}
|
||||
ca, err := NewCertificateAuthorityImpl(
|
||||
testCtx.caConfig,
|
||||
sa,
|
||||
testCtx.pa,
|
||||
testCtx.fc,
|
||||
testCtx.stats,
|
||||
testCtx.issuers,
|
||||
testCtx.cfsslProfiles,
|
||||
testCtx.cfsslRSAProfile,
|
||||
testCtx.cfsslECDSAProfile,
|
||||
testCtx.cfsslIssuers,
|
||||
nil,
|
||||
testCtx.certExpiry,
|
||||
testCtx.certBackdate,
|
||||
testCtx.serialPrefix,
|
||||
testCtx.maxNames,
|
||||
testCtx.ocspLifetime,
|
||||
testCtx.keyPolicy,
|
||||
nil,
|
||||
testCtx.logger,
|
||||
nil)
|
||||
testCtx.stats,
|
||||
testCtx.fc)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
|
||||
// Reconfigure the CA's cfsslSigner to be a linttrapSigner that always returns
|
||||
|
@ -1228,13 +1326,13 @@ func TestIssuePrecertificateLinting(t *testing.T) {
|
|||
rsaIssuer := ca.issuers.byAlg[x509.RSA]
|
||||
rsaIssuer.cfsslSigner = &linttrapSigner{
|
||||
lintErr: &local.LintError{
|
||||
ErrorResults: map[string]lint.LintResult{
|
||||
ErrorResults: map[string]zlint.LintResult{
|
||||
"foobar": {
|
||||
Status: lint.Error,
|
||||
Status: zlint.Error,
|
||||
Details: "foobar is error",
|
||||
},
|
||||
"foobar2": {
|
||||
Status: lint.Warn,
|
||||
Status: zlint.Warn,
|
||||
Details: "foobar2 is warning",
|
||||
},
|
||||
},
|
||||
|
@ -1268,16 +1366,23 @@ func TestGenerateOCSPWithIssuerID(t *testing.T) {
|
|||
sa := &mockSA{}
|
||||
_ = features.Set(map[string]bool{"StoreIssuerInfo": true})
|
||||
ca, err := NewCertificateAuthorityImpl(
|
||||
testCtx.caConfig,
|
||||
sa,
|
||||
testCtx.pa,
|
||||
testCtx.fc,
|
||||
testCtx.stats,
|
||||
testCtx.issuers,
|
||||
testCtx.cfsslProfiles,
|
||||
testCtx.cfsslRSAProfile,
|
||||
testCtx.cfsslECDSAProfile,
|
||||
testCtx.cfsslIssuers,
|
||||
nil,
|
||||
testCtx.certExpiry,
|
||||
testCtx.certBackdate,
|
||||
testCtx.serialPrefix,
|
||||
testCtx.maxNames,
|
||||
testCtx.ocspLifetime,
|
||||
testCtx.keyPolicy,
|
||||
nil,
|
||||
testCtx.logger,
|
||||
nil)
|
||||
testCtx.stats,
|
||||
testCtx.fc)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
|
||||
// GenerateOCSP with feature enabled + req contains bad IssuerID
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
package ca_config
|
||||
|
||||
import (
|
||||
cfsslConfig "github.com/cloudflare/cfssl/config"
|
||||
"github.com/letsencrypt/pkcs11key/v4"
|
||||
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/issuance"
|
||||
)
|
||||
|
||||
// CAConfig structs have configuration information for the certificate
|
||||
// authority, including database parameters as well as controls for
|
||||
// issued certificates.
|
||||
type CAConfig struct {
|
||||
cmd.ServiceConfig
|
||||
cmd.DBConfig
|
||||
cmd.HostnamePolicyConfig
|
||||
|
||||
GRPCCA *cmd.GRPCServerConfig
|
||||
GRPCOCSPGenerator *cmd.GRPCServerConfig
|
||||
|
||||
RSAProfile string
|
||||
ECDSAProfile string
|
||||
SerialPrefix int
|
||||
// Issuers contains configuration information for each issuer cert and key
|
||||
// this CA knows about. The first in the list is used as the default.
|
||||
Issuers []IssuerConfig
|
||||
// SignerProfile contains the signer issuance profile, if using the boulder
|
||||
// signer rather than the CFSSL signer.
|
||||
SignerProfile issuance.ProfileConfig
|
||||
// LifespanOCSP is how long OCSP responses are valid for; It should be longer
|
||||
// than the minTimeToExpiry field for the OCSP Updater.
|
||||
LifespanOCSP cmd.ConfigDuration
|
||||
// How long issued certificates are valid for, should match expiry field
|
||||
// in cfssl config.
|
||||
Expiry string
|
||||
// How far back certificates should be backdated, should match backdate
|
||||
// field in cfssl config.
|
||||
Backdate cmd.ConfigDuration
|
||||
// The maximum number of subjectAltNames in a single certificate
|
||||
MaxNames int
|
||||
CFSSL cfsslConfig.Config
|
||||
IgnoredLints []string
|
||||
|
||||
// WeakKeyFile is the path to a JSON file containing truncated RSA modulus
|
||||
// hashes of known easily enumerable keys.
|
||||
WeakKeyFile string
|
||||
|
||||
// BlockedKeyFile is the path to a YAML file containing Base64 encoded
|
||||
// SHA256 hashes of SubjectPublicKeyInfo's that should be considered
|
||||
// administratively blocked.
|
||||
BlockedKeyFile string
|
||||
|
||||
SAService *cmd.GRPCClientConfig
|
||||
|
||||
// Path to directory holding orphan queue files, if not provided an orphan queue
|
||||
// is not used.
|
||||
OrphanQueueDir string
|
||||
|
||||
Features map[string]bool
|
||||
}
|
||||
|
||||
// IssuerConfig contains info about an issuer: private key and issuer cert.
|
||||
// It should contain either a File path to a PEM-format private key,
|
||||
// or a PKCS11Config defining how to load a module for an HSM.
|
||||
type IssuerConfig struct {
|
||||
// A file from which a pkcs11key.Config will be read and parsed, if present
|
||||
ConfigFile string
|
||||
File string
|
||||
PKCS11 *pkcs11key.Config
|
||||
CertFile string
|
||||
// Number of sessions to open with the HSM. For maximum performance,
|
||||
// this should be equal to the number of cores in the HSM. Defaults to 1.
|
||||
NumSessions int
|
||||
}
|
|
@ -11,11 +11,11 @@ import (
|
|||
|
||||
"github.com/beeker1121/goque"
|
||||
|
||||
cfsslConfig "github.com/cloudflare/cfssl/config"
|
||||
"github.com/cloudflare/cfssl/helpers"
|
||||
pkcs11key "github.com/letsencrypt/pkcs11key/v4"
|
||||
|
||||
"github.com/letsencrypt/boulder/ca"
|
||||
ca_config "github.com/letsencrypt/boulder/ca/config"
|
||||
capb "github.com/letsencrypt/boulder/ca/proto"
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
|
@ -23,56 +23,113 @@ import (
|
|||
"github.com/letsencrypt/boulder/goodkey"
|
||||
bgrpc "github.com/letsencrypt/boulder/grpc"
|
||||
"github.com/letsencrypt/boulder/issuance"
|
||||
"github.com/letsencrypt/boulder/lint"
|
||||
"github.com/letsencrypt/boulder/policy"
|
||||
sapb "github.com/letsencrypt/boulder/sa/proto"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
CA ca_config.CAConfig
|
||||
CA struct {
|
||||
cmd.ServiceConfig
|
||||
cmd.DBConfig
|
||||
cmd.HostnamePolicyConfig
|
||||
|
||||
GRPCCA *cmd.GRPCServerConfig
|
||||
GRPCOCSPGenerator *cmd.GRPCServerConfig
|
||||
|
||||
SAService *cmd.GRPCClientConfig
|
||||
|
||||
// CFSSL contains CFSSL-specific configs as specified by that library.
|
||||
CFSSL cfsslConfig.Config
|
||||
// RSAProfile and ECDSAProfile name which of the profiles specified in the
|
||||
// CFSSL config should be used when issuing RSA and ECDSA certs, respectively.
|
||||
RSAProfile string
|
||||
ECDSAProfile string
|
||||
// Issuers contains configuration information for each issuer cert and key
|
||||
// this CA knows about. The first in the list is used as the default.
|
||||
// Only used by CFSSL.
|
||||
Issuers []IssuerConfig
|
||||
|
||||
// Issuance contains all information necessary to load and initialize non-CFSSL issuers.
|
||||
Issuance struct {
|
||||
Profile issuance.ProfileConfig
|
||||
Issuers []issuance.IssuerConfig
|
||||
IgnoredLints []string
|
||||
}
|
||||
|
||||
// How long issued certificates are valid for, should match expiry field
|
||||
// in cfssl config.
|
||||
Expiry cmd.ConfigDuration
|
||||
|
||||
// How far back certificates should be backdated, should match backdate
|
||||
// field in cfssl config.
|
||||
Backdate cmd.ConfigDuration
|
||||
|
||||
// What digits we should prepend to serials after randomly generating them.
|
||||
SerialPrefix int
|
||||
|
||||
// The maximum number of subjectAltNames in a single certificate
|
||||
MaxNames int
|
||||
|
||||
// LifespanOCSP is how long OCSP responses are valid for; It should be longer
|
||||
// than the minTimeToExpiry field for the OCSP Updater.
|
||||
LifespanOCSP cmd.ConfigDuration
|
||||
|
||||
// WeakKeyFile is the path to a JSON file containing truncated RSA modulus
|
||||
// hashes of known easily enumerable keys.
|
||||
WeakKeyFile string
|
||||
|
||||
// BlockedKeyFile is the path to a YAML file containing Base64 encoded
|
||||
// SHA256 hashes of SubjectPublicKeyInfo's that should be considered
|
||||
// administratively blocked.
|
||||
BlockedKeyFile string
|
||||
|
||||
// Path to directory holding orphan queue files, if not provided an orphan queue
|
||||
// is not used.
|
||||
OrphanQueueDir string
|
||||
|
||||
Features map[string]bool
|
||||
}
|
||||
|
||||
PA cmd.PAConfig
|
||||
|
||||
Syslog cmd.SyslogConfig
|
||||
}
|
||||
|
||||
func loadCFSSLIssuers(configs []ca_config.IssuerConfig) ([]ca.Issuer, error) {
|
||||
// IssuerConfig contains info about an issuer: private key and issuer cert.
|
||||
// It should contain either a File path to a PEM-format private key,
|
||||
// or a PKCS11Config defining how to load a module for an HSM. Used by CFSSL.
|
||||
type IssuerConfig struct {
|
||||
// A file from which a pkcs11key.Config will be read and parsed, if present
|
||||
ConfigFile string
|
||||
File string
|
||||
PKCS11 *pkcs11key.Config
|
||||
CertFile string
|
||||
// Number of sessions to open with the HSM. For maximum performance,
|
||||
// this should be equal to the number of cores in the HSM. Defaults to 1.
|
||||
NumSessions int
|
||||
}
|
||||
|
||||
func loadCFSSLIssuers(configs []IssuerConfig) ([]ca.Issuer, error) {
|
||||
var issuers []ca.Issuer
|
||||
for _, issuerConfig := range configs {
|
||||
priv, cert, err := loadIssuer(issuerConfig)
|
||||
signer, cert, err := loadCFSSLIssuer(issuerConfig)
|
||||
cmd.FailOnError(err, "Couldn't load private key")
|
||||
issuers = append(issuers, ca.Issuer{
|
||||
Signer: priv,
|
||||
Signer: signer,
|
||||
Cert: cert,
|
||||
})
|
||||
}
|
||||
return issuers, nil
|
||||
}
|
||||
|
||||
func loadBoulderIssuers(configs []ca_config.IssuerConfig, profile issuance.ProfileConfig, ignoredLints []string) ([]issuance.IssuerConfig, error) {
|
||||
boulderIssuerConfigs := make([]issuance.IssuerConfig, 0, len(configs))
|
||||
for _, issuerConfig := range configs {
|
||||
signer, issuer, err := loadIssuer(issuerConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
boulderIssuerConfigs = append(boulderIssuerConfigs, issuance.IssuerConfig{
|
||||
Cert: issuer,
|
||||
Signer: signer,
|
||||
IgnoredLints: ignoredLints,
|
||||
Clk: cmd.Clock(),
|
||||
Profile: profile,
|
||||
})
|
||||
}
|
||||
return boulderIssuerConfigs, nil
|
||||
}
|
||||
|
||||
func loadIssuer(issuerConfig ca_config.IssuerConfig) (crypto.Signer, *x509.Certificate, error) {
|
||||
func loadCFSSLIssuer(issuerConfig IssuerConfig) (crypto.Signer, *x509.Certificate, error) {
|
||||
cert, err := core.LoadCert(issuerConfig.CertFile)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
signer, err := loadSigner(issuerConfig, cert)
|
||||
signer, err := loadCFSSLSigner(issuerConfig, cert)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -83,7 +140,7 @@ func loadIssuer(issuerConfig ca_config.IssuerConfig) (crypto.Signer, *x509.Certi
|
|||
return signer, cert, err
|
||||
}
|
||||
|
||||
func loadSigner(issuerConfig ca_config.IssuerConfig, cert *x509.Certificate) (crypto.Signer, error) {
|
||||
func loadCFSSLSigner(issuerConfig IssuerConfig, cert *x509.Certificate) (crypto.Signer, error) {
|
||||
if issuerConfig.File != "" {
|
||||
keyBytes, err := ioutil.ReadFile(issuerConfig.File)
|
||||
if err != nil {
|
||||
|
@ -124,6 +181,34 @@ func loadSigner(issuerConfig ca_config.IssuerConfig, cert *x509.Certificate) (cr
|
|||
pkcs11Config.TokenLabel, pkcs11Config.PIN, cert.PublicKey)
|
||||
}
|
||||
|
||||
func loadBoulderIssuers(profileConfig issuance.ProfileConfig, issuerConfigs []issuance.IssuerConfig, ignoredLints []string) ([]*issuance.Issuer, error) {
|
||||
issuers := make([]*issuance.Issuer, 0, len(issuerConfigs))
|
||||
for _, issuerConfig := range issuerConfigs {
|
||||
profile, err := issuance.NewProfile(profileConfig, issuerConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, signer, err := issuance.LoadIssuer(issuerConfig.Location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
linter, err := lint.NewLinter(signer, ignoredLints)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
issuer, err := issuance.NewIssuer(cert, signer, profile, linter, cmd.Clock())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
issuers = append(issuers, issuer)
|
||||
}
|
||||
return issuers, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
caAddr := flag.String("ca-addr", "", "CA gRPC listen address override")
|
||||
ocspAddr := flag.String("ocsp-addr", "", "OCSP gRPC listen address override")
|
||||
|
@ -172,9 +257,9 @@ func main() {
|
|||
cmd.FailOnError(err, "Couldn't load hostname policy file")
|
||||
|
||||
var cfsslIssuers []ca.Issuer
|
||||
var boulderIssuerConfigs []issuance.IssuerConfig
|
||||
var boulderIssuers []*issuance.Issuer
|
||||
if features.Enabled(features.NonCFSSLSigner) {
|
||||
boulderIssuerConfigs, err = loadBoulderIssuers(c.CA.Issuers, c.CA.SignerProfile, c.CA.IgnoredLints)
|
||||
boulderIssuers, err = loadBoulderIssuers(c.CA.Issuance.Profile, c.CA.Issuance.Issuers, c.CA.Issuance.IgnoredLints)
|
||||
cmd.FailOnError(err, "Couldn't load issuers")
|
||||
} else {
|
||||
cfsslIssuers, err = loadCFSSLIssuers(c.CA.Issuers)
|
||||
|
@ -202,16 +287,23 @@ func main() {
|
|||
}
|
||||
|
||||
cai, err := ca.NewCertificateAuthorityImpl(
|
||||
c.CA,
|
||||
sa,
|
||||
pa,
|
||||
clk,
|
||||
scope,
|
||||
c.CA.CFSSL,
|
||||
c.CA.RSAProfile,
|
||||
c.CA.ECDSAProfile,
|
||||
cfsslIssuers,
|
||||
boulderIssuerConfigs,
|
||||
boulderIssuers,
|
||||
c.CA.Expiry.Duration,
|
||||
c.CA.Backdate.Duration,
|
||||
c.CA.SerialPrefix,
|
||||
c.CA.MaxNames,
|
||||
c.CA.LifespanOCSP.Duration,
|
||||
kp,
|
||||
orphanQueue,
|
||||
logger,
|
||||
orphanQueue)
|
||||
scope,
|
||||
clk)
|
||||
cmd.FailOnError(err, "Failed to create CA impl")
|
||||
|
||||
if orphanQueue != nil {
|
||||
|
|
|
@ -2,12 +2,10 @@ package main
|
|||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
ca_config "github.com/letsencrypt/boulder/ca/config"
|
||||
)
|
||||
|
||||
func TestLoadIssuerSuccess(t *testing.T) {
|
||||
signer, cert, err := loadIssuer(ca_config.IssuerConfig{
|
||||
signer, cert, err := loadCFSSLIssuer(IssuerConfig{
|
||||
File: "../../test/test-ca.key",
|
||||
CertFile: "../../test/test-ca2.pem",
|
||||
})
|
||||
|
@ -23,7 +21,7 @@ func TestLoadIssuerSuccess(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLoadIssuerBadKey(t *testing.T) {
|
||||
_, _, err := loadIssuer(ca_config.IssuerConfig{
|
||||
_, _, err := loadCFSSLIssuer(IssuerConfig{
|
||||
File: "/dev/null",
|
||||
CertFile: "../../test/test-ca2.pem",
|
||||
})
|
||||
|
@ -33,7 +31,7 @@ func TestLoadIssuerBadKey(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLoadIssuerBadCert(t *testing.T) {
|
||||
_, _, err := loadIssuer(ca_config.IssuerConfig{
|
||||
_, _, err := loadCFSSLIssuer(IssuerConfig{
|
||||
File: "../../test/test-ca.key",
|
||||
CertFile: "/dev/null",
|
||||
})
|
||||
|
|
|
@ -8,47 +8,41 @@ import (
|
|||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cfssl/helpers"
|
||||
ct "github.com/google/certificate-transparency-go"
|
||||
cttls "github.com/google/certificate-transparency-go/tls"
|
||||
ctx509 "github.com/google/certificate-transparency-go/x509"
|
||||
"github.com/jmhodges/clock"
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/boulder/lint"
|
||||
"github.com/letsencrypt/boulder/policyasn1"
|
||||
zlint "github.com/zmap/zlint/v2/lint"
|
||||
"github.com/letsencrypt/pkcs11key/v4"
|
||||
)
|
||||
|
||||
// IssuanceRequest describes a certificate issuance request
|
||||
type IssuanceRequest struct {
|
||||
PublicKey crypto.PublicKey
|
||||
// ProfileConfig describes the certificate issuance constraints for all issuers.
|
||||
type ProfileConfig struct {
|
||||
AllowMustStaple bool
|
||||
AllowCTPoison bool
|
||||
AllowSCTList bool
|
||||
AllowCommonName bool
|
||||
|
||||
Serial []byte
|
||||
|
||||
NotBefore time.Time
|
||||
NotAfter time.Time
|
||||
|
||||
CommonName string
|
||||
DNSNames []string
|
||||
|
||||
IncludeMustStaple bool
|
||||
IncludeCTPoison bool
|
||||
SCTList []ct.SignedCertificateTimestamp
|
||||
}
|
||||
|
||||
// PolicyQualifier describes a policy qualifier
|
||||
type PolicyQualifier struct {
|
||||
Type string
|
||||
Value string
|
||||
Policies []PolicyInformation
|
||||
MaxValidityPeriod cmd.ConfigDuration
|
||||
MaxValidityBackdate cmd.ConfigDuration
|
||||
}
|
||||
|
||||
// PolicyInformation describes a policy
|
||||
|
@ -57,26 +51,105 @@ type PolicyInformation struct {
|
|||
Qualifiers []PolicyQualifier
|
||||
}
|
||||
|
||||
// ProfileConfig describes the certificate issuance constraints
|
||||
type ProfileConfig struct {
|
||||
// PolicyQualifier describes a policy qualifier
|
||||
type PolicyQualifier struct {
|
||||
Type string
|
||||
Value string
|
||||
}
|
||||
|
||||
// IssuerConfig describes the constraints on and URLs used by a single issuer.
|
||||
type IssuerConfig struct {
|
||||
UseForRSALeaves bool
|
||||
UseForECDSALeaves bool
|
||||
|
||||
AllowMustStaple bool
|
||||
AllowCTPoison bool
|
||||
AllowSCTList bool
|
||||
AllowCommonName bool
|
||||
IssuerURL string
|
||||
OCSPURL string
|
||||
CRLURL string
|
||||
|
||||
IssuerURL string
|
||||
OCSPURL string
|
||||
CRLURL string
|
||||
Policies []PolicyInformation
|
||||
MaxValidityPeriod cmd.ConfigDuration
|
||||
MaxValidityBackdate cmd.ConfigDuration
|
||||
Location IssuerLoc
|
||||
}
|
||||
|
||||
// The internal structure created by reading in ProfileConfigs
|
||||
type issuanceProfile struct {
|
||||
// IssuerLoc describes the on-disk location and parameters that an issuer
|
||||
// should use to retrieve its certificate and private key.
|
||||
// Only one of File, ConfigFile, or PKCS11 should be set.
|
||||
type IssuerLoc struct {
|
||||
// A file from which a private key will be read and parsed.
|
||||
File string
|
||||
// A file from which a pkcs11key.Config will be read and parsed, if File is not set.
|
||||
ConfigFile string
|
||||
// An in-memory pkcs11key.Config, which will be used if ConfigFile is not set.
|
||||
PKCS11 *pkcs11key.Config
|
||||
// A file from which a certificate will be read and parsed.
|
||||
CertFile string
|
||||
// Number of sessions to open with the HSM. For maximum performance,
|
||||
// this should be equal to the number of cores in the HSM. Defaults to 1.
|
||||
NumSessions int
|
||||
}
|
||||
|
||||
// LoadIssuer loads a signer (private key) and certificate from the locations specified.
|
||||
func LoadIssuer(location IssuerLoc) (*x509.Certificate, crypto.Signer, error) {
|
||||
cert, err := core.LoadCert(location.CertFile)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
signer, err := loadSigner(location, cert)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if !core.KeyDigestEquals(signer.Public(), cert.PublicKey) {
|
||||
return nil, nil, fmt.Errorf("Issuer key did not match issuer cert %s", location.CertFile)
|
||||
}
|
||||
return cert, signer, err
|
||||
}
|
||||
|
||||
func loadSigner(location IssuerLoc, cert *x509.Certificate) (crypto.Signer, error) {
|
||||
if location.File != "" {
|
||||
keyBytes, err := ioutil.ReadFile(location.File)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not read key file %s", location.File)
|
||||
}
|
||||
|
||||
signer, err := helpers.ParsePrivateKeyPEM(keyBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
var pkcs11Config *pkcs11key.Config
|
||||
if location.ConfigFile != "" {
|
||||
contents, err := ioutil.ReadFile(location.ConfigFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkcs11Config = new(pkcs11key.Config)
|
||||
err = json.Unmarshal(contents, pkcs11Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
pkcs11Config = location.PKCS11
|
||||
}
|
||||
|
||||
if pkcs11Config.Module == "" ||
|
||||
pkcs11Config.TokenLabel == "" ||
|
||||
pkcs11Config.PIN == "" {
|
||||
return nil, fmt.Errorf("Missing a field in pkcs11Config %#v", pkcs11Config)
|
||||
}
|
||||
|
||||
numSessions := location.NumSessions
|
||||
if numSessions <= 0 {
|
||||
numSessions = 1
|
||||
}
|
||||
|
||||
return pkcs11key.NewPool(numSessions, pkcs11Config.Module,
|
||||
pkcs11Config.TokenLabel, pkcs11Config.PIN, cert.PublicKey)
|
||||
}
|
||||
|
||||
// Profile is the validated structure created by reading in ProfileConfigs and IssuerConfigs
|
||||
type Profile struct {
|
||||
useForRSALeaves bool
|
||||
useForECDSALeaves bool
|
||||
|
||||
|
@ -114,29 +187,31 @@ var stringToQualifierType = map[string]asn1.ObjectIdentifier{
|
|||
"id-qt-cps": policyasn1.CPSQualifierOID,
|
||||
}
|
||||
|
||||
func newProfile(config ProfileConfig) (*issuanceProfile, error) {
|
||||
if config.IssuerURL == "" {
|
||||
// NewProfile synthesizes the profile config and issuer config into a single
|
||||
// object, and checks various aspects for correctness.
|
||||
func NewProfile(profileConfig ProfileConfig, issuerConfig IssuerConfig) (*Profile, error) {
|
||||
if issuerConfig.IssuerURL == "" {
|
||||
return nil, errors.New("Issuer URL is required")
|
||||
}
|
||||
if config.OCSPURL == "" {
|
||||
if issuerConfig.OCSPURL == "" {
|
||||
return nil, errors.New("OCSP URL is required")
|
||||
}
|
||||
sp := &issuanceProfile{
|
||||
useForRSALeaves: config.UseForRSALeaves,
|
||||
useForECDSALeaves: config.UseForECDSALeaves,
|
||||
allowMustStaple: config.AllowMustStaple,
|
||||
allowCTPoison: config.AllowCTPoison,
|
||||
allowSCTList: config.AllowSCTList,
|
||||
allowCommonName: config.AllowCommonName,
|
||||
issuerURL: config.IssuerURL,
|
||||
crlURL: config.CRLURL,
|
||||
ocspURL: config.OCSPURL,
|
||||
maxBackdate: config.MaxValidityBackdate.Duration,
|
||||
maxValidity: config.MaxValidityPeriod.Duration,
|
||||
sp := &Profile{
|
||||
useForRSALeaves: issuerConfig.UseForRSALeaves,
|
||||
useForECDSALeaves: issuerConfig.UseForECDSALeaves,
|
||||
allowMustStaple: profileConfig.AllowMustStaple,
|
||||
allowCTPoison: profileConfig.AllowCTPoison,
|
||||
allowSCTList: profileConfig.AllowSCTList,
|
||||
allowCommonName: profileConfig.AllowCommonName,
|
||||
issuerURL: issuerConfig.IssuerURL,
|
||||
crlURL: issuerConfig.CRLURL,
|
||||
ocspURL: issuerConfig.OCSPURL,
|
||||
maxBackdate: profileConfig.MaxValidityBackdate.Duration,
|
||||
maxValidity: profileConfig.MaxValidityPeriod.Duration,
|
||||
}
|
||||
if len(config.Policies) > 0 {
|
||||
if len(profileConfig.Policies) > 0 {
|
||||
var policies []policyasn1.PolicyInformation
|
||||
for _, policyConfig := range config.Policies {
|
||||
for _, policyConfig := range profileConfig.Policies {
|
||||
id, err := parseOID(policyConfig.OID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed parsing policy OID %q: %s", policyConfig.OID, err)
|
||||
|
@ -169,7 +244,7 @@ func newProfile(config ProfileConfig) (*issuanceProfile, error) {
|
|||
|
||||
// requestValid verifies the passed IssuanceRequest against the profile. If the
|
||||
// request doesn't match the signing profile an error is returned.
|
||||
func (p *issuanceProfile) requestValid(clk clock.Clock, req *IssuanceRequest) error {
|
||||
func (p *Profile) requestValid(clk clock.Clock, req *IssuanceRequest) error {
|
||||
switch req.PublicKey.(type) {
|
||||
case *rsa.PublicKey:
|
||||
if !p.useForRSALeaves {
|
||||
|
@ -230,7 +305,7 @@ var defaultEKU = []x509.ExtKeyUsage{
|
|||
x509.ExtKeyUsageClientAuth,
|
||||
}
|
||||
|
||||
func (p *issuanceProfile) generateTemplate(clk clock.Clock) *x509.Certificate {
|
||||
func (p *Profile) generateTemplate(clk clock.Clock) *x509.Certificate {
|
||||
template := &x509.Certificate{
|
||||
SignatureAlgorithm: p.sigAlg,
|
||||
ExtKeyUsage: defaultEKU,
|
||||
|
@ -251,31 +326,19 @@ func (p *issuanceProfile) generateTemplate(clk clock.Clock) *x509.Certificate {
|
|||
}
|
||||
|
||||
// Issuer is capable of issuing new certificates
|
||||
// TODO(#5086): make Cert and Signer private when they're no longer needed by ca.internalIssuer
|
||||
type Issuer struct {
|
||||
cert *x509.Certificate
|
||||
signer crypto.Signer
|
||||
profile *issuanceProfile
|
||||
lintKey crypto.Signer
|
||||
lints zlint.Registry
|
||||
clk clock.Clock
|
||||
Cert *x509.Certificate
|
||||
Signer crypto.Signer
|
||||
Profile *Profile
|
||||
Linter *lint.Linter
|
||||
Clk clock.Clock
|
||||
}
|
||||
|
||||
// IssuerConfig contains the information necessary to construct an Issuer
|
||||
type IssuerConfig struct {
|
||||
Cert *x509.Certificate
|
||||
Signer crypto.Signer
|
||||
Profile ProfileConfig
|
||||
IgnoredLints []string
|
||||
Clk clock.Clock
|
||||
}
|
||||
|
||||
// New constructs an Issuer from the provided IssuerConfig
|
||||
func New(config IssuerConfig) (*Issuer, error) {
|
||||
profile, err := newProfile(config.Profile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch k := config.Cert.PublicKey.(type) {
|
||||
// NewIssuer constructs an Issuer on the heap, verifying that the profile
|
||||
// is well-formed.
|
||||
func NewIssuer(cert *x509.Certificate, signer crypto.Signer, profile *Profile, linter *lint.Linter, clk clock.Clock) (*Issuer, error) {
|
||||
switch k := cert.PublicKey.(type) {
|
||||
case *rsa.PublicKey:
|
||||
profile.sigAlg = x509.SHA256WithRSA
|
||||
case *ecdsa.PublicKey:
|
||||
|
@ -290,34 +353,42 @@ func New(config IssuerConfig) (*Issuer, error) {
|
|||
default:
|
||||
return nil, errors.New("unsupported issuer key type")
|
||||
}
|
||||
lintKey, err := lint.MakeSigner(config.Signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lints, err := zlint.GlobalRegistry().Filter(zlint.FilterOptions{
|
||||
ExcludeNames: config.IgnoredLints,
|
||||
ExcludeSources: []zlint.LintSource{
|
||||
// We ignore the ETSI and EVG lints since they do not
|
||||
// apply to the certificates we issue, and not attempting
|
||||
// to apply them will save some cycles.
|
||||
zlint.CABFEVGuidelines,
|
||||
zlint.EtsiEsi,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i := &Issuer{
|
||||
cert: config.Cert,
|
||||
signer: config.Signer,
|
||||
clk: config.Clk,
|
||||
lints: lints,
|
||||
lintKey: lintKey,
|
||||
profile: profile,
|
||||
Cert: cert,
|
||||
Signer: signer,
|
||||
Profile: profile,
|
||||
Linter: linter,
|
||||
Clk: clk,
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// Algs provides the list of leaf certificate public key algorithms for which
|
||||
// this issuer is willing to issue. This is not necessarily the same as the
|
||||
// public key algorithm or signature algorithm in this issuer's own cert.
|
||||
func (i *Issuer) Algs() []x509.PublicKeyAlgorithm {
|
||||
var algs []x509.PublicKeyAlgorithm
|
||||
if i.Profile.useForRSALeaves {
|
||||
algs = append(algs, x509.RSA)
|
||||
}
|
||||
if i.Profile.useForECDSALeaves {
|
||||
algs = append(algs, x509.ECDSA)
|
||||
}
|
||||
return algs
|
||||
}
|
||||
|
||||
// Name provides the Common Name specified in the issuer's certificate.
|
||||
func (i *Issuer) Name() string {
|
||||
return i.Cert.Subject.CommonName
|
||||
}
|
||||
|
||||
// ID provides a stable ID for an issuer's certificate. This is used for
|
||||
// identifying which issuer issued a certificate in the certificateStatus table.
|
||||
func (i *Issuer) ID() int64 {
|
||||
h := sha256.Sum256(i.Cert.Raw)
|
||||
return big.NewInt(0).SetBytes(h[:4]).Int64()
|
||||
}
|
||||
|
||||
var ctPoisonExt = pkix.Extension{
|
||||
// OID for CT poison, RFC 6962 (was never assigned a proper id-pe- name)
|
||||
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3},
|
||||
|
@ -377,6 +448,23 @@ func generateSKID(pk crypto.PublicKey) ([]byte, error) {
|
|||
return skid[:], nil
|
||||
}
|
||||
|
||||
// IssuanceRequest describes a certificate issuance request
|
||||
type IssuanceRequest struct {
|
||||
PublicKey crypto.PublicKey
|
||||
|
||||
Serial []byte
|
||||
|
||||
NotBefore time.Time
|
||||
NotAfter time.Time
|
||||
|
||||
CommonName string
|
||||
DNSNames []string
|
||||
|
||||
IncludeMustStaple bool
|
||||
IncludeCTPoison bool
|
||||
SCTList []ct.SignedCertificateTimestamp
|
||||
}
|
||||
|
||||
// Issue generates a certificate from the provided issuance request and
|
||||
// signs it. Before signing the certificate with the issuer's private
|
||||
// key, it is signed using a throwaway key so that it can be linted using
|
||||
|
@ -384,12 +472,12 @@ func generateSKID(pk crypto.PublicKey) ([]byte, error) {
|
|||
// is not signed using the issuer's key.
|
||||
func (i *Issuer) Issue(req *IssuanceRequest) ([]byte, error) {
|
||||
// check request is valid according to the issuance profile
|
||||
if err := i.profile.requestValid(i.clk, req); err != nil {
|
||||
if err := i.Profile.requestValid(i.Clk, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// generate template from the issuance profile
|
||||
template := i.profile.generateTemplate(i.clk)
|
||||
template := i.Profile.generateTemplate(i.Clk)
|
||||
|
||||
// populate template from the issuance request
|
||||
template.NotBefore, template.NotAfter = req.NotBefore, req.NotAfter
|
||||
|
@ -398,7 +486,7 @@ func (i *Issuer) Issue(req *IssuanceRequest) ([]byte, error) {
|
|||
template.Subject.CommonName = req.CommonName
|
||||
}
|
||||
template.DNSNames = req.DNSNames
|
||||
template.AuthorityKeyId = i.cert.SubjectKeyId
|
||||
template.AuthorityKeyId = i.Cert.SubjectKeyId
|
||||
skid, err := generateSKID(req.PublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -427,16 +515,12 @@ func (i *Issuer) Issue(req *IssuanceRequest) ([]byte, error) {
|
|||
|
||||
// check that the tbsCertificate is properly formed by signing it
|
||||
// with a throwaway key and then linting it using zlint
|
||||
lintCert, err := lint.MakeLintCert(template, i.cert, req.PublicKey, i.lintKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = lint.LintCert(lintCert, i.lints)
|
||||
err = i.Linter.LintTBS(template, i.Cert, req.PublicKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tbsCertificate linting failed: %w", err)
|
||||
}
|
||||
|
||||
return x509.CreateCertificate(rand.Reader, template, i.cert, req.PublicKey, i.signer)
|
||||
return x509.CreateCertificate(rand.Reader, template, i.Cert, req.PublicKey, i.Signer)
|
||||
}
|
||||
|
||||
func ContainsMustStaple(extensions []pkix.Extension) bool {
|
||||
|
|
|
@ -19,20 +19,17 @@ import (
|
|||
ct "github.com/google/certificate-transparency-go"
|
||||
"github.com/jmhodges/clock"
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/lint"
|
||||
"github.com/letsencrypt/boulder/policyasn1"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
)
|
||||
|
||||
func defaultProfileConfig() ProfileConfig {
|
||||
return ProfileConfig{
|
||||
UseForECDSALeaves: true,
|
||||
UseForRSALeaves: true,
|
||||
AllowCommonName: true,
|
||||
AllowCTPoison: true,
|
||||
AllowSCTList: true,
|
||||
AllowMustStaple: true,
|
||||
IssuerURL: "http://issuer-url",
|
||||
OCSPURL: "http://ocsp-url",
|
||||
AllowCommonName: true,
|
||||
AllowCTPoison: true,
|
||||
AllowSCTList: true,
|
||||
AllowMustStaple: true,
|
||||
Policies: []PolicyInformation{
|
||||
{OID: "1.2.3"},
|
||||
},
|
||||
|
@ -41,6 +38,45 @@ func defaultProfileConfig() ProfileConfig {
|
|||
}
|
||||
}
|
||||
|
||||
func defaultIssuerConfig() IssuerConfig {
|
||||
return IssuerConfig{
|
||||
UseForECDSALeaves: true,
|
||||
UseForRSALeaves: true,
|
||||
IssuerURL: "http://issuer-url",
|
||||
OCSPURL: "http://ocsp-url",
|
||||
}
|
||||
}
|
||||
|
||||
func defaultProfile() *Profile {
|
||||
p, _ := NewProfile(defaultProfileConfig(), defaultIssuerConfig())
|
||||
return p
|
||||
}
|
||||
|
||||
var issuerCert *x509.Certificate
|
||||
var issuerSigner *ecdsa.PrivateKey
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
tk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
cmd.FailOnError(err, "failed to generate test key")
|
||||
issuerSigner = tk
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(123),
|
||||
PublicKey: tk.Public(),
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "big ca",
|
||||
},
|
||||
KeyUsage: x509.KeyUsageCertSign,
|
||||
SubjectKeyId: []byte{1, 2, 3, 4, 5, 6, 7, 8},
|
||||
}
|
||||
issuer, err := x509.CreateCertificate(rand.Reader, template, template, tk.Public(), tk)
|
||||
cmd.FailOnError(err, "failed to generate test issuer")
|
||||
issuerCert, err = x509.ParseCertificate(issuer)
|
||||
cmd.FailOnError(err, "failed to parse test issuer")
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestNewProfilePolicies(t *testing.T) {
|
||||
config := defaultProfileConfig()
|
||||
config.Policies = append(config.Policies, PolicyInformation{
|
||||
|
@ -52,9 +88,9 @@ func TestNewProfilePolicies(t *testing.T) {
|
|||
},
|
||||
},
|
||||
})
|
||||
profile, err := newProfile(config)
|
||||
test.AssertNotError(t, err, "newProfile failed")
|
||||
test.AssertDeepEquals(t, *profile, issuanceProfile{
|
||||
profile, err := NewProfile(config, defaultIssuerConfig())
|
||||
test.AssertNotError(t, err, "NewProfile failed")
|
||||
test.AssertDeepEquals(t, *profile, Profile{
|
||||
useForRSALeaves: true,
|
||||
useForECDSALeaves: true,
|
||||
allowMustStaple: true,
|
||||
|
@ -87,33 +123,29 @@ func TestNewProfilePolicies(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewProfileNoIssuerURL(t *testing.T) {
|
||||
_, err := newProfile(ProfileConfig{})
|
||||
test.AssertError(t, err, "newProfile didn't fail with no issuer URL")
|
||||
_, err := NewProfile(ProfileConfig{}, IssuerConfig{})
|
||||
test.AssertError(t, err, "NewProfile didn't fail with no issuer URL")
|
||||
test.AssertEquals(t, err.Error(), "Issuer URL is required")
|
||||
}
|
||||
|
||||
func TestNewProfileNoOCSPURL(t *testing.T) {
|
||||
_, err := newProfile(ProfileConfig{IssuerURL: "issuer-url"})
|
||||
test.AssertError(t, err, "newProfile didn't fail with no OCSP URL")
|
||||
_, err := NewProfile(ProfileConfig{}, IssuerConfig{IssuerURL: "issuer-url"})
|
||||
test.AssertError(t, err, "NewProfile didn't fail with no OCSP URL")
|
||||
test.AssertEquals(t, err.Error(), "OCSP URL is required")
|
||||
}
|
||||
|
||||
func TestNewProfileInvalidOID(t *testing.T) {
|
||||
_, err := newProfile(ProfileConfig{
|
||||
IssuerURL: "issuer-url",
|
||||
OCSPURL: "ocsp-url",
|
||||
_, err := NewProfile(ProfileConfig{
|
||||
Policies: []PolicyInformation{{
|
||||
OID: "a.b.c",
|
||||
}},
|
||||
})
|
||||
test.AssertError(t, err, "newProfile didn't fail with unknown policy qualifier type")
|
||||
}, defaultIssuerConfig())
|
||||
test.AssertError(t, err, "NewProfile didn't fail with unknown policy qualifier type")
|
||||
test.AssertEquals(t, err.Error(), "failed parsing policy OID \"a.b.c\": strconv.Atoi: parsing \"a\": invalid syntax")
|
||||
}
|
||||
|
||||
func TestNewProfileUnknownQualifierType(t *testing.T) {
|
||||
_, err := newProfile(ProfileConfig{
|
||||
IssuerURL: "issuer-url",
|
||||
OCSPURL: "ocsp-url",
|
||||
_, err := NewProfile(ProfileConfig{
|
||||
Policies: []PolicyInformation{{
|
||||
OID: "1.2.3",
|
||||
Qualifiers: []PolicyQualifier{{
|
||||
|
@ -121,8 +153,8 @@ func TestNewProfileUnknownQualifierType(t *testing.T) {
|
|||
Value: "bad",
|
||||
}},
|
||||
}},
|
||||
})
|
||||
test.AssertError(t, err, "newProfile didn't fail with unknown policy qualifier type")
|
||||
}, defaultIssuerConfig())
|
||||
test.AssertError(t, err, "NewProfile didn't fail with unknown policy qualifier type")
|
||||
test.AssertEquals(t, err.Error(), "unknown qualifier type: asd")
|
||||
}
|
||||
|
||||
|
@ -131,31 +163,31 @@ func TestRequestValid(t *testing.T) {
|
|||
fc.Add(time.Hour * 24)
|
||||
tests := []struct {
|
||||
name string
|
||||
profile *issuanceProfile
|
||||
profile *Profile
|
||||
request *IssuanceRequest
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "unsupported key type",
|
||||
profile: &issuanceProfile{},
|
||||
profile: &Profile{},
|
||||
request: &IssuanceRequest{PublicKey: &dsa.PublicKey{}},
|
||||
expectedError: "unsupported public key type",
|
||||
},
|
||||
{
|
||||
name: "cannot sign rsa",
|
||||
profile: &issuanceProfile{},
|
||||
profile: &Profile{},
|
||||
request: &IssuanceRequest{PublicKey: &rsa.PublicKey{}},
|
||||
expectedError: "cannot sign RSA public keys",
|
||||
},
|
||||
{
|
||||
name: "cannot sign ecdsa",
|
||||
profile: &issuanceProfile{},
|
||||
profile: &Profile{},
|
||||
request: &IssuanceRequest{PublicKey: &ecdsa.PublicKey{}},
|
||||
expectedError: "cannot sign ECDSA public keys",
|
||||
},
|
||||
{
|
||||
name: "must staple not allowed",
|
||||
profile: &issuanceProfile{
|
||||
profile: &Profile{
|
||||
useForECDSALeaves: true,
|
||||
},
|
||||
request: &IssuanceRequest{
|
||||
|
@ -166,7 +198,7 @@ func TestRequestValid(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "ct poison not allowed",
|
||||
profile: &issuanceProfile{
|
||||
profile: &Profile{
|
||||
useForECDSALeaves: true,
|
||||
},
|
||||
request: &IssuanceRequest{
|
||||
|
@ -177,7 +209,7 @@ func TestRequestValid(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "sct list not allowed",
|
||||
profile: &issuanceProfile{
|
||||
profile: &Profile{
|
||||
useForECDSALeaves: true,
|
||||
},
|
||||
request: &IssuanceRequest{
|
||||
|
@ -188,7 +220,7 @@ func TestRequestValid(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "sct list and ct poison not allowed",
|
||||
profile: &issuanceProfile{
|
||||
profile: &Profile{
|
||||
useForECDSALeaves: true,
|
||||
allowCTPoison: true,
|
||||
allowSCTList: true,
|
||||
|
@ -202,7 +234,7 @@ func TestRequestValid(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "common name not allowed",
|
||||
profile: &issuanceProfile{
|
||||
profile: &Profile{
|
||||
useForECDSALeaves: true,
|
||||
},
|
||||
request: &IssuanceRequest{
|
||||
|
@ -213,7 +245,7 @@ func TestRequestValid(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "negative validity",
|
||||
profile: &issuanceProfile{
|
||||
profile: &Profile{
|
||||
useForECDSALeaves: true,
|
||||
},
|
||||
request: &IssuanceRequest{
|
||||
|
@ -225,7 +257,7 @@ func TestRequestValid(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "validity larger than max",
|
||||
profile: &issuanceProfile{
|
||||
profile: &Profile{
|
||||
useForECDSALeaves: true,
|
||||
maxValidity: time.Minute,
|
||||
},
|
||||
|
@ -238,7 +270,7 @@ func TestRequestValid(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "validity backdated more than max",
|
||||
profile: &issuanceProfile{
|
||||
profile: &Profile{
|
||||
useForECDSALeaves: true,
|
||||
maxValidity: time.Hour * 2,
|
||||
maxBackdate: time.Hour,
|
||||
|
@ -252,7 +284,7 @@ func TestRequestValid(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "validity is forward dated",
|
||||
profile: &issuanceProfile{
|
||||
profile: &Profile{
|
||||
useForECDSALeaves: true,
|
||||
maxValidity: time.Hour * 2,
|
||||
maxBackdate: time.Hour,
|
||||
|
@ -266,7 +298,7 @@ func TestRequestValid(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "serial too short",
|
||||
profile: &issuanceProfile{
|
||||
profile: &Profile{
|
||||
useForECDSALeaves: true,
|
||||
maxValidity: time.Hour * 2,
|
||||
},
|
||||
|
@ -279,7 +311,7 @@ func TestRequestValid(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "serial too long",
|
||||
profile: &issuanceProfile{
|
||||
profile: &Profile{
|
||||
useForECDSALeaves: true,
|
||||
maxValidity: time.Hour * 2,
|
||||
},
|
||||
|
@ -293,7 +325,7 @@ func TestRequestValid(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "good",
|
||||
profile: &issuanceProfile{
|
||||
profile: &Profile{
|
||||
useForECDSALeaves: true,
|
||||
maxValidity: time.Hour * 2,
|
||||
},
|
||||
|
@ -325,12 +357,12 @@ func TestRequestValid(t *testing.T) {
|
|||
func TestGenerateTemplate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
profile *issuanceProfile
|
||||
profile *Profile
|
||||
expectedTemplate *x509.Certificate
|
||||
}{
|
||||
{
|
||||
name: "crl url",
|
||||
profile: &issuanceProfile{
|
||||
profile: &Profile{
|
||||
crlURL: "crl-url",
|
||||
sigAlg: x509.SHA256WithRSA,
|
||||
},
|
||||
|
@ -345,7 +377,7 @@ func TestGenerateTemplate(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "include policies",
|
||||
profile: &issuanceProfile{
|
||||
profile: &Profile{
|
||||
sigAlg: x509.SHA256WithRSA,
|
||||
policies: &pkix.Extension{
|
||||
Id: asn1.ObjectIdentifier{1, 2, 3},
|
||||
|
@ -378,82 +410,19 @@ func TestGenerateTemplate(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewSignerUnsupportedKeyType(t *testing.T) {
|
||||
_, err := New(IssuerConfig{
|
||||
Profile: defaultProfileConfig(),
|
||||
Cert: &x509.Certificate{
|
||||
_, err := NewIssuer(
|
||||
&x509.Certificate{
|
||||
PublicKey: &ed25519.PublicKey{},
|
||||
},
|
||||
Signer: &ed25519.PrivateKey{},
|
||||
})
|
||||
test.AssertError(t, err, "issuance.New didn't fail")
|
||||
&ed25519.PrivateKey{},
|
||||
defaultProfile(),
|
||||
&lint.Linter{},
|
||||
clock.NewFake(),
|
||||
)
|
||||
test.AssertError(t, err, "NewIssuer didn't fail")
|
||||
test.AssertEquals(t, err.Error(), "unsupported issuer key type")
|
||||
}
|
||||
|
||||
func TestNewSignerRSAKey(t *testing.T) {
|
||||
mod, ok := big.NewInt(0).SetString("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16)
|
||||
test.Assert(t, ok, "failed to set mod")
|
||||
signer, err := New(IssuerConfig{
|
||||
Profile: defaultProfileConfig(),
|
||||
Cert: &x509.Certificate{
|
||||
PublicKey: &rsa.PublicKey{
|
||||
N: mod,
|
||||
},
|
||||
},
|
||||
Signer: &rsa.PrivateKey{
|
||||
PublicKey: rsa.PublicKey{
|
||||
N: mod,
|
||||
},
|
||||
},
|
||||
})
|
||||
test.AssertNotError(t, err, "issuance.New failed")
|
||||
_, ok = signer.lintKey.(*rsa.PrivateKey)
|
||||
test.Assert(t, ok, "lint key is not RSA")
|
||||
}
|
||||
|
||||
func TestNewSignerECDSAKey(t *testing.T) {
|
||||
signer, err := New(IssuerConfig{
|
||||
Profile: defaultProfileConfig(),
|
||||
Cert: &x509.Certificate{
|
||||
PublicKey: &ecdsa.PublicKey{
|
||||
Curve: elliptic.P256(),
|
||||
},
|
||||
},
|
||||
Signer: &ecdsa.PrivateKey{
|
||||
PublicKey: ecdsa.PublicKey{
|
||||
Curve: elliptic.P256(),
|
||||
},
|
||||
},
|
||||
})
|
||||
test.AssertNotError(t, err, "issuance.New failed")
|
||||
_, ok := signer.lintKey.(*ecdsa.PrivateKey)
|
||||
test.Assert(t, ok, "lint key is not ECDSA")
|
||||
}
|
||||
|
||||
var issuerCert *x509.Certificate
|
||||
var issuerSigner *ecdsa.PrivateKey
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
tk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
cmd.FailOnError(err, "failed to generate test key")
|
||||
issuerSigner = tk
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(123),
|
||||
PublicKey: tk.Public(),
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "big ca",
|
||||
},
|
||||
KeyUsage: x509.KeyUsageCertSign,
|
||||
SubjectKeyId: []byte{1, 2, 3, 4, 5, 6, 7, 8},
|
||||
}
|
||||
issuer, err := x509.CreateCertificate(rand.Reader, template, template, tk.Public(), tk)
|
||||
cmd.FailOnError(err, "failed to generate test issuer")
|
||||
issuerCert, err = x509.ParseCertificate(issuer)
|
||||
cmd.FailOnError(err, "failed to parse test issuer")
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestIssue(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
|
@ -478,14 +447,12 @@ func TestIssue(t *testing.T) {
|
|||
t.Run(tc.name, func(t *testing.T) {
|
||||
fc := clock.NewFake()
|
||||
fc.Set(time.Now())
|
||||
signer, err := New(IssuerConfig{
|
||||
Cert: issuerCert,
|
||||
Signer: issuerSigner,
|
||||
Clk: fc,
|
||||
Profile: defaultProfileConfig(),
|
||||
IgnoredLints: []string{"w_ct_sct_policy_count_unsatisfied", "n_subject_common_name_included"},
|
||||
})
|
||||
test.AssertNotError(t, err, "issuance.New failed")
|
||||
linter, _ := lint.NewLinter(
|
||||
issuerSigner,
|
||||
[]string{"w_ct_sct_policy_count_unsatisfied", "n_subject_common_name_included"},
|
||||
)
|
||||
signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
|
||||
test.AssertNotError(t, err, "NewIssuer failed")
|
||||
pk, err := tc.generateFunc()
|
||||
test.AssertNotError(t, err, "failed to generate test key")
|
||||
certBytes, err := signer.Issue(&IssuanceRequest{
|
||||
|
@ -514,14 +481,12 @@ func TestIssue(t *testing.T) {
|
|||
func TestIssueRSA(t *testing.T) {
|
||||
fc := clock.NewFake()
|
||||
fc.Set(time.Now())
|
||||
signer, err := New(IssuerConfig{
|
||||
Cert: issuerCert,
|
||||
Signer: issuerSigner,
|
||||
Clk: fc,
|
||||
Profile: defaultProfileConfig(),
|
||||
IgnoredLints: []string{"w_ct_sct_policy_count_unsatisfied"},
|
||||
})
|
||||
test.AssertNotError(t, err, "issuance.New failed")
|
||||
linter, _ := lint.NewLinter(
|
||||
issuerSigner,
|
||||
[]string{"w_ct_sct_policy_count_unsatisfied"},
|
||||
)
|
||||
signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
|
||||
test.AssertNotError(t, err, "NewIssuer failed")
|
||||
pk, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
test.AssertNotError(t, err, "failed to generate test key")
|
||||
certBytes, err := signer.Issue(&IssuanceRequest{
|
||||
|
@ -545,14 +510,12 @@ func TestIssueRSA(t *testing.T) {
|
|||
func TestIssueCTPoison(t *testing.T) {
|
||||
fc := clock.NewFake()
|
||||
fc.Set(time.Now())
|
||||
signer, err := New(IssuerConfig{
|
||||
Cert: issuerCert,
|
||||
Signer: issuerSigner,
|
||||
Clk: fc,
|
||||
Profile: defaultProfileConfig(),
|
||||
IgnoredLints: []string{"w_ct_sct_policy_count_unsatisfied"},
|
||||
})
|
||||
test.AssertNotError(t, err, "issuance.New failed")
|
||||
linter, _ := lint.NewLinter(
|
||||
issuerSigner,
|
||||
[]string{"w_ct_sct_policy_count_unsatisfied"},
|
||||
)
|
||||
signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
|
||||
test.AssertNotError(t, err, "NewIssuer failed")
|
||||
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
test.AssertNotError(t, err, "failed to generate test key")
|
||||
certBytes, err := signer.Issue(&IssuanceRequest{
|
||||
|
@ -577,14 +540,12 @@ func TestIssueCTPoison(t *testing.T) {
|
|||
func TestIssueSCTList(t *testing.T) {
|
||||
fc := clock.NewFake()
|
||||
fc.Set(time.Now())
|
||||
signer, err := New(IssuerConfig{
|
||||
Cert: issuerCert,
|
||||
Signer: issuerSigner,
|
||||
Clk: fc,
|
||||
Profile: defaultProfileConfig(),
|
||||
IgnoredLints: []string{"w_ct_sct_policy_count_unsatisfied"},
|
||||
})
|
||||
test.AssertNotError(t, err, "issuance.New failed")
|
||||
linter, _ := lint.NewLinter(
|
||||
issuerSigner,
|
||||
[]string{"w_ct_sct_policy_count_unsatisfied"},
|
||||
)
|
||||
signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
|
||||
test.AssertNotError(t, err, "NewIssuer failed")
|
||||
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
test.AssertNotError(t, err, "failed to generate test key")
|
||||
certBytes, err := signer.Issue(&IssuanceRequest{
|
||||
|
@ -614,14 +575,12 @@ func TestIssueSCTList(t *testing.T) {
|
|||
func TestIssueMustStaple(t *testing.T) {
|
||||
fc := clock.NewFake()
|
||||
fc.Set(time.Now())
|
||||
signer, err := New(IssuerConfig{
|
||||
Cert: issuerCert,
|
||||
Signer: issuerSigner,
|
||||
Clk: fc,
|
||||
Profile: defaultProfileConfig(),
|
||||
IgnoredLints: []string{"w_ct_sct_policy_count_unsatisfied"},
|
||||
})
|
||||
test.AssertNotError(t, err, "issuance.New failed")
|
||||
linter, _ := lint.NewLinter(
|
||||
issuerSigner,
|
||||
[]string{"w_ct_sct_policy_count_unsatisfied"},
|
||||
)
|
||||
signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
|
||||
test.AssertNotError(t, err, "NewIssuer failed")
|
||||
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
test.AssertNotError(t, err, "failed to generate test key")
|
||||
certBytes, err := signer.Issue(&IssuanceRequest{
|
||||
|
@ -646,13 +605,9 @@ func TestIssueMustStaple(t *testing.T) {
|
|||
func TestIssueBadLint(t *testing.T) {
|
||||
fc := clock.NewFake()
|
||||
fc.Set(time.Now())
|
||||
signer, err := New(IssuerConfig{
|
||||
Cert: issuerCert,
|
||||
Signer: issuerSigner,
|
||||
Clk: fc,
|
||||
Profile: defaultProfileConfig(),
|
||||
})
|
||||
test.AssertNotError(t, err, "issuance.New failed")
|
||||
linter, _ := lint.NewLinter(issuerSigner, []string{})
|
||||
signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
|
||||
test.AssertNotError(t, err, "NewIssuer failed")
|
||||
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
test.AssertNotError(t, err, "failed to generate test key")
|
||||
_, err = signer.Issue(&IssuanceRequest{
|
||||
|
|
53
lint/lint.go
53
lint/lint.go
|
@ -29,17 +29,11 @@ func Check(tbs, issuer *x509.Certificate, subjectPubKey crypto.PublicKey, realSi
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lints, err := lint.GlobalRegistry().Filter(lint.FilterOptions{
|
||||
ExcludeNames: skipLints,
|
||||
ExcludeSources: []lint.LintSource{
|
||||
lint.CABFEVGuidelines,
|
||||
lint.EtsiEsi,
|
||||
},
|
||||
})
|
||||
reg, err := MakeRegistry(skipLints)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create lint registry: %w", err)
|
||||
return err
|
||||
}
|
||||
return LintCert(lintCert, lints)
|
||||
return LintCert(lintCert, reg)
|
||||
}
|
||||
|
||||
// MakeSigner creates a throwaway crypto.Signer with the same key algorithm
|
||||
|
@ -65,6 +59,22 @@ func MakeSigner(realSigner crypto.Signer) (crypto.Signer, error) {
|
|||
return lintSigner, nil
|
||||
}
|
||||
|
||||
// MakeRegistry creates a zlint Registry of lints to run, filtering out the
|
||||
// EV- and ETSI-specific lints, as well as any others specified.
|
||||
func MakeRegistry(skipLints []string) (lint.Registry, error) {
|
||||
reg, err := lint.GlobalRegistry().Filter(lint.FilterOptions{
|
||||
ExcludeNames: skipLints,
|
||||
ExcludeSources: []lint.LintSource{
|
||||
lint.CABFEVGuidelines,
|
||||
lint.EtsiEsi,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create lint registry: %w", err)
|
||||
}
|
||||
return reg, nil
|
||||
}
|
||||
|
||||
// MakeLintCert creates a throwaway x509.Certificate which can be linted.
|
||||
// Only use the result from MakeSigner as the final argument.
|
||||
func MakeLintCert(tbs, issuer *x509.Certificate, subjectPubKey crypto.PublicKey, lintSigner crypto.Signer) (*zlintx509.Certificate, error) {
|
||||
|
@ -94,3 +104,28 @@ func LintCert(lintCert *zlintx509.Certificate, lints lint.Registry) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Linter struct {
|
||||
signer crypto.Signer
|
||||
registry lint.Registry
|
||||
}
|
||||
|
||||
func NewLinter(realSigner crypto.Signer, skipLints []string) (*Linter, error) {
|
||||
signer, err := MakeSigner(realSigner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reg, err := MakeRegistry(skipLints)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Linter{signer, reg}, nil
|
||||
}
|
||||
|
||||
func (l Linter) LintTBS(tbs, issuer *x509.Certificate, subjectPubKey crypto.PublicKey) error {
|
||||
cert, err := MakeLintCert(tbs, issuer, subjectPubKey, l.signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return LintCert(cert, l.registry)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue