X Tutup
Skip to content

Commit f490281

Browse files
committed
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 letsencrypt#1384
1 parent 3f8ed51 commit f490281

File tree

7 files changed

+153
-11
lines changed

7 files changed

+153
-11
lines changed

ca/certificate-authority.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ package ca
77

88
import (
99
"crypto"
10+
"crypto/ecdsa"
1011
"crypto/rand"
12+
"crypto/rsa"
1113
"crypto/x509"
1214
"encoding/hex"
1315
"encoding/json"
@@ -59,7 +61,8 @@ const (
5961
// CertificateAuthorityImpl represents a CA that signs certificates, CRLs, and
6062
// OCSP responses.
6163
type CertificateAuthorityImpl struct {
62-
profile string
64+
rsaProfile string
65+
ecdsaProfile string
6366
signer signer.Signer
6467
ocspSigner ocsp.Signer
6568
SA core.StorageAuthority
@@ -134,10 +137,26 @@ func NewCertificateAuthorityImpl(
134137
return nil, err
135138
}
136139

140+
rsaProfile := config.RSAProfile
141+
ecdsaProfile := config.ECDSAProfile
142+
if config.Profile != "" {
143+
if rsaProfile != "" || ecdsaProfile != "" {
144+
return nil, errors.New("either specify profile or rsaProfile and ecdsaProfile, but not both")
145+
}
146+
147+
rsaProfile = config.Profile
148+
ecdsaProfile = config.Profile
149+
}
150+
151+
if rsaProfile == "" || ecdsaProfile == "" {
152+
return nil, errors.New("must specify rsaProfile and ecdsaProfile")
153+
}
154+
137155
ca = &CertificateAuthorityImpl{
138156
signer: signer,
139157
ocspSigner: ocspSigner,
140-
profile: config.Profile,
158+
rsaProfile: rsaProfile,
159+
ecdsaProfile: ecdsaProfile,
141160
prefix: config.SerialPrefix,
142161
clk: clk,
143162
log: logger,
@@ -325,10 +344,23 @@ func (ca *CertificateAuthorityImpl) IssueCertificate(csr x509.CertificateRequest
325344
serialBigInt := big.NewInt(0)
326345
serialBigInt = serialBigInt.SetBytes(serialBytes)
327346

347+
var profile string
348+
switch key.(type) {
349+
case *rsa.PublicKey:
350+
profile = ca.rsaProfile
351+
case *ecdsa.PublicKey:
352+
profile = ca.ecdsaProfile
353+
default:
354+
err = core.InternalServerError(fmt.Sprintf("unsupported key type %T", key))
355+
// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
356+
ca.log.AuditErr(err)
357+
return emptyCert, err
358+
}
359+
328360
// Send the cert off for signing
329361
req := signer.SignRequest{
330362
Request: csrPEM,
331-
Profile: ca.profile,
363+
Profile: profile,
332364
Hosts: hostNames,
333365
Subject: &signer.Subject{
334366
CN: commonName,

ca/certificate-authority_test.go

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,18 @@ var (
9696
// Edited signature to become invalid.
9797
WrongSignatureCSR = mustRead("./testdata/invalid_signature.der.csr")
9898

99+
// CSR generated by Go:
100+
// * Random ECDSA public key.
101+
// * CN = [none]
102+
// * DNSNames = example.com, example2.com
103+
ECDSACSR = mustRead("./testdata/ecdsa.der.csr")
104+
99105
log = mocks.UseMockLog()
100106
)
101107

102108
// CFSSL config
103-
const profileName = "ee"
109+
const rsaProfileName = "rsaEE"
110+
const ecdsaProfileName = "ecdsaEE"
104111
const caKeyFile = "../test/test-ca.key"
105112
const caCertFile = "../test/test-ca.pem"
106113

@@ -168,7 +175,8 @@ func setup(t *testing.T) *testCtx {
168175

169176
// Create a CA
170177
caConfig := cmd.CAConfig{
171-
Profile: profileName,
178+
RSAProfile: rsaProfileName,
179+
ECDSAProfile: ecdsaProfileName,
172180
SerialPrefix: 17,
173181
Expiry: "8760h",
174182
LifespanOCSP: "45m",
@@ -177,8 +185,28 @@ func setup(t *testing.T) *testCtx {
177185
CFSSL: cfsslConfig.Config{
178186
Signing: &cfsslConfig.Signing{
179187
Profiles: map[string]*cfsslConfig.SigningProfile{
180-
profileName: &cfsslConfig.SigningProfile{
181-
Usage: []string{"server auth"},
188+
rsaProfileName: &cfsslConfig.SigningProfile{
189+
Usage: []string{"digital signature", "key encipherment", "server auth"},
190+
CA: false,
191+
IssuerURL: []string{"http://not-example.com/issuer-url"},
192+
OCSP: "http://not-example.com/ocsp",
193+
CRL: "http://not-example.com/crl",
194+
195+
Policies: []cfsslConfig.CertificatePolicy{
196+
cfsslConfig.CertificatePolicy{
197+
ID: cfsslConfig.OID(asn1.ObjectIdentifier{2, 23, 140, 1, 2, 1}),
198+
},
199+
},
200+
ExpiryString: "8760h",
201+
Backdate: time.Hour,
202+
CSRWhitelist: &cfsslConfig.CSRWhitelist{
203+
PublicKeyAlgorithm: true,
204+
PublicKey: true,
205+
SignatureAlgorithm: true,
206+
},
207+
},
208+
ecdsaProfileName: &cfsslConfig.SigningProfile{
209+
Usage: []string{"digital signature", "server auth"},
182210
CA: false,
183211
IssuerURL: []string{"http://not-example.com/issuer-url"},
184212
OCSP: "http://not-example.com/ocsp",
@@ -463,6 +491,40 @@ func TestWrongSignature(t *testing.T) {
463491
}
464492
}
465493

494+
func TestProfileSelection(t *testing.T) {
495+
ctx := setup(t)
496+
defer ctx.cleanUp()
497+
ctx.caConfig.MaxNames = 3
498+
ca, _ := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, ctx.stats, caCert, caKey, ctx.keyPolicy)
499+
ca.Publisher = &mocks.Publisher{}
500+
ca.PA = ctx.pa
501+
ca.SA = ctx.sa
502+
503+
testCases := []struct {
504+
CSR []byte
505+
ExpectedKeyUsage x509.KeyUsage
506+
}{
507+
{CNandSANCSR, x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment},
508+
{ECDSACSR, x509.KeyUsageDigitalSignature},
509+
}
510+
511+
for _, testCase := range testCases {
512+
csr, err := x509.ParseCertificateRequest(testCase.CSR)
513+
test.AssertNotError(t, err, "Cannot parse CSR")
514+
515+
// Sign CSR
516+
issuedCert, err := ca.IssueCertificate(*csr, ctx.reg.ID)
517+
test.AssertNotError(t, err, "Failed to sign certificate")
518+
519+
// Verify cert contents
520+
cert, err := x509.ParseCertificate(issuedCert.DER)
521+
test.AssertNotError(t, err, "Certificate failed to parse")
522+
523+
t.Logf("expected key usage %v, got %v", testCase.ExpectedKeyUsage, cert.KeyUsage)
524+
test.AssertEquals(t, cert.KeyUsage, testCase.ExpectedKeyUsage)
525+
}
526+
}
527+
466528
func TestHSMFaultTimeout(t *testing.T) {
467529
ctx := setup(t)
468530
defer ctx.cleanUp()

ca/testdata/ecdsa.der.csr

245 Bytes
Binary file not shown.

ca/testdata/testcsr.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
)
1313

1414
// A 2048-bit RSA private key
15-
var pemPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
15+
var rsaPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
1616
MIIEowIBAAKCAQEA5cpXqfCaUDD+hf93j5jxbrhK4jrJAzfAEjeZj/Lx5Rv/7eEO
1717
uhS2DdCU2is82vR6yJ7EidUYVz/nUAjSTP7JIEsbyvfsfACABbqRyGltHlJnULVH
1818
y/EMjt9xKZf17T8tOLHVUEAJTxsvjKn4TMIQJTNrAqm/lNrUXmCIR41Go+3RBGC6
@@ -40,8 +40,15 @@ Wi8EsQKBgG8iGy3+kVBIjKHxrN5jVs3vj/l/fQL0WRMLCMmVuDBfsKyy3f9n8R1B
4040
A2NgiQ+UeWMia16dZVd6gGDlY3lQpeyLdsdDd+YppNfy9vedjbvT
4141
-----END RSA PRIVATE KEY-----`
4242

43+
// NISTP256 ECDSA private key
44+
var ecdsaPrivateKey = `-----BEGIN EC PRIVATE KEY-----
45+
MHcCAQEEIKwK8ik0Zgw26bWaGuNYa/QAtCDRwpOPS5FIhbwuFqWuoAoGCCqGSM49
46+
AwEHoUQDQgAEfkxXCNEy4/zfwQ4arciDYQql7/+ftYvf51JTLCJAFu8kWKvNBENT
47+
X8ays994FANu2VsJTF5Ud5JPYWHT87hjAA==
48+
-----END EC PRIVATE KEY-----`
49+
4350
func main() {
44-
block, _ := pem.Decode([]byte(pemPrivateKey))
51+
block, _ := pem.Decode([]byte(rsaPrivateKey))
4552
rsaPriv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
4653
if err != nil {
4754
log.Fatalf("Failed to parse private key: %s", err)

cmd/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,8 @@ type CAConfig struct {
289289
DBConfig
290290

291291
Profile string
292+
RSAProfile string
293+
ECDSAProfile string
292294
TestMode bool
293295
SerialPrefix int
294296
Key KeyConfig

ra/registration-authority_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,8 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
206206
LifespanOCSP: "1h",
207207
Expiry: "1h",
208208
CFSSL: cfsslC,
209+
RSAProfile: "rsaEE",
210+
ECDSAProfile: "ecdsaEE",
209211
}
210212
paDbMap, err := sa.NewDbMap(vars.DBConnPolicy)
211213
if err != nil {

test/boulder-config.json

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636

3737
"ca": {
3838
"serialPrefix": 255,
39-
"profile": "ee",
39+
"rsaProfile": "rsaEE",
40+
"ecdsaProfile": "ecdsaEE",
4041
"debugAddr": "localhost:8001",
4142
"Key": {
4243
"File": "test/test-ca.key"
@@ -47,7 +48,7 @@
4748
"cfssl": {
4849
"signing": {
4950
"profiles": {
50-
"ee": {
51+
"rsaEE": {
5152
"usages": [
5253
"digital signature",
5354
"key encipherment",
@@ -83,6 +84,42 @@
8384
"SignatureAlgorithm": true
8485
},
8586
"ClientProvidesSerialNumbers": true
87+
},
88+
"ecdsaEE": {
89+
"usages": [
90+
"digital signature",
91+
"server auth",
92+
"client auth"
93+
],
94+
"backdate": "1h",
95+
"is_ca": false,
96+
"issuer_urls": [
97+
"http://127.0.0.1:4000/acme/issuer-cert"
98+
],
99+
"ocsp_url": "http://127.0.0.1:4002/",
100+
"crl_url": "http://example.com/crl",
101+
"policies": [
102+
{
103+
"ID": "2.23.140.1.2.1"
104+
},
105+
{
106+
"ID": "1.2.3.4",
107+
"Qualifiers": [ {
108+
"type": "id-qt-cps",
109+
"value": "http://example.com/cps"
110+
}, {
111+
"type": "id-qt-unotice",
112+
"value": "Do What Thou Wilt"
113+
} ]
114+
}
115+
],
116+
"expiry": "2160h",
117+
"CSRWhitelist": {
118+
"PublicKeyAlgorithm": true,
119+
"PublicKey": true,
120+
"SignatureAlgorithm": true
121+
},
122+
"ClientProvidesSerialNumbers": true
86123
}
87124
},
88125
"default": {

0 commit comments

Comments
 (0)
X Tutup