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:
parent
c7ba6f13d2
commit
a3d044f681
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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-----
|
Loading…
Reference in New Issue