Skip to content

Commit eb20447

Browse files
committed
tls: extract out SecureContext configuration
In preparation for re-introducing quic, refactor out a reusable piece of the SecureContext configuration that will also be used by the quic impl. Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: nodejs#38116 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 5823fc7 commit eb20447

File tree

3 files changed

+342
-292
lines changed

3 files changed

+342
-292
lines changed

lib/_tls_common.js

+33-290
Original file line numberDiff line numberDiff line change
@@ -21,45 +21,39 @@
2121

2222
'use strict';
2323

24+
const tls = require('tls');
25+
2426
const {
25-
ArrayIsArray,
26-
ArrayPrototypeFilter,
27-
ArrayPrototypeForEach,
28-
ArrayPrototypeJoin,
2927
ArrayPrototypePush,
3028
ObjectCreate,
3129
StringPrototypeReplace,
32-
StringPrototypeSplit,
33-
StringPrototypeStartsWith,
3430
} = primordials;
3531

36-
const { parseCertString } = require('internal/tls');
37-
const { isArrayBufferView } = require('internal/util/types');
38-
const tls = require('tls');
3932
const {
40-
ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED,
41-
ERR_INVALID_ARG_TYPE,
42-
ERR_INVALID_ARG_VALUE,
43-
ERR_TLS_INVALID_PROTOCOL_VERSION,
44-
ERR_TLS_PROTOCOL_VERSION_CONFLICT,
45-
} = require('internal/errors').codes;
33+
codes: {
34+
ERR_TLS_INVALID_PROTOCOL_VERSION,
35+
ERR_TLS_PROTOCOL_VERSION_CONFLICT,
36+
},
37+
} = require('internal/errors');
38+
4639
const {
47-
SSL_OP_CIPHER_SERVER_PREFERENCE,
48-
TLS1_VERSION,
49-
TLS1_1_VERSION,
50-
TLS1_2_VERSION,
51-
TLS1_3_VERSION,
52-
} = internalBinding('constants').crypto;
40+
crypto: {
41+
SSL_OP_CIPHER_SERVER_PREFERENCE,
42+
TLS1_VERSION,
43+
TLS1_1_VERSION,
44+
TLS1_2_VERSION,
45+
TLS1_3_VERSION,
46+
},
47+
} = internalBinding('constants');
5348

5449
const {
55-
validateString,
5650
validateInteger,
57-
validateInt32,
5851
} = require('internal/validators');
5952

6053
const {
61-
toBuf
62-
} = require('internal/crypto/util');
54+
configSecureContext,
55+
parseCertString,
56+
} = require('internal/tls');
6357

