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:
parent
3f8ed51ba4
commit
f49028107e
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
Binary file not shown.
|
@ -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)
|
||||
|
|
|
@ -289,6 +289,8 @@ type CAConfig struct {
|
|||
DBConfig
|
||||
|
||||
Profile string
|
||||
RSAProfile string
|
||||
ECDSAProfile string
|
||||
TestMode bool
|
||||
SerialPrefix int
|
||||
Key KeyConfig
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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": {
|
||||
|
|
Loading…
Reference in New Issue