Allow CFSSL profiles to be selected by key type

Allows multiple CFSSL profiles to be defined. A profile is selected by
key type. ECDSA keys get one profile, RSA keys get another.

Either the "profile" config option or the "rsaProfile" and
"ecdsaProfile" config options must be specified. Both cannot be
specified. Specifying "profile" uses the same profile for RSA and ECDSA.

Fixes #1384
This commit is contained in:
Hugo Landau 2016-01-20 19:40:48 +00:00
parent 3f8ed51ba4
commit f49028107e
7 changed files with 153 additions and 11 deletions

View File

@ -7,7 +7,9 @@ package ca
import (
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/hex"
"encoding/json"
@ -59,7 +61,8 @@ const (
// CertificateAuthorityImpl represents a CA that signs certificates, CRLs, and
// OCSP responses.
type CertificateAuthorityImpl struct {
profile string
rsaProfile string
ecdsaProfile string
signer signer.Signer
ocspSigner ocsp.Signer
SA core.StorageAuthority
@ -134,10 +137,26 @@ func NewCertificateAuthorityImpl(
return nil, err
}
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,
profile: config.Profile,
rsaProfile: rsaProfile,
ecdsaProfile: ecdsaProfile,
prefix: config.SerialPrefix,
clk: clk,
log: logger,
@ -325,10 +344,23 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
serialBigInt := big.NewInt(0)
serialBigInt = serialBigInt.SetBytes(serialBytes)
var profile string
switch key.(type) {
case *rsa.PublicKey:
profile = ca.rsaProfile
case *ecdsa.PublicKey:
profile = ca.ecdsaProfile
default:
err = core.InternalServerError(fmt.Sprintf("unsupported key type %T", key))
// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
ca.log.AuditErr(err)
return emptyCert, err
}
// Send the cert off for signing
req := signer.SignRequest{
Request: csrPEM,
Profile: ca.profile,
Profile: profile,
Hosts: hostNames,
Subject: &signer.Subject{
CN: commonName,

View File

@ -96,11 +96,18 @@ var (
// Edited signature to become invalid.
WrongSignatureCSR = mustRead("./testdata/invalid_signature.der.csr")
// CSR generated by Go:
// * Random ECDSA public key.
// * CN = [none]
// * DNSNames = example.com, example2.com
ECDSACSR = mustRead("./testdata/ecdsa.der.csr")
log = mocks.UseMockLog()
)
// CFSSL config
const profileName = "ee"
const rsaProfileName = "rsaEE"
const ecdsaProfileName = "ecdsaEE"
const caKeyFile = "../test/test-ca.key"
const caCertFile = "../test/test-ca.pem"
@ -168,7 +175,8 @@ func setup(t *testing.T) *testCtx {
// Create a CA
caConfig := cmd.CAConfig{
Profile: profileName,
RSAProfile: rsaProfileName,
ECDSAProfile: ecdsaProfileName,
SerialPrefix: 17,
Expiry: "8760h",
LifespanOCSP: "45m",
@ -177,8 +185,28 @@ func setup(t *testing.T) *testCtx {
CFSSL: cfsslConfig.Config{
Signing: &cfsslConfig.Signing{
Profiles: map[string]*cfsslConfig.SigningProfile{
profileName: &cfsslConfig.SigningProfile{
Usage: []string{"server auth"},
rsaProfileName: &cfsslConfig.SigningProfile{
Usage: []string{"digital signature", "key encipherment", "server auth"},
CA: false,
IssuerURL: []string{"http://not-example.com/issuer-url"},
OCSP: "http://not-example.com/ocsp",
CRL: "http://not-example.com/crl",
Policies: []cfsslConfig.CertificatePolicy{
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,
},
},
ecdsaProfileName: &cfsslConfig.SigningProfile{
Usage: []string{"digital signature", "server auth"},
CA: false,
IssuerURL: []string{"http://not-example.com/issuer-url"},
OCSP: "http://not-example.com/ocsp",
@ -463,6 +491,40 @@ func TestWrongSignature(t *testing.T) {
}
}
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.Publisher = &mocks.Publisher{}
ca.PA = ctx.pa
ca.SA = ctx.sa
testCases := []struct {
CSR []byte
ExpectedKeyUsage x509.KeyUsage
}{
{CNandSANCSR, x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment},
{ECDSACSR, x509.KeyUsageDigitalSignature},
}
for _, testCase := range testCases {
csr, err := x509.ParseCertificateRequest(testCase.CSR)
test.AssertNotError(t, err, "Cannot parse CSR")
// Sign CSR
issuedCert, err := ca.IssueCertificate(*csr, ctx.reg.ID)
test.AssertNotError(t, err, "Failed to sign certificate")
// Verify cert contents
cert, err := x509.ParseCertificate(issuedCert.DER)
test.AssertNotError(t, err, "Certificate failed to parse")
t.Logf("expected key usage %v, got %v", testCase.ExpectedKeyUsage, cert.KeyUsage)
test.AssertEquals(t, cert.KeyUsage, testCase.ExpectedKeyUsage)
}
}
func TestHSMFaultTimeout(t *testing.T) {
ctx := setup(t)
defer ctx.cleanUp()

BIN
ca/testdata/ecdsa.der.csr vendored Normal file

Binary file not shown.

View File

@ -12,7 +12,7 @@ import (
)
// A 2048-bit RSA private key
var pemPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
var rsaPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA5cpXqfCaUDD+hf93j5jxbrhK4jrJAzfAEjeZj/Lx5Rv/7eEO
uhS2DdCU2is82vR6yJ7EidUYVz/nUAjSTP7JIEsbyvfsfACABbqRyGltHlJnULVH
y/EMjt9xKZf17T8tOLHVUEAJTxsvjKn4TMIQJTNrAqm/lNrUXmCIR41Go+3RBGC6
@ -40,8 +40,15 @@ Wi8EsQKBgG8iGy3+kVBIjKHxrN5jVs3vj/l/fQL0WRMLCMmVuDBfsKyy3f9n8R1B
A2NgiQ+UeWMia16dZVd6gGDlY3lQpeyLdsdDd+YppNfy9vedjbvT
-----END RSA PRIVATE KEY-----`
// NISTP256 ECDSA private key
var ecdsaPrivateKey = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIKwK8ik0Zgw26bWaGuNYa/QAtCDRwpOPS5FIhbwuFqWuoAoGCCqGSM49
AwEHoUQDQgAEfkxXCNEy4/zfwQ4arciDYQql7/+ftYvf51JTLCJAFu8kWKvNBENT
X8ays994FANu2VsJTF5Ud5JPYWHT87hjAA==
-----END EC PRIVATE KEY-----`
func main() {
block, _ := pem.Decode([]byte(pemPrivateKey))
block, _ := pem.Decode([]byte(rsaPrivateKey))
rsaPriv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
log.Fatalf("Failed to parse private key: %s", err)

View File

@ -289,6 +289,8 @@ type CAConfig struct {
DBConfig
Profile string
RSAProfile string
ECDSAProfile string
TestMode bool
SerialPrefix int
Key KeyConfig

View File

@ -206,6 +206,8 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
LifespanOCSP: "1h",
Expiry: "1h",
CFSSL: cfsslC,
RSAProfile: "rsaEE",
ECDSAProfile: "ecdsaEE",
}
paDbMap, err := sa.NewDbMap(vars.DBConnPolicy)
if err != nil {

View File

@ -36,7 +36,8 @@
"ca": {
"serialPrefix": 255,
"profile": "ee",
"rsaProfile": "rsaEE",
"ecdsaProfile": "ecdsaEE",
"debugAddr": "localhost:8001",
"Key": {
"File": "test/test-ca.key"
@ -47,7 +48,7 @@
"cfssl": {
"signing": {
"profiles": {
"ee": {
"rsaEE": {
"usages": [
"digital signature",
"key encipherment",
@ -83,6 +84,42 @@
"SignatureAlgorithm": true
},
"ClientProvidesSerialNumbers": true
},
"ecdsaEE": {
"usages": [
"digital signature",
"server auth",
"client auth"
],
"backdate": "1h",
"is_ca": false,
"issuer_urls": [
"http://127.0.0.1:4000/acme/issuer-cert"
],
"ocsp_url": "http://127.0.0.1:4002/",
"crl_url": "http://example.com/crl",
"policies": [
{
"ID": "2.23.140.1.2.1"
},
{
"ID": "1.2.3.4",
"Qualifiers": [ {
"type": "id-qt-cps",
"value": "http://example.com/cps"
}, {
"type": "id-qt-unotice",
"value": "Do What Thou Wilt"
} ]
}
],
"expiry": "2160h",
"CSRWhitelist": {
"PublicKeyAlgorithm": true,
"PublicKey": true,
"SignatureAlgorithm": true
},
"ClientProvidesSerialNumbers": true
}
},
"default": {