Skip to content

Commit 457d569

Browse files
authored
Merge pull request #50 from jpage-godaddy/catch-fs-errors
Better error handling
2 parents c07ee49 + 9eea904 commit 457d569

File tree

6 files changed

+333
-219
lines changed

6 files changed

+333
-219
lines changed

.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
16

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# CHANGELOG
22

3+
## 3.2.1
4+
5+
- [#50] Fix bug where cert file reading errors do not surface
6+
37
## 3.2.0
48

59
- [#37] Support HTTP2

index.js

Lines changed: 103 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,18 @@
77
*
88
*/
99

10-
var fs = require('fs'),
10+
const
11+
fs = require('fs').promises,
1112
tls = require('tls'),
1213
path = require('path'),
1314
constants = require('constants'),
1415
connected = require('connected'),
1516
errs = require('errs'),
1617
assign = require('object-assign');
1718

18-
var pemFormat = /-----BEGIN/;
19+
const pemFormat = /-----BEGIN/;
1920

20-
var CIPHERS = [
21+
const CIPHERS = [
2122
'ECDHE-RSA-AES256-SHA384',
2223
'DHE-RSA-AES256-SHA384',
2324
'ECDHE-RSA-AES256-SHA256',
@@ -36,7 +37,7 @@ var CIPHERS = [
3637
'!CAMELLIA'
3738
].join(':');
3839

39-
var secureOptions = constants.SSL_OP_NO_SSLv3;
40+
const secureOptions = constants.SSL_OP_NO_SSLv3;
4041

4142
/**
4243
* function createServers (dispatch, options, callback)
@@ -49,31 +50,28 @@ module.exports = async function createServers(options, listening) {
4950
return listening(err);
5051
}
5152

52-
const [
53-
[httpErr, http],
54-
[httpsErr, https],
55-
[http2Err, http2]] = await Promise.all([
53+
const [httpResult, httpsResult, http2Result] = await Promise.allSettled([
5654
createHttp(options.http, options.log),
5755
createHttps(options.https, options.log),
5856
createHttps(options.http2, options.log, true)
59-
]);
57+
])
6058

6159
const servers = {};
62-
if (http) servers.http = http;
63-
if (https) servers.https = https;
64-
if (http2) servers.http2 = http2;
60+
if (httpResult.value) servers.http = httpResult.value;
61+
if (httpsResult.value) servers.https = httpsResult.value;
62+
if (http2Result.value) servers.http2 = http2Result.value;
6563

66-
if (httpErr || httpsErr || http2Err) {
67-
let errorSource = http2Err || httpsErr || httpErr;
64+
const errorSource = httpResult.reason || httpsResult.reason || http2Result.reason;
65+
if (errorSource) {
6866
if (Array.isArray(errorSource)) {
6967
errorSource = errorSource[0];
7068
}
7169
return listening(
7270
errs.create({
7371
message: errorSource && errorSource.message,
74-
http2: http2Err,
75-
https: httpsErr,
76-
http: httpErr
72+
http2: http2Result.reason,
73+
https: httpsResult.reason,
74+
http: httpResult.reason
7775
}),
7876
servers
7977
);
@@ -172,25 +170,25 @@ function normalizeCertChainList(root, data) {
172170
// If this is an array, treat like an array of bundles, otherwise a single
173171
// bundle
174172
return Array.isArray(data)
175-
? data.map(function(item) {
173+
? Promise.all(data.map(function(item) {
176174
return normalizeCertChain(root, item);
177-
})
175+
}))
178176
: normalizePEMContent(root, data);
179177
}
180178

181-
function normalizeCertChain(root, data) {
179+
async function normalizeCertChain(root, data) {
182180
// A chain can be an array, which we concatenate together into one PEM,
183181
// an already-concatenated chain, or a single PEM
184182

185-
const content = normalizePEMContent(root, data);
183+
const content = await normalizePEMContent(root, data);
186184
return Array.isArray(content) ? content.join('\n') : content;
187185
}
188186

189187
function normalizeCA(root, ca) {
190188
if (ca && !Array.isArray(ca)) {
191189
ca = [ca];
192190
}
193-
return ca && ca.map(normalizePEMContent.bind(null, root));
191+
return ca && Promise.all(ca.map(normalizePEMContent.bind(null, root)));
194192
}
195193

196194
/**
@@ -201,9 +199,9 @@ function normalizeCA(root, ca) {
201199
*/
202200
function normalizePEMContent(root, file) {
203201
if (Array.isArray(file))
204-
return file.map(function map(item) {
202+
return Promise.all(file.map(function map(item) {
205203
return normalizePEMContent(root, item);
206-
});
204+
}));
207205

208206
//
209207
// Assumption that this is a Buffer, a PEM file, or something broken
@@ -212,7 +210,7 @@ function normalizePEMContent(root, file) {
212210
return file;
213211
}
214212

215-
return fs.readFileSync(path.resolve(root, file));
213+
return fs.readFile(path.resolve(root, file));
216214
}
217215

218216
function normalizeCiphers(ciphers) {
@@ -226,11 +224,11 @@ function normalizeCiphers(ciphers) {
226224
return ciphers;
227225
}
228226

229-
function getSNIHandler(sslOpts) {
230-
var sniHosts = Object.keys(sslOpts.sni);
227+
async function getSNIHandler(sslOpts) {
228+
const sniHosts = Object.keys(sslOpts.sni);
231229

232230
// Pre-compile regexps for the hostname
233-
var hostRegexps = sniHosts.map(function(host) {
231+
const hostRegexps = sniHosts.map(function(host) {
234232
return host === '*' ? /.*/ : new RegExp(
235233
'^' +
236234
host
@@ -242,16 +240,22 @@ function getSNIHandler(sslOpts) {
242240
});
243241

244242
// Prepare secure contexts ahead-of-time
245-
var hostSecureContexts = sniHosts.map(function(host) {
243+
const hostSecureContexts = await Promise.all(sniHosts.map(async function(host) {
246244
var hostOpts = sslOpts.sni[host];
247245

248246
var root = hostOpts.root || sslOpts.root;
249247

248+
const [key, cert, ca] = await Promise.all([
249+
normalizePEMContent(root, hostOpts.key),
250+
normalizeCertContent(root, hostOpts.cert),
251+
normalizeCA(root, hostOpts.ca || sslOpts.ca)
252+
])
253+
250254
return tls.createSecureContext(
251255
assign({}, sslOpts, hostOpts, {
252-
key: normalizePEMContent(root, hostOpts.key),
253-
cert: normalizeCertContent(root, hostOpts.cert),
254-
ca: normalizeCA(root, hostOpts.ca || sslOpts.ca),
256+
key,
257+
cert,
258+
ca,
255259
ciphers: normalizeCiphers(hostOpts.ciphers || sslOpts.ciphers),
256260
honorCipherOrder: !!(
257261
hostOpts.honorCipherOrder || sslOpts.honorCipherOrder
@@ -260,7 +264,7 @@ function getSNIHandler(sslOpts) {
260264
secureOptions: secureOptions
261265
})
262266
);
263-
});
267+
}));
264268

265269
return function(hostname, cb) {
266270
var matchingHostIdx = sniHosts.findIndex(function(candidate, i) {
@@ -282,29 +286,30 @@ function getSNIHandler(sslOpts) {
282286
async function createHttp(httpConfig, log) {
283287
if (typeof httpConfig === 'undefined') {
284288
log('http | no options.http; no server');
285-
return [null, null];
289+
return null;
286290
}
287291

288292
if (Array.isArray(httpConfig)) {
289293
return await createMultiple(createHttp, httpConfig, log);
290294
}
291295

292-
return await new Promise(resolve => {
293-
var server = require('http').createServer(httpConfig.handler),
294-
timeout = httpConfig.timeout,
295-
port = httpConfig.port,
296-
args;
296+
const
297+
server = require('http').createServer(httpConfig.handler),
298+
timeout = httpConfig.timeout,
299+
port = httpConfig.port;
297300

298-
if (typeof timeout === 'number') server.setTimeout(timeout);
301+
if (typeof timeout === 'number') server.setTimeout(timeout);
299302

300-
args = [server, port];
301-
if (httpConfig.host) {
302-
args.push(httpConfig.host);
303-
}
303+
const args = [server, port];
304+
if (httpConfig.host) {
305+
args.push(httpConfig.host);
306+
}
307+
308+
log('http | try listen ' + port);
304309

305-
log('http | try listen ' + port);
310+
return new Promise((resolve, reject) => {
306311
args.push(function listener(err) {
307-
resolve([err, server]);
312+
err ? reject(err) : resolve(server);
308313
});
309314
connected.apply(null, args);
310315
});
@@ -317,73 +322,76 @@ async function createHttp(httpConfig, log) {
317322
async function createHttps(ssl, log, h2) {
318323
if (typeof ssl === 'undefined') {
319324
log('https | no options.https; no server');
320-
return [null, null];
325+
return null;
321326
}
322327

323328
if (Array.isArray(ssl)) {
324329
return await createMultiple(createHttps, ssl, log, h2);
325330
}
326331

327-
return await new Promise(resolve => {
328-
var port = ssl.port,
329-
timeout = ssl.timeout,
330-
server,
331-
args;
332-
333-
var finalHttpsOptions = assign({}, ssl, {
334-
//
335-
// Load default SSL key, cert and ca(s).
336-
//
337-
key: normalizePEMContent(ssl.root, ssl.key),
338-
cert: normalizeCertContent(ssl.root, ssl.cert, ssl.key),
339-
ca: normalizeCA(ssl.root, ssl.ca),
340-
//
341-
// Properly expose ciphers for an A+ SSL rating:
342-
// https://certsimple.com/blog/a-plus-node-js-ssl
343-
//
344-
ciphers: normalizeCiphers(ssl.ciphers),
345-
honorCipherOrder: !!ssl.honorCipherOrder,
346-
//
347-
// Protect against the POODLE attack by disabling SSLv3
348-
// @see http://googleonlinesecurity.blogspot.nl/2014/10/this-poodle-bites-exploiting-ssl-30.html
349-
//
350-
secureProtocol: 'SSLv23_method',
351-
secureOptions: secureOptions
352-
});
332+
const [key, cert, ca] = await Promise.all([
333+
normalizePEMContent(ssl.root, ssl.key),
334+
normalizeCertContent(ssl.root, ssl.cert, ssl.key),
335+
normalizeCA(ssl.root, ssl.ca)
336+
]);
353337

354-
if (ssl.sni && !finalHttpsOptions.SNICallback) {
355-
finalHttpsOptions.SNICallback = getSNIHandler(ssl);
356-
}
338+
const finalHttpsOptions = assign({}, ssl, {
339+
key,
340+
cert,
341+
ca,
342+
//
343+
// Properly expose ciphers for an A+ SSL rating:
344+
// https://certsimple.com/blog/a-plus-node-js-ssl
345+
//
346+
ciphers: normalizeCiphers(ssl.ciphers),
347+
honorCipherOrder: !!ssl.honorCipherOrder,
348+
//
349+
// Protect against the POODLE attack by disabling SSLv3
350+
// @see http://googleonlinesecurity.blogspot.nl/2014/10/this-poodle-bites-exploiting-ssl-30.html
351+
//
352+
secureProtocol: 'SSLv23_method',
353+
secureOptions: secureOptions
354+
});
357355

358-
log('https | listening on %d', port);
359-
if(h2) {
360-
server = require('http2').createSecureServer(finalHttpsOptions, ssl.handler)
361-
} else {
362-
server = require('https').createServer(finalHttpsOptions, ssl.handler);
363-
}
356+
if (ssl.sni && !finalHttpsOptions.SNICallback) {
357+
finalHttpsOptions.SNICallback = await getSNIHandler(ssl);
358+
}
364359

365-
if (typeof timeout === 'number') server.setTimeout(timeout);
366-
args = [server, port];
367-
if (ssl.host) {
368-
args.push(ssl.host);
369-
}
360+
const port = ssl.port;
361+
log('https | listening on %d', port);
362+
const server = h2
363+
? require('http2').createSecureServer(finalHttpsOptions, ssl.handler)
364+
: require('https').createServer(finalHttpsOptions, ssl.handler);
365+
366+
const timeout = ssl.timeout;
367+
if (typeof timeout === 'number') server.setTimeout(timeout);
368+
const args = [server, port];
369+
if (ssl.host) {
370+
args.push(ssl.host);
371+
}
370372

373+
return new Promise((resolve, reject) => {
371374
args.push(function listener(err) {
372-
resolve([err, server]);
375+
err ? reject(err) : resolve(server);
373376
});
374377
connected.apply(null, args);
375378
});
376379
}
377380

378381
async function createMultiple(createFn, configArray, log) {
379-
const errorsOrServers = await Promise.all(
382+
const errorsOrServers = await Promise.allSettled(
380383
configArray.map(cfg => createFn(cfg, log))
381384
);
382-
const errors = [],
383-
servers = [];
384-
for (const [error, server] of errorsOrServers) {
385-
error && errors.push(error);
386-
server && servers.push(server);
385+
386+
const errors = [], servers = [];
387+
for (const result of errorsOrServers) {
388+
result.reason && errors.push(result.reason);
389+
result.value && servers.push(result.value);
390+
}
391+
392+
if (errors.length) {
393+
throw errors;
394+
} else {
395+
return servers;
387396
}
388-
return [errors.length ? errors : null, servers];
389397
}

0 commit comments

Comments
 (0)