Skip to content

Commit 96bbcdd

Browse files
jpage-godaddyindexzero
authored andcommitted
Support creating cert chains (#18)
* Support creating cert chains Sometimes, certs are signed by untrusted intermediate CAs, and a cert along with any intermediates needs to be sent during the TLS handshake. This adds a convenience so you can create a cert chain by using an array. This is distinct from supporting multiple site certificates, which can also themselves be served up as chains. * Remove accidental inclusion of key
1 parent f147418 commit 96bbcdd

File tree

7 files changed

+220
-9
lines changed

7 files changed

+220
-9
lines changed

README.md

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,62 @@ following properties:
4242

4343
`create-servers` provides some conveniences for `https.ca`, `https.key`, and
4444
`https.cert` config properties. You may use PEM data directly (inside a `Buffer`
45-
or string) or a file name. When using a file name, you may also set an
46-
`https.root` config property to enable using relative paths to cert/key files.
47-
`https.ca` and `https.cert` also support specifying an Array of certs/files.
45+
or string) or a file name. When using a file name, you must also set an
46+
`https.root` config property if using relative paths to cert/key files.
47+
48+
`https.ca`, `https.cert`, and `https.key` also support specifying an Array.
49+
Given an array for `cert`, you must have a matching array for `key` so each cert
50+
can be matched with its private key.
51+
52+
```js
53+
const createServers = require('create-servers');
54+
55+
createServers({
56+
https: {
57+
root: '/cert/path',
58+
cert: ['cert1.crt', 'cert2.crt'],
59+
key: ['cert1.key', 'cert2.key']
60+
}
61+
}, err => {
62+
// ...
63+
})
64+
```
65+
66+
If you have a cert that is signed by an intermediate CA, your server will need
67+
to append the untrusted parts of the CA chain with your cert. To make this more
68+
convenient, `create-servers` lets you use an array to automatically create a
69+
chain.
70+
71+
```js
72+
const createServers = require('create-servers');
73+
74+
createServers({
75+
https: {
76+
root: '/cert/path',
77+
cert: ['cert.crt', 'intermediate.crt'],
78+
key: 'cert.key'
79+
}
80+
}, err => {
81+
// ...
82+
})
83+
```
84+
85+
If you are specifying multiple certs _and_ you want to create chains for each,
86+
use an array of arrays.
87+
88+
```js
89+
const createServers = require('create-servers');
90+
91+
createServers({
92+
https: {
93+
root: '/cert/path',
94+
cert: [['cert1.crt', 'intermediate.crt'], 'cert2.crt'],
95+
key: ['cert1.key', 'cert2.key']
96+
}
97+
}, err => {
98+
// ...
99+
})
100+
```
48101

49102
## NOTE on Security
50103
Inspired by [`iojs`][iojs] and a well written [article][article], we have defaulted

