Add multi-issuer support to the CA.

Currently this is only supported for OCSP, not for EE cert signing.

Also clean up some things in the CA unittest.
This commit is contained in:
Jacob Hoffman-Andrews 2016-03-19 10:17:26 -07:00
parent c7ba6f13d2
commit a3d044f681
5 changed files with 407 additions and 99 deletions

View File

@ -120,37 +120,85 @@ const (
// CertificateAuthorityImpl represents a CA that signs certificates, CRLs, and
// OCSP responses.
type CertificateAuthorityImpl struct {
rsaProfile string
ecdsaProfile string
signer signer.Signer
ocspSigner ocsp.Signer
rsaProfile string
ecdsaProfile string
// A map from issuer cert common name to an internalIssuer struct
issuers map[string]*internalIssuer
// The common name of the default issuer cert
defaultIssuer *internalIssuer
SA core.StorageAuthority
PA core.PolicyAuthority
Publisher core.Publisher
keyPolicy core.KeyPolicy
clk clock.Clock // TODO(jmhodges): should be private, like log
clk clock.Clock
log *blog.AuditLogger
stats statsd.Statter
prefix int // Prepended to the serial number
validityPeriod time.Duration
notAfter time.Time
maxNames int
forceCNFromSAN bool
enableMustStaple bool
}
// NewCertificateAuthorityImpl creates a CA that talks to a remote CFSSL
// instance. (To use a local signer, simply instantiate CertificateAuthorityImpl
// directly.) Communications with the CA are authenticated with MACs,
// using CFSSL's authenticated signature scheme. A CA created in this way
// issues for a single profile on the remote signer, which is indicated
// by name in this constructor.
// Issuer represents a single issuer certificate, along with its key.
type Issuer struct {
Signer crypto.Signer
Cert *x509.Certificate
}
// internalIssuer represents the fully initialized internal state for a single
// issuer, including the cfssl signer and OCSP signer objects.
type internalIssuer struct {
cert *x509.Certificate
eeSigner signer.Signer
ocspSigner ocsp.Signer
}
func makeInternalIssuers(
issuers []Issuer,
policy *cfsslConfig.Signing,
lifespanOCSP time.Duration,
) (map[string]*internalIssuer, error) {
if len(issuers) == 0 {
return nil, errors.New("No issuers specified.")
}
internalIssuers := make(map[string]*internalIssuer)
for _, iss := range issuers {
if iss.Cert == nil || iss.Signer == nil {
return nil, errors.New("Issuer with nil cert or signer specified.")
}
eeSigner, err := local.NewSigner(iss.Signer, iss.Cert, x509.SHA256WithRSA, policy)
if err != nil {
return nil, err
}
// Set up our OCSP signer. Note this calls for both the issuer cert and the
// OCSP signing cert, which are the same in our case.
ocspSigner, err := ocsp.NewSigner(iss.Cert, iss.Cert, iss.Signer, lifespanOCSP)
if err != nil {
return nil, err
}
cn := iss.Cert.Subject.CommonName
if internalIssuers[cn] != nil {
return nil, errors.New("Multiple issuer certs with the same CommonName are not supported")
}
internalIssuers[cn] = &internalIssuer{
cert: iss.Cert,
eeSigner: eeSigner,
ocspSigner: ocspSigner,
}
}
return internalIssuers, nil
}
// NewCertificateAuthorityImpl creates a CA instance that can sign certificates
// from a single issuer (the first first in the issers slice), and can sign OCSP
// for any of the issuer certificates provided.
func NewCertificateAuthorityImpl(
config cmd.CAConfig,
clk clock.Clock,
stats statsd.Statter,
issuer *x509.Certificate,
privateKey crypto.Signer,
issuers []Issuer,
keyPolicy core.KeyPolicy,
) (*CertificateAuthorityImpl, error) {
var ca *CertificateAuthorityImpl
@ -174,51 +222,35 @@ func NewCertificateAuthorityImpl(
return nil, err
}
signer, err := local.NewSigner(privateKey, issuer, x509.SHA256WithRSA, cfsslConfigObj.Signing)
if err != nil {
return nil, err
}
if config.LifespanOCSP == "" {
if config.LifespanOCSP.Duration == 0 {
return nil, errors.New("Config must specify an OCSP lifespan period.")
}
lifespanOCSP, err := time.ParseDuration(config.LifespanOCSP)
if err != nil {
return nil, err
}
// Set up our OCSP signer. Note this calls for both the issuer cert and the
// OCSP signing cert, which are the same in our case.
ocspSigner, err := ocsp.NewSigner(issuer, issuer, privateKey, lifespanOCSP)
internalIssuers, err := makeInternalIssuers(
issuers,
cfsslConfigObj.Signing,
config.LifespanOCSP.Duration)
if err != nil {
return nil, err
}
defaultIssuer := internalIssuers[issuers[0].Cert.Subject.CommonName]
rsaProfile := config.RSAProfile
ecdsaProfile := config.ECDSAProfile
if config.Profile != "" {
if rsaProfile != "" || ecdsaProfile != "" {
return nil, errors.New("either specify profile or rsaProfile and ecdsaProfile, but not both")
}
rsaProfile = config.Profile
ecdsaProfile = config.Profile
}
if rsaProfile == "" || ecdsaProfile == "" {
return nil, errors.New("must specify rsaProfile and ecdsaProfile")
}
ca = &CertificateAuthorityImpl{
signer: signer,
ocspSigner: ocspSigner,
issuers: internalIssuers,
defaultIssuer: defaultIssuer,
rsaProfile: rsaProfile,
ecdsaProfile: ecdsaProfile,
prefix: config.SerialPrefix,
clk: clk,
log: logger,
stats: stats,
notAfter: issuer.NotAfter,
keyPolicy: keyPolicy,
forceCNFromSAN: !config.DoNotForceCN, // Note the inversion here
enableMustStaple: config.EnableMustStaple,
@ -337,7 +369,20 @@ func (ca *CertificateAuthorityImpl) GenerateOCSP(xferObj core.OCSPSigningRequest
RevokedAt: xferObj.RevokedAt,
}
ocspResponse, err := ca.ocspSigner.Sign(signRequest)
cn := cert.Issuer.CommonName
issuer := ca.issuers[cn]
if issuer == nil {
return nil, fmt.Errorf("This CA doesn't have an issuer cert with CommonName %q", cn)
}
err = cert.CheckSignatureFrom(issuer.cert)
if err != nil {
return nil, fmt.Errorf("GenerateOCSP was asked to sign OCSP for cert "+
"%s from %q, but the cert's signature was not valid: %s.",
core.SerialToString(cert.SerialNumber), cn, err)
}
ocspResponse, err := issuer.ocspSigner.Sign(signRequest)
ca.noteSignError(err)
return ocspResponse, err
}
@ -345,6 +390,7 @@ func (ca *CertificateAuthorityImpl) GenerateOCSP(xferObj core.OCSPSigningRequest
// IssueCertificate attempts to convert a CSR into a signed Certificate, while
// enforcing all policies. Names (domains) in the CertificateRequest will be
// lowercased before storage.
// Currently it will always sign with the defaultIssuer.
func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest, regID int64) (core.Certificate, error) {
emptyCert := core.Certificate{}
@ -429,10 +475,11 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
return emptyCert, err
}
issuer := ca.defaultIssuer
notAfter := ca.clk.Now().Add(ca.validityPeriod)
if ca.notAfter.Before(notAfter) {
err = core.InternalServerError("Cannot issue a certificate that expires after the intermediate certificate.")
if issuer.cert.NotAfter.Before(notAfter) {
err = core.InternalServerError("Cannot issue a certificate that expires after the issuer certificate.")
// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
ca.log.AuditErr(err)
return emptyCert, err
@ -490,7 +537,7 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
ca.log.AuditNotice(fmt.Sprintf("Signing: serial=[%s] names=[%s] csr=[%s]",
serialHex, strings.Join(hostNames, ", "), csrPEM))
certPEM, err := ca.signer.Sign(req)
certPEM, err := issuer.eeSigner.Sign(req)
ca.noteSignError(err)
if err != nil {
err = core.InternalServerError(err.Error())
@ -501,7 +548,7 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
ca.log.AuditNotice(fmt.Sprintf("Signing success: serial=[%s] names=[%s] csr=[%s] pem=[%s]",
serialHex, strings.Join(hostNames, ", "), csrPEM,
base64.StdEncoding.EncodeToString(certPEM)))
certPEM))
if len(certPEM) == 0 {
err = core.InternalServerError("No certificate returned by server")

View File

@ -18,8 +18,8 @@ import (
cfsslConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers"
ocspConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp/config"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/golang.org/x/crypto/ocsp"
"github.com/letsencrypt/boulder/cmd"
"github.com/letsencrypt/boulder/core"
@ -32,10 +32,6 @@ import (
)
var (
CAkeyPEM = mustRead("./testdata/ca_key.pem")
CAcertPEM = mustRead("./testdata/ca_cert.pem")
// CSR generated by Go:
// * Random public key
// * CN = not-example.com
// * DNSNames = not-example.com, www.not-example.com
@ -160,6 +156,7 @@ type testCtx struct {
caConfig cmd.CAConfig
reg core.Registration
pa core.PolicyAuthority
issuers []Issuer
keyPolicy core.KeyPolicy
fc clock.FakeClock
stats *mocks.Statter
@ -215,7 +212,7 @@ func setup(t *testing.T) *testCtx {
ECDSAProfile: ecdsaProfileName,
SerialPrefix: 17,
Expiry: "8760h",
LifespanOCSP: "45m",
LifespanOCSP: cmd.ConfigDuration{Duration: 45 * time.Minute},
MaxNames: 2,
DoNotForceCN: true,
CFSSL: cfsslConfig.Config{
@ -271,16 +268,13 @@ func setup(t *testing.T) *testCtx {
ExpiryString: "8760h",
},
},
OCSP: &ocspConfig.Config{
CACertFile: caCertFile,
ResponderCertFile: caCertFile,
KeyFile: caKeyFile,
},
},
}
stats := mocks.NewStatter()
issuers := []Issuer{{caKey, caCert}}
keyPolicy := core.KeyPolicy{
AllowRSA: true,
AllowECDSANISTP256: true,
@ -292,6 +286,7 @@ func setup(t *testing.T) *testCtx {
caConfig,
reg,
pa,
issuers,
keyPolicy,
fc,
&stats,
@ -304,28 +299,29 @@ func TestFailNoSerial(t *testing.T) {
defer ctx.cleanUp()
ctx.caConfig.SerialPrefix = 0
_, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey, ctx.keyPolicy)
_, err := NewCertificateAuthorityImpl(
ctx.caConfig,
ctx.fc,
ctx.stats,
ctx.issuers,
ctx.keyPolicy)
test.AssertError(t, err, "CA should have failed with no SerialPrefix")
}
func TestIssueCertificate(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey, ctx.keyPolicy)
ca, err := NewCertificateAuthorityImpl(
ctx.caConfig,
ctx.fc,
ctx.stats,
ctx.issuers,
ctx.keyPolicy)
test.AssertNotError(t, err, "Failed to create CA")
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
ca.SA = ctx.sa
/*
// Uncomment to test with a local signer
signer, _ := local.NewSigner(caKey, caCert, x509.SHA256WithRSA, nil)
ca := CertificateAuthorityImpl{
Signer: signer,
SA: sa,
}
*/
csrs := [][]byte{CNandSANCSR, NoSANCSR}
for _, csrDER := range csrs {
csr, _ := x509.ParseCertificateRequest(csrDER)
@ -384,10 +380,149 @@ func TestIssueCertificate(t *testing.T) {
}
}
// Test issuing when multiple issuers are present.
func TestIssueCertificateMultipleIssuers(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
// Load multiple issuers, and ensure the first one in the list is used.
newIssuerCert, err := core.LoadCert("../test/test-ca2.pem")
test.AssertNotError(t, err, "Failed to load new cert")
newIssuers := []Issuer{
{
Signer: caKey,
// newIssuerCert is first, so it will be the default.
Cert: newIssuerCert,
}, {
Signer: caKey,
Cert: caCert,
},
}
ca, err := NewCertificateAuthorityImpl(
ctx.caConfig,
ctx.fc,
ctx.stats,
newIssuers,
ctx.keyPolicy)
test.AssertNotError(t, err, "Failed to remake CA")
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
ca.SA = ctx.sa
csr, _ := x509.ParseCertificateRequest(CNandSANCSR)
issuedCert, err := ca.IssueCertificate(*csr, ctx.reg.ID)
test.AssertNotError(t, err, "Failed to sign certificate")
cert, err := x509.ParseCertificate(issuedCert.DER)
test.AssertNotError(t, err, "Certificate failed to parse")
// Verify cert was signed by newIssuerCert, not caCert.
err = cert.CheckSignatureFrom(newIssuerCert)
test.AssertNotError(t, err, "Certificate failed signature validation")
}
func TestOCSP(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(
ctx.caConfig,
ctx.fc,
ctx.stats,
ctx.issuers,
ctx.keyPolicy)
test.AssertNotError(t, err, "Failed to create CA")
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
ca.SA = ctx.sa
csr, _ := x509.ParseCertificateRequest(CNandSANCSR)
cert, err := ca.IssueCertificate(*csr, ctx.reg.ID)
test.AssertNotError(t, err, "Failed to issue")
parsedCert, err := x509.ParseCertificate(cert.DER)
test.AssertNotError(t, err, "Failed to parse cert")
ocspResp, err := ca.GenerateOCSP(core.OCSPSigningRequest{
CertDER: cert.DER,
Status: string(core.OCSPStatusGood),
})
test.AssertNotError(t, err, "Failed to generate OCSP")
parsed, err := ocsp.ParseResponse(ocspResp, caCert)
test.AssertNotError(t, err, "Failed to parse validate OCSP")
test.AssertEquals(t, parsed.Status, 0)
test.AssertEquals(t, parsed.RevocationReason, 0)
test.AssertEquals(t, parsed.SerialNumber.Cmp(parsedCert.SerialNumber), 0)
// Test that signatures are checked.
ocspResp, err = ca.GenerateOCSP(core.OCSPSigningRequest{
CertDER: append(cert.DER, byte(0)),
Status: string(core.OCSPStatusGood),
})
test.AssertError(t, err, "Generated OCSP for cert with bad signature")
// Load multiple issuers, including the old issuer, and ensure OCSP is still
// signed correctly.
newIssuerCert, err := core.LoadCert("../test/test-ca2.pem")
test.AssertNotError(t, err, "Failed to load new cert")
newIssuers := []Issuer{
{
Signer: caKey,
// newIssuerCert is first, so it will be the default.
Cert: newIssuerCert,
}, {
Signer: caKey,
Cert: caCert,
},
}
ca, err = NewCertificateAuthorityImpl(
ctx.caConfig,
ctx.fc,
ctx.stats,
newIssuers,
ctx.keyPolicy)
test.AssertNotError(t, err, "Failed to remake CA")
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
ca.SA = ctx.sa
// Now issue a new cert, signed by newIssuerCert
newCert, err := ca.IssueCertificate(*csr, ctx.reg.ID)
test.AssertNotError(t, err, "Failed to issue newCert")
parsedNewCert, err := x509.ParseCertificate(newCert.DER)
test.AssertNotError(t, err, "Failed to parse newCert")
err = parsedNewCert.CheckSignatureFrom(newIssuerCert)
t.Logf("check sig: %s", err)
// ocspResp2 is a second OCSP response for `cert` (issued by caCert), and
// should be signed by caCert.
ocspResp2, err := ca.GenerateOCSP(core.OCSPSigningRequest{
CertDER: append(cert.DER),
Status: string(core.OCSPStatusGood),
})
test.AssertNotError(t, err, "Failed to sign second OCSP response")
_, err = ocsp.ParseResponse(ocspResp2, caCert)
test.AssertNotError(t, err, "Failed to parse / validate second OCSP response")
// newCertOcspResp is an OCSP response for `newCert` (issued by newIssuer),
// and should be signed by newIssuer.
newCertOcspResp, err := ca.GenerateOCSP(core.OCSPSigningRequest{
CertDER: newCert.DER,
Status: string(core.OCSPStatusGood),
})
test.AssertNotError(t, err, "Failed to generate OCSP")
parsedNewCertOcspResp, err := ocsp.ParseResponse(newCertOcspResp, newIssuerCert)
test.AssertNotError(t, err, "Failed to parse / validate OCSP for newCert")
test.AssertEquals(t, parsedNewCertOcspResp.Status, 0)
test.AssertEquals(t, parsedNewCertOcspResp.RevocationReason, 0)
test.AssertEquals(t, parsedNewCertOcspResp.SerialNumber.Cmp(parsedNewCert.SerialNumber), 0)
}
func TestNoHostnames(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey, ctx.keyPolicy)
ca, err := NewCertificateAuthorityImpl(
ctx.caConfig,
ctx.fc,
ctx.stats,
ctx.issuers,
ctx.keyPolicy)
test.AssertNotError(t, err, "Failed to create CA")
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
@ -403,7 +538,12 @@ func TestNoHostnames(t *testing.T) {
func TestRejectTooManyNames(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey, ctx.keyPolicy)
ca, err := NewCertificateAuthorityImpl(
ctx.caConfig,
ctx.fc,
ctx.stats,
ctx.issuers,
ctx.keyPolicy)
test.AssertNotError(t, err, "Failed to create CA")
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
@ -420,7 +560,12 @@ func TestRejectTooManyNames(t *testing.T) {
func TestDeduplication(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey, ctx.keyPolicy)
ca, err := NewCertificateAuthorityImpl(
ctx.caConfig,
ctx.fc,
ctx.stats,
ctx.issuers,
ctx.keyPolicy)
test.AssertNotError(t, err, "Failed to create CA")
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
@ -443,17 +588,26 @@ func TestDeduplication(t *testing.T) {
func TestRejectValidityTooLong(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey, ctx.keyPolicy)
ca, err := NewCertificateAuthorityImpl(
ctx.caConfig,
ctx.fc,
ctx.stats,
ctx.issuers,
ctx.keyPolicy)
test.AssertNotError(t, err, "Failed to create CA")
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
ca.SA = ctx.sa
// This time is a few minutes before the notAfter in testdata/ca_cert.pem
future, err := time.Parse(time.RFC3339, "2025-02-10T00:30:00Z")
test.AssertNotError(t, err, "Failed to parse time")
ctx.fc.Set(future)
// Test that the CA rejects CSRs that would expire after the intermediate cert
csr, _ := x509.ParseCertificateRequest(NoCNCSR)
ca.notAfter = ctx.fc.Now()
_, err = ca.IssueCertificate(*csr, 1)
test.AssertEquals(t, err.Error(), "Cannot issue a certificate that expires after the intermediate certificate.")
test.AssertError(t, err, "Cannot issue a certificate that expires after the intermediate certificate")
_, ok := err.(core.InternalServerError)
test.Assert(t, ok, "Incorrect error type returned")
}
@ -461,7 +615,12 @@ func TestRejectValidityTooLong(t *testing.T) {
func TestShortKey(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey, ctx.keyPolicy)
ca, err := NewCertificateAuthorityImpl(
ctx.caConfig,
ctx.fc,
ctx.stats,
ctx.issuers,
ctx.keyPolicy)
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
ca.SA = ctx.sa
@ -477,7 +636,12 @@ func TestShortKey(t *testing.T) {
func TestAllowNoCN(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey, ctx.keyPolicy)
ca, err := NewCertificateAuthorityImpl(
ctx.caConfig,
ctx.fc,
ctx.stats,
ctx.issuers,
ctx.keyPolicy)
test.AssertNotError(t, err, "Couldn't create new CA")
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
@ -513,7 +677,12 @@ func TestAllowNoCN(t *testing.T) {
func TestLongCommonName(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey, ctx.keyPolicy)
ca, err := NewCertificateAuthorityImpl(
ctx.caConfig,
ctx.fc,
ctx.stats,
ctx.issuers,
ctx.keyPolicy)
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
ca.SA = ctx.sa
@ -528,7 +697,12 @@ func TestLongCommonName(t *testing.T) {
func TestRejectBadAlgorithm(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey, ctx.keyPolicy)
ca, err := NewCertificateAuthorityImpl(
ctx.caConfig,
ctx.fc,
ctx.stats,
ctx.issuers,
ctx.keyPolicy)
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
ca.SA = ctx.sa
@ -545,7 +719,12 @@ func TestCapitalizedLetters(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ctx.caConfig.MaxNames = 3
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey, ctx.keyPolicy)
ca, err := NewCertificateAuthorityImpl(
ctx.caConfig,
ctx.fc,
ctx.stats,
ctx.issuers,
ctx.keyPolicy)
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
ca.SA = ctx.sa
@ -567,7 +746,12 @@ func TestWrongSignature(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ctx.caConfig.MaxNames = 3
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey, ctx.keyPolicy)
ca, err := NewCertificateAuthorityImpl(
ctx.caConfig,
ctx.fc,
ctx.stats,
ctx.issuers,
ctx.keyPolicy)
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
ca.SA = ctx.sa
@ -585,7 +769,12 @@ func TestProfileSelection(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ctx.caConfig.MaxNames = 3
ca, _ := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey, ctx.keyPolicy)
ca, _ := NewCertificateAuthorityImpl(
ctx.caConfig,
ctx.fc,
ctx.stats,
ctx.issuers,
ctx.keyPolicy)
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
ca.SA = ctx.sa
@ -630,7 +819,12 @@ func TestExtensions(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()
ctx.caConfig.MaxNames = 3
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey, ctx.keyPolicy)
ca, err := NewCertificateAuthorityImpl(
ctx.caConfig,
ctx.fc,
ctx.stats,
ctx.issuers,
ctx.keyPolicy)
ca.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
ca.SA = ctx.sa

View File

@ -7,6 +7,7 @@ package main
import (
"crypto"
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
@ -27,19 +28,59 @@ import (
const clientName = "CA"
func loadPrivateKey(keyConfig cmd.KeyConfig) (crypto.Signer, error) {
if keyConfig.File != "" {
keyBytes, err := ioutil.ReadFile(keyConfig.File)
func loadIssuers(c cmd.Config) ([]ca.Issuer, error) {
if c.CA.Key != nil {
issuerConfig := *c.CA.Key
issuerConfig.CertFile = c.Common.IssuerCert
priv, cert, err := loadIssuer(issuerConfig)
return []ca.Issuer{{
Signer: priv,
Cert: cert,
}}, err
}
var issuers []ca.Issuer
for _, issuerConfig := range c.CA.Issuers {
priv, cert, err := loadIssuer(issuerConfig)
cmd.FailOnError(err, "Couldn't load private key")
issuers = append(issuers, ca.Issuer{
Signer: priv,
Cert: cert,
})
}
return issuers, nil
}
func loadIssuer(issuerConfig cmd.IssuerConfig) (crypto.Signer, *x509.Certificate, error) {
cert, err := core.LoadCert(issuerConfig.CertFile)
if err != nil {
return nil, nil, err
}
signer, err := loadSigner(issuerConfig)
if !core.KeyDigestEquals(signer.Public(), cert.PublicKey) {
return nil, nil, fmt.Errorf("Issuer key did not match issuer cert %s", issuerConfig.CertFile)
}
return signer, cert, err
}
func loadSigner(issuerConfig cmd.IssuerConfig) (crypto.Signer, error) {
if issuerConfig.File != "" {
keyBytes, err := ioutil.ReadFile(issuerConfig.File)
if err != nil {
return nil, fmt.Errorf("Could not read key file %s", keyConfig.File)
return nil, fmt.Errorf("Could not read key file %s", issuerConfig.File)
}
return helpers.ParsePrivateKeyPEM(keyBytes)
signer, err := helpers.ParsePrivateKeyPEM(keyBytes)
if err != nil {
return nil, err
}
return signer, nil
}
var pkcs11Config *pkcs11key.Config
if keyConfig.ConfigFile != "" {
contents, err := ioutil.ReadFile(keyConfig.ConfigFile)
if issuerConfig.ConfigFile != "" {
contents, err := ioutil.ReadFile(issuerConfig.ConfigFile)
if err != nil {
return nil, err
}
@ -49,7 +90,7 @@ func loadPrivateKey(keyConfig cmd.KeyConfig) (crypto.Signer, error) {
return nil, err
}
} else {
pkcs11Config = keyConfig.PKCS11
pkcs11Config = issuerConfig.PKCS11
}
if pkcs11Config.Module == "" ||
pkcs11Config.TokenLabel == "" ||
@ -89,18 +130,14 @@ func main() {
cmd.FailOnError(err, "Couldn't load hostname policy file")
}
priv, err := loadPrivateKey(c.CA.Key)
cmd.FailOnError(err, "Couldn't load private key")
issuer, err := core.LoadCert(c.Common.IssuerCert)
cmd.FailOnError(err, "Couldn't load issuer cert")
issuers, err := loadIssuers(c)
cmd.FailOnError(err, "Couldn't load issuers")
cai, err := ca.NewCertificateAuthorityImpl(
c.CA,
clock.Default(),
stats,
issuer,
priv,
issuers,
c.KeyPolicy())
cmd.FailOnError(err, "Failed to create CA impl")
cai.PA = pa

View File

@ -311,15 +311,18 @@ type CAConfig struct {
DBConfig
HostnamePolicyConfig
Profile string
RSAProfile string
ECDSAProfile string
TestMode bool
SerialPrefix int
Key KeyConfig
// TODO(jsha): Remove Key field once we've migrated to Issuers
Key *IssuerConfig
// 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
// LifespanOCSP is how long OCSP responses are valid for; It should be longer
// than the minTimeToExpiry field for the OCSP Updater.
LifespanOCSP string
LifespanOCSP ConfigDuration
// How long issued certificates are valid for, should match expiry field
// in cfssl config.
Expiry string
@ -368,13 +371,15 @@ func (pc PAConfig) CheckChallenges() error {
return nil
}
// KeyConfig should contain either a File path to a PEM-format private key,
// 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 KeyConfig struct {
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
}
// TLSConfig reprents certificates and a key for authenticated TLS.

25
test/test-ca2.pem Normal file
View File

@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIERTCCAy2gAwIBAgICElowDQYJKoZIhvcNAQELBQAwKzEpMCcGA1UEAwwgY2Fj
a2xpbmcgY3J5cHRvZ3JhcGhlciBmYWtlIFJPT1QwHhcNMTYwMzIyMDI0NzUyWhcN
MjEwMzIxMDI0NzUyWjAfMR0wGwYDVQQDDBRoMnBweSBoMmNrZXIgZmFrZSBDQTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMIKR3maBcUSsncXYzQT13D5
Nr+Z3mLxMMh3TUdt6sACmqbJ0btRlgXfMtNLM2OU1I6a3Ju+tIZSdn2v21JBwvxU
zpZQ4zy2cimIiMQDZCQHJwzC9GZn8HaW091iz9H0Go3A7WDXwYNmsdLNRi00o14U
joaVqaPsYrZWvRKaIRqaU0hHmS0AWwQSvN/93iMIXuyiwywmkwKbWnnxCQ/gsctK
FUtcNrwEx9Wgj6KlhwDTyI1QWSBbxVYNyUgPFzKxrSmwMO0yNff7ho+QT9x5+Y/7
XE59S4Mc4ZXxcXKew/gSlN9U5mvT+D2BhDtkCupdfsZNCQWp27A+b/DmrFI9NqsC
AwEAAaOCAX0wggF5MBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGG
MH8GCCsGAQUFBwEBBHMwcTAyBggrBgEFBQcwAYYmaHR0cDovL2lzcmcudHJ1c3Rp
ZC5vY3NwLmlkZW50cnVzdC5jb20wOwYIKwYBBQUHMAKGL2h0dHA6Ly9hcHBzLmlk
ZW50cnVzdC5jb20vcm9vdHMvZHN0cm9vdGNheDMucDdjMB8GA1UdIwQYMBaAFOmk
P+6epeby1dd5YDyTpi4kjpeqMFQGA1UdIARNMEswCAYGZ4EMAQIBMD8GCysGAQQB
gt8TAQEBMDAwLgYIKwYBBQUHAgEWImh0dHA6Ly9jcHMucm9vdC14MS5sZXRzZW5j
cnlwdC5vcmcwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5pZGVudHJ1c3Qu
Y29tL0RTVFJPT1RDQVgzQ1JMLmNybDAdBgNVHQ4EFgQU+3hPEvlgFYMsnxd/NBmz
LjbqQYkwDQYJKoZIhvcNAQELBQADggEBAKvePfYXBaAcYca2e0WwkswwJ7lLU/i3
GIFM8tErKThNf3gD3KdCtDZ45XomOsgdRv8oxYTvQpBGTclYRAqLsO9t/LgGxeSB
jzwY7Ytdwwj8lviEGtiun06sJxRvvBU+l9uTs3DKBxWKZ/YRf4+6wq/vERrShpEC
KuQ5+NgMcStQY7dywrsd6x1p3bkOvowbDlaRwru7QCIXTBSb8TepKqCqRzr6YREt
doIw2FE8MKMCGR2p+U3slhxfLTh13MuqIOvTuA145S/qf6xCkRc9I92GpjoQk87Z
v1uhpkgT9uwbRw0Cs5DMdxT/LgIUSfUTKU83GNrbrQNYinkJ77i6wG0=
-----END CERTIFICATE-----