1'use strict'; 2 3const { 4 ArrayIsArray, 5 ArrayPrototypeFilter, 6 ArrayPrototypeForEach, 7 ArrayPrototypeJoin, 8 StringPrototypeSplit, 9 StringPrototypeStartsWith, 10} = primordials; 11 12const { 13 codes: { 14 ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED, 15 ERR_INVALID_ARG_TYPE, 16 ERR_INVALID_ARG_VALUE, 17 }, 18} = require('internal/errors'); 19 20const { 21 kEmptyObject, 22} = require('internal/util'); 23 24const { 25 isArrayBufferView, 26} = require('internal/util/types'); 27 28const { 29 validateBuffer, 30 validateInt32, 31 validateObject, 32 validateString, 33} = require('internal/validators'); 34 35const { 36 toBuf, 37} = require('internal/crypto/util'); 38 39const { 40 crypto: { 41 TLS1_2_VERSION, 42 TLS1_3_VERSION, 43 }, 44} = internalBinding('constants'); 45 46function getDefaultEcdhCurve() { 47 // We do it this way because DEFAULT_ECDH_CURVE can be 48 // changed by users, so we need to grab the current 49 // value, but we want the evaluation to be lazy. 50 return require('tls').DEFAULT_ECDH_CURVE || 'auto'; 51} 52 53function getDefaultCiphers() { 54 // We do it this way because DEFAULT_CIPHERS can be 55 // changed by users, so we need to grab the current 56 // value, but we want the evaluation to be lazy. 57 return require('tls').DEFAULT_CIPHERS; 58} 59 60function addCACerts(context, certs, name) { 61 ArrayPrototypeForEach(certs, (cert) => { 62 validateKeyOrCertOption(name, cert); 63 context.addCACert(cert); 64 }); 65} 66 67function setCerts(context, certs, name) { 68 ArrayPrototypeForEach(certs, (cert) => { 69 validateKeyOrCertOption(name, cert); 70 context.setCert(cert); 71 }); 72} 73 74function validateKeyOrCertOption(name, value) { 75 if (typeof value !== 'string' && !isArrayBufferView(value)) { 76 throw new ERR_INVALID_ARG_TYPE( 77 name, 78 [ 79 'string', 80 'Buffer', 81 'TypedArray', 82 'DataView', 83 ], 84 value, 85 ); 86 } 87} 88 89function setKey(context, key, passphrase, name) { 90 validateKeyOrCertOption(`${name}.key`, key); 91 if (passphrase !== undefined && passphrase !== null) 92 validateString(passphrase, `${name}.passphrase`); 93 context.setKey(key, passphrase); 94} 95 96function processCiphers(ciphers, name) { 97 ciphers = StringPrototypeSplit(ciphers || getDefaultCiphers(), ':'); 98 99 const cipherList = 100 ArrayPrototypeJoin( 101 ArrayPrototypeFilter( 102 ciphers, 103 (cipher) => { 104 return cipher.length > 0 && 105 !StringPrototypeStartsWith(cipher, 'TLS_'); 106 }), ':'); 107 108 const cipherSuites = 109 ArrayPrototypeJoin( 110 ArrayPrototypeFilter( 111 ciphers, 112 (cipher) => { 113 return cipher.length > 0 && 114 StringPrototypeStartsWith(cipher, 'TLS_'); 115 }), ':'); 116 117 // Specifying empty cipher suites for both TLS1.2 and TLS1.3 is invalid, its 118 // not possible to handshake with no suites. 119 if (cipherSuites === '' && cipherList === '') 120 throw new ERR_INVALID_ARG_VALUE(name, ciphers); 121 122 return { cipherList, cipherSuites }; 123} 124 125function configSecureContext(context, options = kEmptyObject, name = 'options') { 126 validateObject(options, name); 127 128 const { 129 ca, 130 cert, 131 ciphers = getDefaultCiphers(), 132 clientCertEngine, 133 crl, 134 dhparam, 135 ecdhCurve = getDefaultEcdhCurve(), 136 key, 137 passphrase, 138 pfx, 139 privateKeyIdentifier, 140 privateKeyEngine, 141 sessionIdContext, 142 sessionTimeout, 143 sigalgs, 144 ticketKeys, 145 } = options; 146 147 // Add CA before the cert to be able to load cert's issuer in C++ code. 148 // NOTE(@jasnell): ca, cert, and key are permitted to be falsy, so do not 149 // change the checks to !== undefined checks. 150 if (ca) { 151 addCACerts(context, ArrayIsArray(ca) ? ca : [ca], `${name}.ca`); 152 } else { 153 context.addRootCerts(); 154 } 155 156 if (cert) { 157 setCerts(context, ArrayIsArray(cert) ? cert : [cert], `${name}.cert`); 158 } 159 160 // Set the key after the cert. 161 // `ssl_set_pkey` returns `0` when the key does not match the cert, but 162 // `ssl_set_cert` returns `1` and nullifies the key in the SSL structure 163 // which leads to the crash later on. 164 if (key) { 165 if (ArrayIsArray(key)) { 166 for (let i = 0; i < key.length; ++i) { 167 const val = key[i]; 168 const pem = ( 169 val?.pem !== undefined ? val.pem : val); 170 const pass = ( 171 val?.passphrase !== undefined ? val.passphrase : passphrase); 172 setKey(context, pem, pass, name); 173 } 174 } else { 175 setKey(context, key, passphrase, name); 176 } 177 } 178 179 if (sigalgs !== undefined && sigalgs !== null) { 180 validateString(sigalgs, `${name}.sigalgs`); 181 182 if (sigalgs === '') 183 throw new ERR_INVALID_ARG_VALUE(`${name}.sigalgs`, sigalgs); 184 185 context.setSigalgs(sigalgs); 186 } 187 188 if (privateKeyIdentifier !== undefined && privateKeyIdentifier !== null) { 189 if (privateKeyEngine === undefined || privateKeyEngine === null) { 190 // Engine is required when privateKeyIdentifier is present 191 throw new ERR_INVALID_ARG_VALUE(`${name}.privateKeyEngine`, 192 privateKeyEngine); 193 } 194 if (key) { 195 // Both data key and engine key can't be set at the same time 196 throw new ERR_INVALID_ARG_VALUE(`${name}.privateKeyIdentifier`, 197 privateKeyIdentifier); 198 } 199 200 if (typeof privateKeyIdentifier === 'string' && 201 typeof privateKeyEngine === 'string') { 202 if (context.setEngineKey) 203 context.setEngineKey(privateKeyIdentifier, privateKeyEngine); 204 else 205 throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED(); 206 } else if (typeof privateKeyIdentifier !== 'string') { 207 throw new ERR_INVALID_ARG_TYPE(`${name}.privateKeyIdentifier`, 208 ['string', 'null', 'undefined'], 209 privateKeyIdentifier); 210 } else { 211 throw new ERR_INVALID_ARG_TYPE(`${name}.privateKeyEngine`, 212 ['string', 'null', 'undefined'], 213 privateKeyEngine); 214 } 215 } 216 217 if (ciphers !== undefined && ciphers !== null) 218 validateString(ciphers, `${name}.ciphers`); 219 220 // Work around an OpenSSL API quirk. cipherList is for TLSv1.2 and below, 221 // cipherSuites is for TLSv1.3 (and presumably any later versions). TLSv1.3 222 // cipher suites all have a standard name format beginning with TLS_, so split 223 // the ciphers and pass them to the appropriate API. 224 const { 225 cipherList, 226 cipherSuites, 227 } = processCiphers(ciphers, `${name}.ciphers`); 228 229 if (cipherSuites !== '') 230 context.setCipherSuites(cipherSuites); 231 context.setCiphers(cipherList); 232 233 if (cipherList === '' && 234 context.getMinProto() < TLS1_3_VERSION && 235 context.getMaxProto() > TLS1_2_VERSION) { 236 context.setMinProto(TLS1_3_VERSION); 237 } 238 239 validateString(ecdhCurve, `${name}.ecdhCurve`); 240 context.setECDHCurve(ecdhCurve); 241 242 if (dhparam !== undefined && dhparam !== null) { 243 validateKeyOrCertOption(`${name}.dhparam`, dhparam); 244 const warning = context.setDHParam(dhparam === 'auto' || dhparam); 245 if (warning) 246 process.emitWarning(warning, 'SecurityWarning'); 247 } 248 249 if (crl !== undefined && crl !== null) { 250 if (ArrayIsArray(crl)) { 251 for (const val of crl) { 252 validateKeyOrCertOption(`${name}.crl`, val); 253 context.addCRL(val); 254 } 255 } else { 256 validateKeyOrCertOption(`${name}.crl`, crl); 257 context.addCRL(crl); 258 } 259 } 260 261 if (sessionIdContext !== undefined && sessionIdContext !== null) { 262 validateString(sessionIdContext, `${name}.sessionIdContext`); 263 context.setSessionIdContext(sessionIdContext); 264 } 265 266 if (pfx !== undefined && pfx !== null) { 267 if (ArrayIsArray(pfx)) { 268 ArrayPrototypeForEach(pfx, (val) => { 269 const raw = val.buf || val; 270 const pass = val.passphrase || passphrase; 271 if (pass !== undefined && pass !== null) { 272 context.loadPKCS12(toBuf(raw), toBuf(pass)); 273 } else { 274 context.loadPKCS12(toBuf(raw)); 275 } 276 }); 277 } else if (passphrase) { 278 context.loadPKCS12(toBuf(pfx), toBuf(passphrase)); 279 } else { 280 context.loadPKCS12(toBuf(pfx)); 281 } 282 } 283 284 if (typeof clientCertEngine === 'string') { 285 if (typeof context.setClientCertEngine !== 'function') 286 throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED(); 287 else 288 context.setClientCertEngine(clientCertEngine); 289 } else if (clientCertEngine !== undefined && clientCertEngine !== null) { 290 throw new ERR_INVALID_ARG_TYPE(`${name}.clientCertEngine`, 291 ['string', 'null', 'undefined'], 292 clientCertEngine); 293 } 294 295 if (ticketKeys !== undefined && ticketKeys !== null) { 296 validateBuffer(ticketKeys, `${name}.ticketKeys`); 297 if (ticketKeys.byteLength !== 48) { 298 throw new ERR_INVALID_ARG_VALUE( 299 `${name}.ticketKeys`, 300 ticketKeys.byteLength, 301 'must be exactly 48 bytes'); 302 } 303 context.setTicketKeys(ticketKeys); 304 } 305 306 if (sessionTimeout !== undefined && sessionTimeout !== null) { 307 validateInt32(sessionTimeout, `${name}.sessionTimeout`); 308 context.setSessionTimeout(sessionTimeout); 309 } 310} 311 312module.exports = { 313 configSecureContext, 314}; 315