index.js

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,9 @@ module.exports = function createServers(options, listening) {
149149
//
150150
// Load default SSL key, cert and ca(s).
151151
//
152-
key: normalizeCertFile(ssl.root, ssl.key),
153-
cert: normalizeCertFile(ssl.root, ssl.cert),
154-
ca: ca && ca.map(normalizeCertFile.bind(null, ssl.root)),
152+
key: normalizePEMContent(ssl.root, ssl.key),
153+
cert: normalizeCertContent(ssl.root, ssl.cert, ssl.key),
154+
ca: ca && ca.map(normalizePEMContent.bind(null, ssl.root)),
155155
//
156156
// Properly expose ciphers for an A+ SSL rating:
157157
// https://certsimple.com/blog/a-plus-node-js-ssl
@@ -183,15 +183,51 @@ module.exports = function createServers(options, listening) {
183183
.forEach(function (fn) { fn(); });
184184
};
185185

186+
function normalizeCertContent(root, cert, key) {
187+
// Node accepts an array of certs, which must match up with an array of keys.
188+
// The user may instead intend for an array passed into cert to represent
189+
// a cert chain they want to concatenate. Therefore, if key is not an array,
190+
// we'll assume the latter.
191+
if (Array.isArray(cert)) {
192+
if (Array.isArray(key)) {
193+
// This is an array of certs/chains with corresponding keys
194+
return normalizeCertChainList(root, cert);
195+
} else {
196+
// This is a single cert chain
197+
return normalizeCertChain(root, cert);
198+
}
199+
}
200+
201+
return normalizePEMContent(root, cert);
202+
}
203+
204+
function normalizeCertChainList(root, data) {
205+
// If this is an array, treat like an array of bundles, otherwise a single
206+
// bundle
207+
return Array.isArray(data)
208+
? data.map(function (item) {
209+
return normalizeCertChain(root, item);
210+
})
211+
: normalizePEMContent(root, data);
212+
}
213+
214+
function normalizeCertChain(root, data) {
215+
// A chain can be an array, which we concatenate together into one PEM,
216+
// an already-concatenated chain, or a single PEM
217+
218+
const content = normalizePEMContent(root, data);
219+
return Array.isArray(content) ? content.join('\n') : content;
220+
}
221+
186222
/**
187-
* function normalizeCertFile(root, file)
223+
* function normalizePEMContent(root, file)
188224
* Returns the contents of `file` verbatim if it is determined to be
189225
* certificate material and not a file path. Otherwise, returns the
190226
* certificate material read from that file path.
191227
*/
192-
function normalizeCertFile(root, file) {
228+
function normalizePEMContent(root, file) {
193229
if (Array.isArray(file)) return file.map(function map(item) {
194-
return normalizeCertFile(root, item)
230+
return normalizePEMContent(root, item)
195231
});
196232

197233
//

test/create-servers-test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,33 @@ test('supports cert array instead of strings', function (t) {
274274
});
275275
});
276276

277+
test('supports creating certificate chains', function (t) {
278+
t.plan(2);
279+
var root = path.join(__dirname, 'fixtures');
280+
var agent3Cert = fs.readFileSync(path.resolve(root, 'agent3-cert.pem'));
281+
var intermediate = fs.readFileSync(path.resolve(root, 'intermediate-cert.pem'));
282+
var spy = sinon.spy(https, 'createServer');
283+
createServers({
284+
log: console.log,
285+
https: {
286+
port: 3456,
287+
root: root,
288+
cert: ['agent3-cert.pem', 'intermediate-cert.pem'],
289+
key: 'agent3-key.pem'
290+
},
291+
handler: fend
292+
}, function (err, servers) {
293+
t.error(err);
294+
295+
const expectedBundle = [agent3Cert, intermediate].join('\n');
296+
const cert = spy.lastCall.args[0].cert;
297+
t.equals(cert, expectedBundle, 'should create a cert chain');
298+
299+
servers.https.close();
300+
spy.restore();
301+
});
302+
});
303+
277304
test('supports requestCert https option', function (t) {
278305
t.plan(2);
279306
var spy = sinon.spy(https, 'createServer');

test/fixtures/agent3-cert.pem

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIEIDCCAggCCQChRDh/XiBF+zANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQGEwJ1
3+
czETMBEGA1UECAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEeMBwGA1UE
4+
AwwVRHVtbXkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDYyMjIwMzEwNFoXDTMyMDIy
5+
OTIwMzEwNFowUDELMAkGA1UEBhMCdXMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAO
6+
BgNVBAcMB1NlYXR0bGUxGjAYBgNVBAMMEWR1bW15LmV4YW1wbGUuY29tMIIBIjAN
7+
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvSQq3d8AeZMTvtqZ13jWCckikyXJ
8+
SACvkGCQUCJqOceESbg6IHdRzQdoccE4P3sbvNsf9BlbdJKM+neCxabqKaU1PPje
9+
4P0tHT57t6yJrMuUh9NxEz3Bgh1srNHVS7saKvwHmcKm79jc+wxlioPmEQvQagjn
10+
y7oTkyLt0sn4LGxBjrcv2JoHOC9f1pxX7l47MaiN0/ctRau7Nr3PFn+pkB4Yf6Z0
11+
VyicVJbaUSz39Qo4HQWl1L2hiBP3CS1oKs2Yk0O1aOCMExWrhZQan+ZgHqL1rhgm
12+
kPpw2/zwwPt5Vf9CSakvHwg198EXuTTXtkzYduuIJAm8yp969iEIiG2xTwIDAQAB
13+
MA0GCSqGSIb3DQEBCwUAA4ICAQBnMSIo+kujkeXPh+iErFBmNtu/7EA+i/QnFPbN
14+
lSLngclYYBJAGQI+DhirJI8ghDi6vmlHB2THewDaOJXEKvC1czE8064wioIcA9HJ
15+
l3QJ3YYOFRctYdSHBU4TWdJbPgkLWDzYP5smjOfw8nDdr4WO/5jh9qRFcFpTFmQf
16+
DyU3xgWLsQnNK3qXLdJjWG75pEhHR+7TGo+Ob/RUho/1RX/P89Ux7/oVbzdKqqFu
17+
SErXAsjEIEFzWOM2uDOt6hrxDF6q+8/zudwQNEo422poEcTT9tDEFxMQ391CzZRi
18+
nozBm4igRn1f5S3YZzLI6VEUns0s76BNy2CzvFWn40DziTqNBExAMfFFj76wiMsX
19+
6fTIdcvkaTBa0S9SZB0vN99qahBdcG17rt4RssMHVRH1Wn7NXMwe476L0yXZ6gO7
20+
Z4uNAPxgaI3BRP75EPfslLutCLZ+BC4Zzu6MY0Salbpfl0Go462EhsKCxvYhE2Dg
21+
T477pICLfETZfA499Fd1tOaIsoLCrILAia/+Yd76uf94MuXUIqykDng/4H7xCc47
22+
BZhNFJiPC6XHaXzN7NYSEUNX9VOwY8ncxKwtP6TXga96PdMUy/p98KIM8RZlDoxB
23+
Xy9dcZBFNn/zrqjW7R0CCWCUriDIFSmEP0wDZ91YYa6BVuJMb5uL/USkTLpjZS4/
24+
HNGvug==
25+
-----END CERTIFICATE-----

test/fixtures/agent3-key.pem

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEpQIBAAKCAQEAvSQq3d8AeZMTvtqZ13jWCckikyXJSACvkGCQUCJqOceESbg6
3+
IHdRzQdoccE4P3sbvNsf9BlbdJKM+neCxabqKaU1PPje4P0tHT57t6yJrMuUh9Nx
4+
Ez3Bgh1srNHVS7saKvwHmcKm79jc+wxlioPmEQvQagjny7oTkyLt0sn4LGxBjrcv
5+
2JoHOC9f1pxX7l47MaiN0/ctRau7Nr3PFn+pkB4Yf6Z0VyicVJbaUSz39Qo4HQWl
6+
1L2hiBP3CS1oKs2Yk0O1aOCMExWrhZQan+ZgHqL1rhgmkPpw2/zwwPt5Vf9CSakv
7+
Hwg198EXuTTXtkzYduuIJAm8yp969iEIiG2xTwIDAQABAoIBAGPIw/C/qJF7HYyv
8+
6T+7GTiaa2o0IiehbP3/Y8NTFLWc49a8obXlHTvMr7Zr2I/tE+ojtIzkH9K1SjkN
9+
eelqsNj9tsOPDI6oIvftsflpxkxqLtclnt8m0oMhoObf4OaONDT/N8dP4SBiSdsM
10+
ZDmacnMFx5NZVWiup4sVf2CYexx7qks9FhyN2K5PArCQ4S9LHjFhSJVH4DSEpv7E
11+
Ykbp30rhpqV7wSwjgUsm8ZYvI2NOlmffzLSiPdt3vy2K5Q25S/MVEAicg83rfDgK
12+
6EluHjeygRI1xU6DJ0hU7tnU7zE9KURoHPUycO3BKzZnzUH26AA36I58Pu4fXWw/
13+
Cgmbv2ECgYEA+og9E4ziKCEi3p8gqjIfwTRgWZxDLjEzooB/K0UhEearn/xiX29A
14+
FiSzEHKfCB4uSrw5OENg2ckDs8uy08Qmxx7xFXL7AtufAl5fIYaWa0sNSqCaIk7p
15+
ebbUmPcaYhKiLzIEd1EYEL38sXVZ62wvSVMRSWvEMq44g1qnoRlDa/8CgYEAwUTt
16+
talYNwVmR9ZdkVEWm9ZxirdzoM6NaM6u4Tf34ygptpapdmIFSUhfq4iOiEnRGNg/
17+
tuNqhNCIb3LNpJbhRPEzqN7E7qiF/mp7AcJgbuxLZBm12QuLuJdG3nrisKPFXcY1
18+
lA4A7CFmNgH3E4THFfgwzyDXsBOxVLXleTqn+rECgYEA9up1P6J3dtOJuV2d5P/3
19+
ugRz/X173LfTSxJXw36jZDAy8D/feG19/RT4gnplcKvGNhQiVOhbOOnbw0U8n2fQ
20+
TCmbs+cZqyxnH/+AxNsPvvk+RVHZ93xMsY/XIldP4l65B8jFDA+Zp06IESI2mEeM
21+
pzi+bd1Phh+dRSCA2865W2MCgYEAlxYsgmQ1WyX0dFpHYU+zzfXRYzDQyrhOYc2Z
22+
duVK+yCto1iad7pfCY/zgmRJkI+sT7DV9kJIRjXDQuTLkEyHJF8vFGe6KhxCS8aw
23+
DIsI2g4NTd6vg1J8UryoIUqNpqsQoqNNxUVBQVdG0ReuMGsPO8R/W50AIFz0txVP
24+
o/rP0LECgYEA7e/mOwCnR+ovmS/CAksmos3oIqvkRkXNKpKe513FVmp3TpTU38ex
25+
cBkFNU3hEO31FyrX1hGIKp3N5mHYSQ1lyODHM6teHW0OLWWTwIe8rIGvR2jfRLe0
26+
bbkdj40atYVkfeFmpz9uHHG24CUYxJdPc360jbXTVp4i3q8zqgL5aMY=
27+
-----END RSA PRIVATE KEY-----

test/fixtures/intermediate-cert.pem

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIEHDCCAwQCCQCCsqUfs9TMATANBgkqhkiG9w0BAQsFADBMMQswCQYDVQQGEwJ1
3+
czETMBEGA1UECAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEWMBQGA1UE
4+
AwwNRHVtbXkgUm9vdCBDQTAeFw0xODA2MjIyMDI5MzlaFw0zMjAyMjkyMDI5Mzla
5+
MFQxCzAJBgNVBAYTAnVzMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdT
6+
ZWF0dGxlMR4wHAYDVQQDDBVEdW1teSBJbnRlcm1lZGlhdGUgQ0EwggIiMA0GCSqG
7+
SIb3DQEBAQUAA4ICDwAwggIKAoICAQCoqDdxHsQrj+6wtrK2BhLaCOcpdt6qm5jH
8+
IGrrustz+gYWzA7Xyc1UqINWKjeEl9/m1K5kP+tc+fHftWmVOYFo639nAlpuXgbH
9+
jpRX3FplTqeLRm+7rF8Z5bT6KDcyb1b7/W4HfKYaMbV29+nmjWB3tfjT6B3HNAZT
10+
O4SFnsEERQ+VnoMf8FPPMQM5nwwUvk/9zFs5SkwVT7eM3USD6XQeDrRMb+akhd6f
11+
KaaDkwzEouwnuIS30PqCRdneb6HmzYEJBedlyp/+lTYzITGqpNVPzXN7emNwJPJV
12+
GRMlVezkJgxmlO+J2DWZxc3rUKwppYDlAEwrPB5T+zJKTOZd9WLdUS/dEgtsqA0K
13+
TV18aQBzyBpR0G5braZUUNZq2i2/ObMMDIoK9k56qeVGO07xm3dkcqFlzhvOLxC8
14+
1ugDBFY6p5a34UIWEg/TWjt5yWdIv5hPtwFulo+B5Y7QVQv0PBD3QhhtL/OgqyZ3
15+
Mgp3wfG1NeAOpJ0xyTxBNx1EZmW5HKFbNrfjOMM+r9RIA+w6/8RtK06hVJKQnAWC
16+
yy1zOoz/fzi4UX45UeXonTCEYRal43DkYK2o4ED8PvEMVCTVR93a8fZf9NZv7nLn
17+
jY+kOYhe7Ik2sf9+UFtOylfTm3vj0Af1hQ4J3eCGW4ShtlXTpQku+wZ82jAOKc8w
18+
+iGoFoH9cwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQC8XJsX5pvzoT08TdQL+8SR
19+
3GUYezJjcTyXGpp6chw//L5SoSsEFZjb/gieLe5E/7mSIy2GeQW85ApuNorPWpvl
20+
L+gAl6OHWEqjoIzIakLYXAe8fyq+j/TdKEg7eRX4DBBILrhHMLyyqGbfWsLJySRk
21+
OFpPVGJ/xhxPvIt5jtKAEY5E+HSpLSZqDLjinH4CQyhdkZOJ9s5SwzmWTrCSpnWd
22+
ZRZW/aGlid6HT+1VdwjpL7Gce3kM44P5GVrH7Zix35DAXlUAghY7iZn4kgUW47uj
23+
SeJ0n//uxkMnb2kLv1RDz9Kgoz0EApgMVr3JKVL/dv2z2NgrfFAtDcS4oMcp8G5p
24+
-----END CERTIFICATE-----

test/fixtures/rootca-cert.pem

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDFDCCAfwCCQCww2ht6TFNnDANBgkqhkiG9w0BAQsFADBMMQswCQYDVQQGEwJ1
3+
czETMBEGA1UECAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEWMBQGA1UE
4+
AwwNRHVtbXkgUm9vdCBDQTAeFw0xODA2MjIyMDEzNDdaFw0yMTA0MTEyMDEzNDda
5+
MEwxCzAJBgNVBAYTAnVzMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdT
6+
ZWF0dGxlMRYwFAYDVQQDDA1EdW1teSBSb290IENBMIIBIjANBgkqhkiG9w0BAQEF
7+
AAOCAQ8AMIIBCgKCAQEA4Uy7shQKMdYnO1RNBoDUon/AQCdZXI3hBXjEuD3FaquE
8+
aw2oOvEsgLKPyWHfSsNVHkiqyeFDjHq5qmPdPjOAK3RqRQ0AzNSenzFFc1HEcWft
9+
bG5YSGVwcK9JVAqgl6TGIBA+L6SMi2nD24kABqkMCWMbUQv0r/88knP/Mpm3arCv
10+
UgStHAo64Fv1wxXXO9vq2ouKWjpp8OJMC62v50xlwt4cScl7OlJAhXj0K88u6Bce
11+
YlyJHE+KIMB7Nd8IbCnJ0xXpL0ju05oqvwmp7u3z3iHyj+FB89Zy2CgKW0kqyyT0
12+
ilDJH2ond9J7O2onqwPS0KYAJrfQVSVcp/teS9rcowIDAQABMA0GCSqGSIb3DQEB
13+
CwUAA4IBAQArYHB4M3InUcXKuQbhYJ1A8MScjR3kWvaQ9iqIQiNHx4IRNEUFmnYa
14+
saSjXo8pmW0WYt1gmVGGIk8b52RcAVgKrWIZG2qCh54bxPf2Cc3rQyQffY+YxqhZ
15+
s7KuJ6TBJnSJC3QnW2lQzc4ZV4clzMd2R9gj6KItG7eRay3Q71L8LG+pYqXi6+D/
16+
IGopnR9dVT2iVY1I+i60rKqNkR3kh0y0062Xju2TTP/+tBu45Pu/RlHKEiC0tP5E
17+
Wkso0rfipG45owySkcqINZClm3aGe55IwRcQD8+mE1g2y6mfE52WTZ8P/bnpb9ud
18+
W4+sed7/uYU1nDVznSZaxHlmjDpWsvqs
19+
-----END CERTIFICATE-----

0 commit comments

Comments
 (0)