Skip to content

Commit a81dc9a

Browse files
jasnellcodebytere
authored andcommitted
src,crypto: refactoring of crypto_context, SecureContext
Cleaup and improvement of crypto_context and SecureContext. Signed-off-by: James M Snell <[email protected]> PR-URL: #35665 Reviewed-By: Alba Mendez <[email protected]> Reviewed-By: Tobias Nießen <[email protected]> Reviewed-By: Rich Trott <[email protected]> Reviewed-By: Franziska Hinkelmann <[email protected]>
1 parent 9c6be3c commit a81dc9a

File tree

3 files changed

+309
-287
lines changed

3 files changed

+309
-287
lines changed

lib/_tls_common.js

+172-102
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@
2323

2424
const {
2525
ArrayIsArray,
26+
ArrayPrototypeFilter,
27+
ArrayPrototypeJoin,
2628
ObjectCreate,
29+
StringPrototypeSplit,
30+
StringPrototypeStartsWith,
2731
} = primordials;
2832

2933
const { parseCertString } = require('internal/tls');
@@ -44,8 +48,15 @@ const {
4448
TLS1_3_VERSION,
4549
} = internalBinding('constants').crypto;
4650

47-
// Lazily loaded from internal/crypto/util.
48-
let toBuf = null;
51+
const {
52+
validateString,
53+
validateInteger,
54+
validateInt32,
55+
} = require('internal/validators');
56+
57+
const {
58+
toBuf
59+
} = require('internal/crypto/util');
4960

5061
function toV(which, v, def) {
5162
if (v == null) v = def;
@@ -75,7 +86,10 @@ function SecureContext(secureProtocol, secureOptions, minVersion, maxVersion) {
7586
toV('minimum', minVersion, tls.DEFAULT_MIN_VERSION),
7687
toV('maximum', maxVersion, tls.DEFAULT_MAX_VERSION));
7788

78-
if (secureOptions) this.context.setOptions(secureOptions);
89+
if (secureOptions) {
90+
validateInteger(secureOptions, 'secureOptions');
91+
this.context.setOptions(secureOptions);
92+
}
7993
}
8094

