Allow PKCS11 config to be loaded from a file.
This allows secret values (PIN) to be separated from the main config. Part of #1157. In the process, move the CA constructor in the direction of https://github.com/letsencrypt/boulder/wiki/Config-plan: - Make most fields private. - Take private key and issuer cert as constructor arguments rather than constructing them internally. This allows the CA test to parse the private key and issuer cert once, rather than once per test case.
This commit is contained in:
parent
fab8bec525
commit
3ede9b7223
|
@ -14,7 +14,6 @@ import (
|
|||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -27,8 +26,6 @@ import (
|
|||
blog "github.com/letsencrypt/boulder/log"
|
||||
|
||||
cfsslConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/local"
|
||||
|
@ -63,18 +60,18 @@ const (
|
|||
// OCSP responses.
|
||||
type CertificateAuthorityImpl struct {
|
||||
profile string
|
||||
Signer signer.Signer
|
||||
OCSPSigner ocsp.Signer
|
||||
signer signer.Signer
|
||||
ocspSigner ocsp.Signer
|
||||
SA core.StorageAuthority
|
||||
PA core.PolicyAuthority
|
||||
Publisher core.Publisher
|
||||
Clk clock.Clock // TODO(jmhodges): should be private, like log
|
||||
clk clock.Clock // TODO(jmhodges): should be private, like log
|
||||
log *blog.AuditLogger
|
||||
stats statsd.Statter
|
||||
Prefix int // Prepended to the serial number
|
||||
ValidityPeriod time.Duration
|
||||
NotAfter time.Time
|
||||
MaxNames int
|
||||
prefix int // Prepended to the serial number
|
||||
validityPeriod time.Duration
|
||||
notAfter time.Time
|
||||
maxNames int
|
||||
|
||||
hsmFaultLock sync.Mutex
|
||||
hsmFaultLastObserved time.Time
|
||||
|
@ -87,7 +84,13 @@ type CertificateAuthorityImpl struct {
|
|||
// 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.
|
||||
func NewCertificateAuthorityImpl(config cmd.CAConfig, clk clock.Clock, stats statsd.Statter, issuerCert string) (*CertificateAuthorityImpl, error) {
|
||||
func NewCertificateAuthorityImpl(
|
||||
config cmd.CAConfig,
|
||||
clk clock.Clock,
|
||||
stats statsd.Statter,
|
||||
issuer *x509.Certificate,
|
||||
privateKey crypto.Signer,
|
||||
) (*CertificateAuthorityImpl, error) {
|
||||
var ca *CertificateAuthorityImpl
|
||||
var err error
|
||||
logger := blog.GetAuditLogger()
|
||||
|
@ -109,18 +112,7 @@ func NewCertificateAuthorityImpl(config cmd.CAConfig, clk clock.Clock, stats sta
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Load the private key, which can be a file or a PKCS#11 key.
|
||||
priv, err := loadKey(config.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
issuer, err := core.LoadCert(issuerCert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signer, err := local.NewSigner(priv, issuer, x509.SHA256WithRSA, cfsslConfigObj.Signing)
|
||||
signer, err := local.NewSigner(privateKey, issuer, x509.SHA256WithRSA, cfsslConfigObj.Signing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -135,61 +127,36 @@ func NewCertificateAuthorityImpl(config cmd.CAConfig, clk clock.Clock, stats sta
|
|||
|
||||
// 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, priv, lifespanOCSP)
|
||||
ocspSigner, err := ocsp.NewSigner(issuer, issuer, privateKey, lifespanOCSP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ca = &CertificateAuthorityImpl{
|
||||
Signer: signer,
|
||||
OCSPSigner: ocspSigner,
|
||||
signer: signer,
|
||||
ocspSigner: ocspSigner,
|
||||
profile: config.Profile,
|
||||
Prefix: config.SerialPrefix,
|
||||
Clk: clk,
|
||||
prefix: config.SerialPrefix,
|
||||
clk: clk,
|
||||
log: logger,
|
||||
stats: stats,
|
||||
NotAfter: issuer.NotAfter,
|
||||
notAfter: issuer.NotAfter,
|
||||
hsmFaultTimeout: config.HSMFaultTimeout.Duration,
|
||||
}
|
||||
|
||||
if config.Expiry == "" {
|
||||
return nil, errors.New("Config must specify an expiry period.")
|
||||
}
|
||||
ca.ValidityPeriod, err = time.ParseDuration(config.Expiry)
|
||||
ca.validityPeriod, err = time.ParseDuration(config.Expiry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ca.MaxNames = config.MaxNames
|
||||
ca.maxNames = config.MaxNames
|
||||
|
||||
return ca, nil
|
||||
}
|
||||
|
||||
func loadKey(keyConfig cmd.KeyConfig) (priv crypto.Signer, err error) {
|
||||
if keyConfig.File != "" {
|
||||
var keyBytes []byte
|
||||
keyBytes, err = ioutil.ReadFile(keyConfig.File)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not read key file %s", keyConfig.File)
|
||||
}
|
||||
|
||||
priv, err = helpers.ParsePrivateKeyPEM(keyBytes)
|
||||
return
|
||||
}
|
||||
|
||||
pkcs11Config := keyConfig.PKCS11
|
||||
if pkcs11Config.Module == "" ||
|
||||
pkcs11Config.TokenLabel == "" ||
|
||||
pkcs11Config.PIN == "" ||
|
||||
pkcs11Config.PrivateKeyLabel == "" {
|
||||
err = fmt.Errorf("Missing a field in pkcs11Config %#v", pkcs11Config)
|
||||
return
|
||||
}
|
||||
priv, err = pkcs11key.New(pkcs11Config.Module,
|
||||
pkcs11Config.TokenLabel, pkcs11Config.PIN, pkcs11Config.PrivateKeyLabel)
|
||||
return
|
||||
}
|
||||
|
||||
// checkHSMFault checks whether there has been an HSM fault observed within the
|
||||
// timeout window. CA methods that use the HSM should call this method right
|
||||
// away, to minimize the performance impact of HSM outages.
|
||||
|
@ -202,7 +169,7 @@ func (ca *CertificateAuthorityImpl) checkHSMFault() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
now := ca.Clk.Now()
|
||||
now := ca.clk.Now()
|
||||
timeout := ca.hsmFaultLastObserved.Add(ca.hsmFaultTimeout)
|
||||
if now.Before(timeout) {
|
||||
err := core.ServiceUnavailableError("HSM is unavailable")
|
||||
|
@ -221,7 +188,7 @@ func (ca *CertificateAuthorityImpl) noteHSMFault(err error) {
|
|||
|
||||
if err != nil {
|
||||
ca.stats.Inc(metricHSMFaultObserved, 1, 1.0)
|
||||
ca.hsmFaultLastObserved = ca.Clk.Now()
|
||||
ca.hsmFaultLastObserved = ca.clk.Now()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -246,7 +213,7 @@ func (ca *CertificateAuthorityImpl) GenerateOCSP(xferObj core.OCSPSigningRequest
|
|||
RevokedAt: xferObj.RevokedAt,
|
||||
}
|
||||
|
||||
ocspResponse, err := ca.OCSPSigner.Sign(signRequest)
|
||||
ocspResponse, err := ca.ocspSigner.Sign(signRequest)
|
||||
ca.noteHSMFault(err)
|
||||
return ocspResponse, err
|
||||
}
|
||||
|
@ -307,8 +274,8 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
|
|||
|
||||
// Collapse any duplicate names. Note that this operation may re-order the names
|
||||
hostNames = core.UniqueLowerNames(hostNames)
|
||||
if ca.MaxNames > 0 && len(hostNames) > ca.MaxNames {
|
||||
err = core.MalformedRequestError(fmt.Sprintf("Certificate request has %d > %d names", len(hostNames), ca.MaxNames))
|
||||
if ca.maxNames > 0 && len(hostNames) > ca.maxNames {
|
||||
err = core.MalformedRequestError(fmt.Sprintf("Certificate request has %d names, maximum is %d.", len(hostNames), ca.maxNames))
|
||||
ca.log.WarningErr(err)
|
||||
return emptyCert, err
|
||||
}
|
||||
|
@ -331,9 +298,9 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
|
|||
}
|
||||
}
|
||||
|
||||
notAfter := ca.Clk.Now().Add(ca.ValidityPeriod)
|
||||
notAfter := ca.clk.Now().Add(ca.validityPeriod)
|
||||
|
||||
if ca.NotAfter.Before(notAfter) {
|
||||
if ca.notAfter.Before(notAfter) {
|
||||
err = core.InternalServerError("Cannot issue a certificate that expires after the intermediate certificate.")
|
||||
// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
|
||||
ca.log.AuditErr(err)
|
||||
|
@ -349,7 +316,7 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
|
|||
// We want 136 bits of random number, plus an 8-bit instance id prefix.
|
||||
const randBits = 136
|
||||
serialBytes := make([]byte, randBits/8+1)
|
||||
serialBytes[0] = byte(ca.Prefix)
|
||||
serialBytes[0] = byte(ca.prefix)
|
||||
_, err = rand.Read(serialBytes[1:])
|
||||
if err != nil {
|
||||
err = core.InternalServerError(err.Error())
|
||||
|
@ -372,7 +339,7 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
|
|||
Serial: serialBigInt,
|
||||
}
|
||||
|
||||
certPEM, err := ca.Signer.Sign(req)
|
||||
certPEM, err := ca.signer.Sign(req)
|
||||
ca.noteHSMFault(err)
|
||||
if err != nil {
|
||||
err = core.InternalServerError(err.Error())
|
||||
|
|
|
@ -7,6 +7,7 @@ package ca
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
|
@ -16,6 +17,7 @@ import (
|
|||
"time"
|
||||
|
||||
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/cmd"
|
||||
|
@ -116,6 +118,21 @@ type testCtx struct {
|
|||
cleanUp func()
|
||||
}
|
||||
|
||||
var caKey crypto.Signer
|
||||
var caCert *x509.Certificate
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
caKey, err = helpers.ParsePrivateKeyPEM(mustRead(caKeyFile))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to parse %s: %s", caKeyFile, err))
|
||||
}
|
||||
caCert, err = core.LoadCert(caCertFile)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to parse %s: %s", caCertFile, err))
|
||||
}
|
||||
}
|
||||
|
||||
func setup(t *testing.T) *testCtx {
|
||||
// Create an SA
|
||||
dbMap, err := sa.NewDbMap(vars.DBConnSA)
|
||||
|
@ -146,11 +163,8 @@ func setup(t *testing.T) *testCtx {
|
|||
|
||||
// Create a CA
|
||||
caConfig := cmd.CAConfig{
|
||||
Profile: profileName,
|
||||
SerialPrefix: 17,
|
||||
Key: cmd.KeyConfig{
|
||||
File: caKeyFile,
|
||||
},
|
||||
Profile: profileName,
|
||||
SerialPrefix: 17,
|
||||
Expiry: "8760h",
|
||||
LifespanOCSP: "45m",
|
||||
MaxNames: 2,
|
||||
|
@ -193,7 +207,15 @@ func setup(t *testing.T) *testCtx {
|
|||
|
||||
stats := mocks.NewStatter()
|
||||
|
||||
return &testCtx{ssa, caConfig, reg, pa, fc, &stats, cleanUp}
|
||||
return &testCtx{
|
||||
ssa,
|
||||
caConfig,
|
||||
reg,
|
||||
pa,
|
||||
fc,
|
||||
&stats,
|
||||
cleanUp,
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailNoSerial(t *testing.T) {
|
||||
|
@ -201,14 +223,14 @@ func TestFailNoSerial(t *testing.T) {
|
|||
defer ctx.cleanUp()
|
||||
|
||||
ctx.caConfig.SerialPrefix = 0
|
||||
_, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
|
||||
_, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
|
||||
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, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
|
@ -285,7 +307,7 @@ func TestIssueCertificate(t *testing.T) {
|
|||
func TestRejectNoName(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
|
@ -302,7 +324,7 @@ func TestRejectNoName(t *testing.T) {
|
|||
func TestRejectTooManyNames(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
|
@ -319,7 +341,7 @@ 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, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
|
@ -343,7 +365,7 @@ 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, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
|
||||
test.AssertNotError(t, err, "Failed to create CA")
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
|
@ -351,7 +373,7 @@ func TestRejectValidityTooLong(t *testing.T) {
|
|||
|
||||
// Test that the CA rejects CSRs that would expire after the intermediate cert
|
||||
csr, _ := x509.ParseCertificateRequest(NoCNCSR)
|
||||
ca.NotAfter = ctx.fc.Now()
|
||||
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.")
|
||||
_, ok := err.(core.InternalServerError)
|
||||
|
@ -361,7 +383,7 @@ 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, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
@ -377,7 +399,7 @@ func TestShortKey(t *testing.T) {
|
|||
func TestRejectBadAlgorithm(t *testing.T) {
|
||||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
@ -394,7 +416,7 @@ func TestCapitalizedLetters(t *testing.T) {
|
|||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
ctx.caConfig.MaxNames = 3
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
@ -415,7 +437,7 @@ func TestHSMFaultTimeout(t *testing.T) {
|
|||
ctx := setup(t)
|
||||
defer ctx.cleanUp()
|
||||
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCertFile)
|
||||
ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey)
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
ca.PA = ctx.pa
|
||||
ca.SA = ctx.sa
|
||||
|
@ -429,13 +451,13 @@ func TestHSMFaultTimeout(t *testing.T) {
|
|||
}
|
||||
|
||||
// Swap in a bad signer
|
||||
goodSigner := ca.Signer
|
||||
goodSigner := ca.signer
|
||||
badHSMErrorMessage := "This is really serious. You should wait"
|
||||
badSigner := mocks.BadHSMSigner(badHSMErrorMessage)
|
||||
badOCSPSigner := mocks.BadHSMOCSPSigner(badHSMErrorMessage)
|
||||
|
||||
// Cause the CA to enter the HSM fault condition
|
||||
ca.Signer = badSigner
|
||||
ca.signer = badSigner
|
||||
_, err = ca.IssueCertificate(*csr, ctx.reg.ID)
|
||||
test.AssertError(t, err, "CA failed to return HSM error")
|
||||
test.AssertEquals(t, err.Error(), badHSMErrorMessage)
|
||||
|
@ -450,7 +472,7 @@ func TestHSMFaultTimeout(t *testing.T) {
|
|||
test.AssertEquals(t, err.Error(), "HSM is unavailable")
|
||||
|
||||
// Swap in a good signer and move the clock forward to clear the fault
|
||||
ca.Signer = goodSigner
|
||||
ca.signer = goodSigner
|
||||
ctx.fc.Add(ca.hsmFaultTimeout)
|
||||
ctx.fc.Add(10 * time.Second)
|
||||
|
||||
|
@ -460,7 +482,7 @@ func TestHSMFaultTimeout(t *testing.T) {
|
|||
_, err = ca.GenerateOCSP(ocspRequest)
|
||||
|
||||
// Check that GenerateOCSP can also trigger an HSM failure, in the same way
|
||||
ca.OCSPSigner = badOCSPSigner
|
||||
ca.ocspSigner = badOCSPSigner
|
||||
_, err = ca.GenerateOCSP(ocspRequest)
|
||||
test.AssertError(t, err, "CA failed to return HSM error")
|
||||
test.AssertEquals(t, err.Error(), badHSMErrorMessage)
|
||||
|
|
|
@ -6,10 +6,18 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
|
||||
"github.com/letsencrypt/boulder/ca"
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/policy"
|
||||
"github.com/letsencrypt/boulder/rpc"
|
||||
|
@ -18,6 +26,40 @@ import (
|
|||
|
||||
const clientName = "CA"
|
||||
|
||||
func loadPrivateKey(keyConfig cmd.KeyConfig) (crypto.Signer, error) {
|
||||
if keyConfig.File != "" {
|
||||
keyBytes, err := ioutil.ReadFile(keyConfig.File)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not read key file %s", keyConfig.File)
|
||||
}
|
||||
|
||||
return helpers.ParsePrivateKeyPEM(keyBytes)
|
||||
}
|
||||
|
||||
var pkcs11Config *pkcs11key.Config
|
||||
if keyConfig.ConfigFile != "" {
|
||||
contents, err := ioutil.ReadFile(keyConfig.ConfigFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkcs11Config = new(pkcs11key.Config)
|
||||
err = json.Unmarshal(contents, pkcs11Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
pkcs11Config = keyConfig.PKCS11
|
||||
}
|
||||
if pkcs11Config.Module == "" ||
|
||||
pkcs11Config.TokenLabel == "" ||
|
||||
pkcs11Config.PIN == "" ||
|
||||
pkcs11Config.PrivateKeyLabel == "" {
|
||||
return nil, fmt.Errorf("Missing a field in pkcs11Config %#v", pkcs11Config)
|
||||
}
|
||||
return pkcs11key.New(pkcs11Config.Module,
|
||||
pkcs11Config.TokenLabel, pkcs11Config.PIN, pkcs11Config.PrivateKeyLabel)
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := cmd.NewAppShell("boulder-ca", "Handles issuance operations")
|
||||
app.Action = func(c cmd.Config, stats statsd.Statter, auditlogger *blog.AuditLogger) {
|
||||
|
@ -37,7 +79,18 @@ func main() {
|
|||
pa, err := policy.NewPolicyAuthorityImpl(paDbMap, c.PA.EnforcePolicyWhitelist, c.PA.Challenges)
|
||||
cmd.FailOnError(err, "Couldn't create PA")
|
||||
|
||||
cai, err := ca.NewCertificateAuthorityImpl(c.CA, clock.Default(), stats, c.Common.IssuerCert)
|
||||
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")
|
||||
|
||||
cai, err := ca.NewCertificateAuthorityImpl(
|
||||
c.CA,
|
||||
clock.Default(),
|
||||
stats,
|
||||
issuer,
|
||||
priv)
|
||||
cmd.FailOnError(err, "Failed to create CA impl")
|
||||
cai.PA = pa
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"time"
|
||||
|
||||
cfsslConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/crypto/pkcs11key"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/boulder/publisher"
|
||||
"github.com/letsencrypt/boulder/va"
|
||||
|
@ -263,16 +264,10 @@ func (pc *PAConfig) SetDefaultChallengesIfEmpty() {
|
|||
// KeyConfig 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 {
|
||||
File string
|
||||
PKCS11 PKCS11Config
|
||||
}
|
||||
|
||||
// PKCS11Config defines how to load a module for an HSM.
|
||||
type PKCS11Config struct {
|
||||
Module string
|
||||
TokenLabel string
|
||||
PIN string
|
||||
PrivateKeyLabel string
|
||||
// A file from which a pkcs11key.Config will be read and parsed, if present
|
||||
ConfigFile string
|
||||
File string
|
||||
PKCS11 *pkcs11key.Config
|
||||
}
|
||||
|
||||
// TLSConfig reprents certificates and a key for authenticated TLS.
|
||||
|
|
|
@ -20,8 +20,6 @@ import (
|
|||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
|
||||
cfsslConfig "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/local"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
|
||||
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
"github.com/letsencrypt/boulder/ca"
|
||||
|
@ -180,20 +178,26 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
|
|||
caKey, _ := x509.ParsePKCS1PrivateKey(caKeyPEM.Bytes)
|
||||
caCertPEM, _ := pem.Decode([]byte(CAcertPEM))
|
||||
caCert, _ := x509.ParseCertificate(caCertPEM.Bytes)
|
||||
basicPolicy := &cfsslConfig.Signing{
|
||||
Default: &cfsslConfig.SigningProfile{
|
||||
Usage: []string{"server auth", "client auth"},
|
||||
Expiry: 1 * time.Hour,
|
||||
CSRWhitelist: &cfsslConfig.CSRWhitelist{
|
||||
PublicKey: true,
|
||||
PublicKeyAlgorithm: true,
|
||||
SignatureAlgorithm: true,
|
||||
DNSNames: true,
|
||||
cfsslC := cfsslConfig.Config{
|
||||
Signing: &cfsslConfig.Signing{
|
||||
Default: &cfsslConfig.SigningProfile{
|
||||
Usage: []string{"server auth", "client auth"},
|
||||
ExpiryString: "1h",
|
||||
CSRWhitelist: &cfsslConfig.CSRWhitelist{
|
||||
PublicKey: true,
|
||||
PublicKeyAlgorithm: true,
|
||||
SignatureAlgorithm: true,
|
||||
DNSNames: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
signer, _ := local.NewSigner(caKey, caCert, x509.SHA256WithRSA, basicPolicy)
|
||||
ocspSigner, _ := ocsp.NewSigner(caCert, caCert, caKey, time.Hour)
|
||||
caConf := cmd.CAConfig{
|
||||
SerialPrefix: 10,
|
||||
LifespanOCSP: "1h",
|
||||
Expiry: "1h",
|
||||
CFSSL: cfsslC,
|
||||
}
|
||||
paDbMap, err := sa.NewDbMap(vars.DBConnPolicy)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create dbMap: %s", err)
|
||||
|
@ -201,16 +205,19 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
|
|||
policyDBCleanUp := test.ResetPolicyTestDatabase(t)
|
||||
pa, err := policy.NewPolicyAuthorityImpl(paDbMap, false, SupportedChallenges)
|
||||
test.AssertNotError(t, err, "Couldn't create PA")
|
||||
ca := ca.CertificateAuthorityImpl{
|
||||
Signer: signer,
|
||||
OCSPSigner: ocspSigner,
|
||||
SA: ssa,
|
||||
PA: pa,
|
||||
ValidityPeriod: time.Hour * 2190,
|
||||
NotAfter: time.Now().Add(time.Hour * 8761),
|
||||
Clk: fc,
|
||||
Publisher: &mocks.Publisher{},
|
||||
}
|
||||
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
|
||||
ca, err := ca.NewCertificateAuthorityImpl(
|
||||
caConf,
|
||||
fc,
|
||||
stats,
|
||||
caCert,
|
||||
caKey)
|
||||
test.AssertNotError(t, err, "Couldn't create CA")
|
||||
ca.SA = ssa
|
||||
ca.PA = pa
|
||||
ca.Publisher = &mocks.Publisher{}
|
||||
cleanUp := func() {
|
||||
saDBCleanUp()
|
||||
policyDBCleanUp()
|
||||
|
@ -224,7 +231,6 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
|
|||
InitialIP: net.ParseIP("3.2.3.3"),
|
||||
})
|
||||
|
||||
stats, _ := statsd.NewNoopClient()
|
||||
ra := NewRegistrationAuthorityImpl(fc,
|
||||
blog.GetAuditLogger(),
|
||||
stats,
|
||||
|
@ -237,7 +243,7 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
|
|||
}, 1)
|
||||
ra.SA = ssa
|
||||
ra.VA = va
|
||||
ra.CA = &ca
|
||||
ra.CA = ca
|
||||
ra.PA = pa
|
||||
ra.DNSResolver = &mocks.DNSResolver{}
|
||||
|
||||
|
|
Loading…
Reference in New Issue