6458
function toV(which, v, def) {
6559
if (v == null) v = def;
@@ -70,7 +64,10 @@ function toV(which, v, def) {
7064
throw new ERR_TLS_INVALID_PROTOCOL_VERSION(v, which);
7165
}
7266

73-
const { SecureContext: NativeSecureContext } = internalBinding('crypto');
67+
const {
68+
SecureContext: NativeSecureContext,
69+
} = internalBinding('crypto');
70+
7471
function SecureContext(secureProtocol, secureOptions, minVersion, maxVersion) {
7572
if (!(this instanceof SecureContext)) {
7673
return new SecureContext(secureProtocol, secureOptions, minVersion,
@@ -95,93 +92,14 @@ function SecureContext(secureProtocol, secureOptions, minVersion, maxVersion) {
9592
}
9693
}
9794

98-
function validateKeyOrCertOption(name, value) {
99-
if (typeof value !== 'string' && !isArrayBufferView(value)) {
100-
throw new ERR_INVALID_ARG_TYPE(
101-
`options.${name}`,
102-
['string', 'Buffer', 'TypedArray', 'DataView'],
103-
value
104-
);
105-
}
106-
}
107-
108-
exports.SecureContext = SecureContext;
109-
110-
function setKey(context, key, passphrase) {
111-
validateKeyOrCertOption('key', key);
112-
if (passphrase != null)
113-
validateString(passphrase, 'options.passphrase');
114-
context.setKey(key, passphrase);
115-
}
116-
117-
function processCiphers(ciphers) {
118-
ciphers = StringPrototypeSplit(ciphers || tls.DEFAULT_CIPHERS, ':');
119-
120-
const cipherList =
121-
ArrayPrototypeJoin(
122-
ArrayPrototypeFilter(
123-
ciphers,
124-
(cipher) => {
125-
return cipher.length > 0 &&
126-
!StringPrototypeStartsWith(cipher, 'TLS_');
127-
}), ':');
128-
129-
const cipherSuites =
130-
ArrayPrototypeJoin(
131-
ArrayPrototypeFilter(
132-
ciphers,
133-
(cipher) => {
134-
return cipher.length > 0 &&
135-
StringPrototypeStartsWith(cipher, 'TLS_');
136-
}), ':');
137-
138-
// Specifying empty cipher suites for both TLS1.2 and TLS1.3 is invalid, its
139-
// not possible to handshake with no suites.
140-
if (cipherSuites === '' && cipherList === '')
141-
throw new ERR_INVALID_ARG_VALUE('options.ciphers', ciphers);
142-
143-
return { cipherList, cipherSuites };
144-
}
145-
146-
function addCACerts(context, certs) {
147-
ArrayPrototypeForEach(certs, (cert) => {
148-
validateKeyOrCertOption('ca', cert);
149-
context.addCACert(cert);
150-
});
151-
}
152-
153-
function setCerts(context, certs) {
154-
ArrayPrototypeForEach(certs, (cert) => {
155-
validateKeyOrCertOption('cert', cert);
156-
context.setCert(cert);
157-
});
158-
}
159-
160-
exports.createSecureContext = function createSecureContext(options) {
95+
function createSecureContext(options) {
16196
if (!options) options = {};
16297

16398
const {
164-
ca,
165-
cert,
166-
ciphers,
167-
clientCertEngine,
168-
crl,
169-
dhparam,
170-
ecdhCurve = tls.DEFAULT_ECDH_CURVE,
17199
honorCipherOrder,
172-
key,
173100
minVersion,
174101
maxVersion,
175-
passphrase,
176-
pfx,
177-
privateKeyIdentifier,
178-
privateKeyEngine,
179102
secureProtocol,
180-
sessionIdContext,
181-
sessionTimeout,
182-
sigalgs,
183-
singleUse,
184-
ticketKeys,
185103
} = options;
186104

187105
let { secureOptions } = options;
@@ -192,196 +110,15 @@ exports.createSecureContext = function createSecureContext(options) {
192110
const c = new SecureContext(secureProtocol, secureOptions,
193111
minVersion, maxVersion);
194112

195-
// Add CA before the cert to be able to load cert's issuer in C++ code.
196-
// NOTE(@jasnell): ca, cert, and key are permitted to be falsy, so do not
197-
// change the checks to !== undefined checks.
198-
if (ca) {
199-
if (ArrayIsArray(ca))
200-
addCACerts(c.context, ca);
201-
else
202-
addCACerts(c.context, [ca]);
203-
} else {
204-
c.context.addRootCerts();
205-
}
206-
207-
if (cert) {
208-
if (ArrayIsArray(cert))
209-
setCerts(c.context, cert);
210-
else
211-
setCerts(c.context, [cert]);
212-
}
213-
214-
// Set the key after the cert.
215-
// `ssl_set_pkey` returns `0` when the key does not match the cert, but
216-
// `ssl_set_cert` returns `1` and nullifies the key in the SSL structure
217-
// which leads to the crash later on.
218-
if (key) {
219-
if (ArrayIsArray(key)) {
220-
for (let i = 0; i < key.length; ++i) {
221-
const val = key[i];
222-
// eslint-disable-next-line eqeqeq
223-
const pem = (val != undefined && val.pem !== undefined ? val.pem : val);
224-
setKey(c.context, pem, val.passphrase || passphrase);
225-
}
226-
} else {
227-
setKey(c.context, key, passphrase);
228-
}
229-
}
230-
231-
if (sigalgs !== undefined) {
232-
validateString(sigalgs, 'options.sigalgs');
233-
234-
if (sigalgs === '')
235-
throw new ERR_INVALID_ARG_VALUE('options.sigalgs', sigalgs);
236-
237-
c.context.setSigalgs(sigalgs);
238-
}
239-
240-
if (privateKeyIdentifier !== undefined) {
241-
if (privateKeyEngine === undefined) {
242-
// Engine is required when privateKeyIdentifier is present
243-
throw new ERR_INVALID_ARG_VALUE('options.privateKeyEngine',
244-
privateKeyEngine);
245-
}
246-
if (key) {
247-
// Both data key and engine key can't be set at the same time
248-
throw new ERR_INVALID_ARG_VALUE('options.privateKeyIdentifier',
249-
privateKeyIdentifier);
250-
}
251-
252-
if (typeof privateKeyIdentifier === 'string' &&
253-
typeof privateKeyEngine === 'string') {
254-
if (c.context.setEngineKey)
255-
c.context.setEngineKey(privateKeyIdentifier, privateKeyEngine);
256-
else
257-
throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED();
258-
} else if (typeof privateKeyIdentifier !== 'string') {
259-
throw new ERR_INVALID_ARG_TYPE('options.privateKeyIdentifier',
260-
['string', 'undefined'],
261-
privateKeyIdentifier);
262-
} else {
263-
throw new ERR_INVALID_ARG_TYPE('options.privateKeyEngine',
264-
['string', 'undefined'],
265-
privateKeyEngine);
266-
}
267-
}
268-
269-
if (ciphers != null)
270-
validateString(ciphers, 'options.ciphers');
271-
272-
// Work around an OpenSSL API quirk. cipherList is for TLSv1.2 and below,
273-
// cipherSuites is for TLSv1.3 (and presumably any later versions). TLSv1.3
274-
// cipher suites all have a standard name format beginning with TLS_, so split
275-
// the ciphers and pass them to the appropriate API.
276-
const { cipherList, cipherSuites } = processCiphers(ciphers);
277-
278-
c.context.setCipherSuites(cipherSuites);
279-
c.context.setCiphers(cipherList);
280-
281-
if (cipherSuites === '' &&
282-
c.context.getMaxProto() > TLS1_2_VERSION &&
283-
c.context.getMinProto() < TLS1_3_VERSION) {
284-
c.context.setMaxProto(TLS1_2_VERSION);
285-
}
286-
287-
if (cipherList === '' &&
288-
c.context.getMinProto() < TLS1_3_VERSION &&
289-
c.context.getMaxProto() > TLS1_2_VERSION) {
290-
c.context.setMinProto(TLS1_3_VERSION);
291-
}
292-
293-
validateString(ecdhCurve, 'options.ecdhCurve');
294-
c.context.setECDHCurve(ecdhCurve);
295-
296-
if (dhparam !== undefined) {
297-
validateKeyOrCertOption('dhparam', dhparam);
298-
const warning = c.context.setDHParam(dhparam);
299-
if (warning)
300-
process.emitWarning(warning, 'SecurityWarning');
301-
}
302-
303-
if (crl !== undefined) {
304-
if (ArrayIsArray(crl)) {
305-
for (const val of crl) {
306-
validateKeyOrCertOption('crl', val);
307-
c.context.addCRL(val);
308-
}
309-
} else {
310-
validateKeyOrCertOption('crl', crl);
311-
c.context.addCRL(crl);
312-
}
313-
}
314-
315-
if (sessionIdContext !== undefined) {
316-
validateString(sessionIdContext, 'options.sessionIdContext');
317-
c.context.setSessionIdContext(sessionIdContext);
318-
}
319-
320-
if (pfx !== undefined) {
321-
if (ArrayIsArray(pfx)) {
322-
ArrayPrototypeForEach(pfx, (val) => {
323-
const raw = val.buf ? val.buf : val;
324-
const pass = val.passphrase || passphrase;
325-
if (pass !== undefined) {
326-
c.context.loadPKCS12(toBuf(raw), toBuf(pass));
327-
} else {
328-
c.context.loadPKCS12(toBuf(raw));
329-
}
330-
});
331-
} else if (passphrase) {
332-
c.context.loadPKCS12(toBuf(pfx), toBuf(passphrase));
333-
} else {
334-
c.context.loadPKCS12(toBuf(pfx));
335-
}
336-
}
337-
338-
// Do not keep read/write buffers in free list for OpenSSL < 1.1.0. (For
339-
// OpenSSL 1.1.0, buffers are malloced and freed without the use of a
340-
// freelist.)
341-
if (singleUse) {
342-
c.singleUse = true;
343-
c.context.setFreeListLength(0);
344-
}
345-
346-
if (clientCertEngine !== undefined) {
347-
if (typeof c.context.setClientCertEngine !== 'function')
348-
throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED();
349-
if (typeof clientCertEngine !== 'string') {
350-
throw new ERR_INVALID_ARG_TYPE('options.clientCertEngine',
351-
['string', 'null', 'undefined'],
352-
clientCertEngine);
353-
}
354-
c.context.setClientCertEngine(clientCertEngine);
355-
}
356-
357-
if (ticketKeys !== undefined) {
358-
if (!isArrayBufferView(ticketKeys)) {
359-
throw new ERR_INVALID_ARG_TYPE(
360-
'options.ticketKeys',
361-
['Buffer', 'TypedArray', 'DataView'],
362-
ticketKeys);
363-
}
364-
if (ticketKeys.byteLength !== 48) {
365-
throw new ERR_INVALID_ARG_VALUE(
366-
'options.ticketKeys',
367-
ticketKeys.byteLength,
368-
'must be exactly 48 bytes');
369-
}
370-
c.context.setTicketKeys(ticketKeys);
371-
}
372-
373-
if (sessionTimeout !== undefined) {
374-
validateInt32(sessionTimeout, 'options.sessionTimeout');
375-
c.context.setSessionTimeout(sessionTimeout);
376-
}
113+
configSecureContext(c.context, options);
377114

378115
return c;
379-
};
116+
}
380117

381118
// Translate some fields from the handle's C-friendly format into more idiomatic
382119
// javascript object representations before passing them back to the user. Can
383120
// be used on any cert object, but changing the name would be semver-major.
384-
exports.translatePeerCertificate = function translatePeerCertificate(c) {
121+
function translatePeerCertificate(c) {
385122
if (!c)
386123
return null;
387124

@@ -404,4 +141,10 @@ exports.translatePeerCertificate = function translatePeerCertificate(c) {
404141
});
405142
}
406143
return c;
144+
}
145+
146+
module.exports = {
147+
SecureContext,
148+
createSecureContext,
149+
translatePeerCertificate,
407150
};

0 commit comments

Comments
 (0)