8195
function validateKeyOrCertOption(name, value) {
@@ -90,80 +104,136 @@ function validateKeyOrCertOption(name, value) {
90104

91105
exports.SecureContext = SecureContext;
92106

107+
function setKey(context, key, passphrase) {
108+
validateKeyOrCertOption('key', key);
109+
if (passphrase != null)
110+
validateString(passphrase, 'options.passphrase');
111+
context.setKey(key, passphrase);
112+
}
113+
114+
function processCiphers(ciphers) {
115+
ciphers = StringPrototypeSplit(ciphers || tls.DEFAULT_CIPHERS, ':');
116+
117+
const cipherList =
118+
ArrayPrototypeJoin(
119+
ArrayPrototypeFilter(
120+
ciphers,
121+
(cipher) => {
122+
return cipher.length > 0 &&
123+
!StringPrototypeStartsWith(cipher, 'TLS_');
124+
}), ':');
125+
126+
const cipherSuites =
127+
ArrayPrototypeJoin(
128+
ArrayPrototypeFilter(
129+
ciphers,
130+
(cipher) => {
131+
return cipher.length > 0 &&
132+
StringPrototypeStartsWith(cipher, 'TLS_');
133+
}), ':');
134+
135+
// Specifying empty cipher suites for both TLS1.2 and TLS1.3 is invalid, its
136+
// not possible to handshake with no suites.
137+
if (cipherSuites === '' && cipherList === '')
138+
throw new ERR_INVALID_ARG_VALUE('options.ciphers', ciphers);
139+
140+
return { cipherList, cipherSuites };
141+
}
142+
143+
function addCACerts(context, ...certs) {
144+
for (const cert of certs) {
145+
validateKeyOrCertOption('ca', cert);
146+
context.addCACert(cert);
147+
}
148+
}
149+
150+
function setCerts(context, ...certs) {
151+
for (const cert of certs) {
152+
validateKeyOrCertOption('cert', cert);
153+
context.setCert(cert);
154+
}
155+
}
93156

94157
exports.createSecureContext = function createSecureContext(options) {
95158
if (!options) options = {};
96159

97-
let secureOptions = options.secureOptions;
98-
if (options.honorCipherOrder)
160+
const {
161+
ca,
162+
cert,
163+
ciphers,
164+
clientCertEngine,
165+
crl,
166+
dhparam,
167+
ecdhCurve = tls.DEFAULT_ECDH_CURVE,
168+
honorCipherOrder,
169+
key,
170+
minVersion,
171+
maxVersion,
172+
passphrase,
173+
pfx,
174+
privateKeyIdentifier,
175+
privateKeyEngine,
176+
secureProtocol,
177+
sessionIdContext,
178+
sessionTimeout,
179+
sigalgs,
180+
singleUse,
181+
ticketKeys,
182+
} = options;
183+
184+
let { secureOptions } = options;
185+
186+
if (honorCipherOrder)
99187
secureOptions |= SSL_OP_CIPHER_SERVER_PREFERENCE;
100188

101-
const c = new SecureContext(options.secureProtocol, secureOptions,
102-
options.minVersion, options.maxVersion);
189+
const c = new SecureContext(secureProtocol, secureOptions,
190+
minVersion, maxVersion);
103191

104192
// Add CA before the cert to be able to load cert's issuer in C++ code.
105-
const { ca } = options;
193+
// NOTE(@jasnell): ca, cert, and key are permitted to be falsy, so do not
194+
// change the checks to !== undefined checks.
106195
if (ca) {
107-
if (ArrayIsArray(ca)) {
108-
for (const val of ca) {
109-
validateKeyOrCertOption('ca', val);
110-
c.context.addCACert(val);
111-
}
112-
} else {
113-
validateKeyOrCertOption('ca', ca);
114-
c.context.addCACert(ca);
115-
}
196+
if (ArrayIsArray(ca))
197+
addCACerts(c.context, ...ca);
198+
else
199+
addCACerts(c.context, ca);
116200
} else {
117201
c.context.addRootCerts();
118202
}
119203

120-
const { cert } = options;
121204
if (cert) {
122-
if (ArrayIsArray(cert)) {
123-
for (const val of cert) {
124-
validateKeyOrCertOption('cert', val);
125-
c.context.setCert(val);
126-
}
127-
} else {
128-
validateKeyOrCertOption('cert', cert);
129-
c.context.setCert(cert);
130-
}
205+
if (ArrayIsArray(cert))
206+
setCerts(c.context, ...cert);
207+
else
208+
setCerts(c.context, cert);
131209
}
132210

133211
// Set the key after the cert.
134212
// `ssl_set_pkey` returns `0` when the key does not match the cert, but
135213
// `ssl_set_cert` returns `1` and nullifies the key in the SSL structure
136214
// which leads to the crash later on.
137-
const key = options.key;
138-
const passphrase = options.passphrase;
139215
if (key) {
140216
if (ArrayIsArray(key)) {
141217
for (const val of key) {
142218
// eslint-disable-next-line eqeqeq
143219
const pem = (val != undefined && val.pem !== undefined ? val.pem : val);
144-
validateKeyOrCertOption('key', pem);
145-
c.context.setKey(pem, val.passphrase || passphrase);
220+
setKey(c.context, pem, val.passphrase || passphrase);
146221
}
147222
} else {
148-
validateKeyOrCertOption('key', key);
149-
c.context.setKey(key, passphrase);
223+
setKey(c.context, key, passphrase);
150224
}
151225
}
152226

153-
const sigalgs = options.sigalgs;
154227
if (sigalgs !== undefined) {
155-
if (typeof sigalgs !== 'string') {
228+
if (typeof sigalgs !== 'string')
156229
throw new ERR_INVALID_ARG_TYPE('options.sigalgs', 'string', sigalgs);
157-
}
158230

159-
if (sigalgs === '') {
231+
if (sigalgs === '')
160232
throw new ERR_INVALID_ARG_VALUE('options.sigalgs', sigalgs);
161-
}
162233

163234
c.context.setSigalgs(sigalgs);
164235
}
165236

166-
const { privateKeyIdentifier, privateKeyEngine } = options;
167237
if (privateKeyIdentifier !== undefined) {
168238
if (privateKeyEngine === undefined) {
169239
// Engine is required when privateKeyIdentifier is present
@@ -193,113 +263,113 @@ exports.createSecureContext = function createSecureContext(options) {
193263
}
194264
}
195265

196-
if (options.ciphers && typeof options.ciphers !== 'string') {
197-
throw new ERR_INVALID_ARG_TYPE(
198-
'options.ciphers', 'string', options.ciphers);
199-
}
266+
if (ciphers !== undefined)
267+
validateString(ciphers, 'options.ciphers');
200268

201269
// Work around an OpenSSL API quirk. cipherList is for TLSv1.2 and below,
202270
// cipherSuites is for TLSv1.3 (and presumably any later versions). TLSv1.3
203271
// cipher suites all have a standard name format beginning with TLS_, so split
204272
// the ciphers and pass them to the appropriate API.
205-
const ciphers = (options.ciphers || tls.DEFAULT_CIPHERS).split(':');
206-
const cipherList = ciphers.filter((_) => !_.match(/^TLS_/) &&
207-
_.length > 0).join(':');
208-
const cipherSuites = ciphers.filter((_) => _.match(/^TLS_/)).join(':');
209-
210-
if (cipherSuites === '' && cipherList === '') {
211-
// Specifying empty cipher suites for both TLS1.2 and TLS1.3 is invalid, its
212-
// not possible to handshake with no suites.
213-
throw new ERR_INVALID_ARG_VALUE('options.ciphers', ciphers);
214-
}
273+
const { cipherList, cipherSuites } = processCiphers(ciphers);
215274

216275
c.context.setCipherSuites(cipherSuites);
217276
c.context.setCiphers(cipherList);
218277

219-
if (cipherSuites === '' && c.context.getMaxProto() > TLS1_2_VERSION &&
220-
c.context.getMinProto() < TLS1_3_VERSION)
278+
if (cipherSuites === '' &&
279+
c.context.getMaxProto() > TLS1_2_VERSION &&
280+
c.context.getMinProto() < TLS1_3_VERSION) {
221281
c.context.setMaxProto(TLS1_2_VERSION);
282+
}
222283

223-
if (cipherList === '' && c.context.getMinProto() < TLS1_3_VERSION &&
224-
c.context.getMaxProto() > TLS1_2_VERSION)
284+
if (cipherList === '' &&
285+
c.context.getMinProto() < TLS1_3_VERSION &&
286+
c.context.getMaxProto() > TLS1_2_VERSION) {
225287
c.context.setMinProto(TLS1_3_VERSION);
288+
}
226289

227-
if (options.ecdhCurve === undefined)
228-
c.context.setECDHCurve(tls.DEFAULT_ECDH_CURVE);
229-
else if (options.ecdhCurve)
230-
c.context.setECDHCurve(options.ecdhCurve);
290+
validateString(ecdhCurve, 'options.ecdhCurve');
291+
c.context.setECDHCurve(ecdhCurve);
231292

232-
if (options.dhparam) {
233-
const warning = c.context.setDHParam(options.dhparam);
293+
if (dhparam !== undefined) {
294+
validateKeyOrCertOption('dhparam', dhparam);
295+
const warning = c.context.setDHParam(dhparam);
234296
if (warning)
235297
process.emitWarning(warning, 'SecurityWarning');
236298
}
237299

238-
if (options.crl) {
239-
if (ArrayIsArray(options.crl)) {
240-
for (const crl of options.crl) {
241-
c.context.addCRL(crl);
300+
if (crl !== undefined) {
301+
if (ArrayIsArray(crl)) {
302+
for (const val of crl) {
303+
validateKeyOrCertOption('crl', val);
304+
c.context.addCRL(val);
242305
}
243306
} else {
244-
c.context.addCRL(options.crl);
307+
validateKeyOrCertOption('crl', crl);
308+
c.context.addCRL(crl);
245309
}
246310
}
247311

248-
if (options.sessionIdContext) {
249-
c.context.setSessionIdContext(options.sessionIdContext);
312+
if (sessionIdContext !== undefined) {
313+
validateString(sessionIdContext, 'options.sessionIdContext');
314+
c.context.setSessionIdContext(sessionIdContext);
250315
}
251316

252-
if (options.pfx) {
253-
if (!toBuf)
254-
toBuf = require('internal/crypto/util').toBuf;
255-
256-
if (ArrayIsArray(options.pfx)) {
257-
for (const pfx of options.pfx) {
258-
const raw = pfx.buf ? pfx.buf : pfx;
259-
const buf = toBuf(raw);
260-
const passphrase = pfx.passphrase || options.passphrase;
261-
if (passphrase) {
262-
c.context.loadPKCS12(buf, toBuf(passphrase));
317+
if (pfx !== undefined) {
318+
if (ArrayIsArray(pfx)) {
319+
for (const val of pfx) {
320+
const raw = val.buf ? val.buf : val;
321+
const pass = val.passphrase || passphrase;
322+
if (pass !== undefined) {
323+
c.context.loadPKCS12(toBuf(raw), toBuf(pass));
263324
} else {
264-
c.context.loadPKCS12(buf);
325+
c.context.loadPKCS12(toBuf(raw));
265326
}
266327
}
328+
} else if (passphrase) {
329+
c.context.loadPKCS12(toBuf(pfx), toBuf(passphrase));
267330
} else {
268-
const buf = toBuf(options.pfx);
269-
const passphrase = options.passphrase;
270-
if (passphrase) {
271-
c.context.loadPKCS12(buf, toBuf(passphrase));
272-
} else {
273-
c.context.loadPKCS12(buf);
274-
}
331+
c.context.loadPKCS12(toBuf(pfx));
275332
}
276333
}
277334

278335
// Do not keep read/write buffers in free list for OpenSSL < 1.1.0. (For
279336
// OpenSSL 1.1.0, buffers are malloced and freed without the use of a
280337
// freelist.)
281-
if (options.singleUse) {
338+
if (singleUse) {
282339
c.singleUse = true;
283340
c.context.setFreeListLength(0);
284341
}
285342

286-
if (typeof options.clientCertEngine === 'string') {
287-
if (c.context.setClientCertEngine)
288-
c.context.setClientCertEngine(options.clientCertEngine);
289-
else
343+
if (clientCertEngine !== undefined) {
344+
if (typeof c.context.setClientCertEngine !== 'function')
290345
throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED();
291-
} else if (options.clientCertEngine != null) {
292-
throw new ERR_INVALID_ARG_TYPE('options.clientCertEngine',
293-
['string', 'null', 'undefined'],
294-
options.clientCertEngine);
346+
if (typeof clientCertEngine !== 'string') {
347+
throw new ERR_INVALID_ARG_TYPE('options.clientCertEngine',
348+
['string', 'null', 'undefined'],
349+
clientCertEngine);
350+
}
351+
c.context.setClientCertEngine(clientCertEngine);
295352
}
296353

297-
if (options.ticketKeys) {
298-
c.context.setTicketKeys(options.ticketKeys);
354+
if (ticketKeys !== undefined) {
355+
if (!isArrayBufferView(ticketKeys)) {
356+
throw new ERR_INVALID_ARG_TYPE(
357+
'options.ticketKeys',
358+
['Buffer', 'TypedArray', 'DataView'],
359+
ticketKeys);
360+
}
361+
if (ticketKeys.byteLength !== 48) {
362+
throw new ERR_INVALID_ARG_VALUE(
363+
'options.ticketKeys',
364+
ticketKeys.byteLenth,
365+
'must be exactly 48 bytes');
366+
}
367+
c.context.setTicketKeys(ticketKeys);
299368
}
300369

301-
if (options.sessionTimeout) {
302-
c.context.setSessionTimeout(options.sessionTimeout);
370+
if (sessionTimeout !== undefined) {
371+
validateInt32(sessionTimeout, 'options.sessionTimeout');
372+
c.context.setSessionTimeout(sessionTimeout);
303373
}
304374

305375
return c;

0 commit comments

Comments
 (0)