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 // Set the cipher list and cipher suite before anything else because 148 // @SECLEVEL=<n> changes the security level and that affects subsequent 149 // operations. 150 if (ciphers !== undefined && ciphers !== null) 151 validateString(ciphers, `${name}.ciphers`); 152 153 // Work around an OpenSSL API quirk. cipherList is for TLSv1.2 and below, 154 // cipherSuites is for TLSv1.3 (and presumably any later versions). TLSv1.3 155 // cipher suites all have a standard name format beginning with TLS_, so split 156 // the ciphers and pass them to the appropriate API. 157 const { 158 cipherList, 159 cipherSuites, 160 } = processCiphers(ciphers, `${name}.ciphers`); 161 162 if (cipherSuites !== '') 163 context.setCipherSuites(cipherSuites); 164 context.setCiphers(cipherList); 165 166 if (cipherList === '' && 167 context.getMinProto() < TLS1_3_VERSION && 168 context.getMaxProto() > TLS1_2_VERSION) { 169 context.setMinProto(TLS1_3_VERSION); 170 } 171 172 // Add CA before the cert to be able to load cert's issuer in C++ code. 173 // NOTE(@jasnell): ca, cert, and key are permitted to be falsy, so do not 174 // change the checks to !== undefined checks. 175 if (ca) { 176 addCACerts(context, ArrayIsArray(ca) ? ca : [ca], `${name}.ca`); 177 } else { 178 context.addRootCerts(); 179 } 180 181 if (cert) { 182 setCerts(context, ArrayIsArray(cert) ? cert : [cert], `${name}.cert`); 183 } 184 185 // Set the key after the cert. 186 // `ssl_set_pkey` returns `0` when the key does not match the cert, but 187 // `ssl_set_cert` returns `1` and nullifies the key in the SSL structure 188 // which leads to the crash later on. 189 if (key) { 190 if (ArrayIsArray(key)) { 191 for (let i = 0; i < key.length; ++i) { 192 const val = key[i]; 193 const pem = ( 194 val?.pem !== undefined ? val.pem : val); 195 const pass = ( 196 val?.passphrase !== undefined ? val.passphrase : passphrase); 197 setKey(context, pem, pass, name); 198 } 199 } else { 200 setKey(context, key, passphrase, name); 201 } 202 } 203 204 if (sigalgs !== undefined && sigalgs !== null) { 205 validateString(sigalgs, `${name}.sigalgs`); 206 207 if (sigalgs === '') 208 throw new ERR_INVALID_ARG_VALUE(`${name}.sigalgs`, sigalgs); 209 210 context.setSigalgs(sigalgs); 211 } 212 213 if (privateKeyIdentifier !== undefined && privateKeyIdentifier !== null) { 214 if (privateKeyEngine === undefined || privateKeyEngine === null) { 215 // Engine is required when privateKeyIdentifier is present 216 throw new ERR_INVALID_ARG_VALUE(`${name}.privateKeyEngine`, 217 privateKeyEngine); 218 } 219 if (key) { 220 // Both data key and engine key can't be set at the same time 221 throw new ERR_INVALID_ARG_VALUE(`${name}.privateKeyIdentifier`, 222 privateKeyIdentifier); 223 } 224 225 if (typeof privateKeyIdentifier === 'string' && 226 typeof privateKeyEngine === 'string') { 227 if (context.setEngineKey) 228 context.setEngineKey(privateKeyIdentifier, privateKeyEngine); 229 else 230 throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED(); 231 } else if (typeof privateKeyIdentifier !== 'string') { 232 throw new ERR_INVALID_ARG_TYPE(`${name}.privateKeyIdentifier`, 233 ['string', 'null', 'undefined'], 234 privateKeyIdentifier); 235 } else { 236 throw new ERR_INVALID_ARG_TYPE(`${name}.privateKeyEngine`, 237 ['string', 'null', 'undefined'], 238 privateKeyEngine); 239 } 240 } 241 242 validateString(ecdhCurve, `${name}.ecdhCurve`); 243 context.setECDHCurve(ecdhCurve); 244 245 if (dhparam !== undefined && dhparam !== null) { 246 validateKeyOrCertOption(`${name}.dhparam`, dhparam); 247 const warning = context.setDHParam(dhparam === 'auto' || dhparam); 248 if (warning) 249 process.emitWarning(warning, 'SecurityWarning'); 250 } 251 252 if (crl !== undefined && crl !== null) { 253 if (ArrayIsArray(crl)) { 254 for (const val of crl) { 255 validateKeyOrCertOption(`${name}.crl`, val); 256 context.addCRL(val); 257 } 258 } else { 259 validateKeyOrCertOption(`${name}.crl`, crl); 260 context.addCRL(crl); 261 } 262 } 263 264 if (sessionIdContext !== undefined && sessionIdContext !== null) { 265 validateString(sessionIdContext, `${name}.sessionIdContext`); 266 context.setSessionIdContext(sessionIdContext); 267 } 268 269 if (pfx !== undefined && pfx !== null) { 270 if (ArrayIsArray(pfx)) { 271 ArrayPrototypeForEach(pfx, (val) => { 272 const raw = val.buf || val; 273 const pass = val.passphrase || passphrase; 274 if (pass !== undefined && pass !== null) { 275 context.loadPKCS12(toBuf(raw), toBuf(pass)); 276 } else { 277 context.loadPKCS12(toBuf(raw)); 278 } 279 }); 280 } else if (passphrase) { 281 context.loadPKCS12(toBuf(pfx), toBuf(passphrase)); 282 } else { 283 context.loadPKCS12(toBuf(pfx)); 284 } 285 } 286 287 if (typeof clientCertEngine === 'string') { 288 if (typeof context.setClientCertEngine !== 'function') 289 throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED(); 290 else 291 context.setClientCertEngine(clientCertEngine); 292 } else if (clientCertEngine !== undefined && clientCertEngine !== null) { 293 throw new ERR_INVALID_ARG_TYPE(`${name}.clientCertEngine`, 294 ['string', 'null', 'undefined'], 295 clientCertEngine); 296 } 297 298 if (ticketKeys !== undefined && ticketKeys !== null) { 299 validateBuffer(ticketKeys, `${name}.ticketKeys`); 300 if (ticketKeys.byteLength !== 48) { 301 throw new ERR_INVALID_ARG_VALUE( 302 `${name}.ticketKeys`, 303 ticketKeys.byteLength, 304 'must be exactly 48 bytes'); 305 } 306 context.setTicketKeys(ticketKeys); 307 } 308 309 if (sessionTimeout !== undefined && sessionTimeout !== null) { 310 validateInt32(sessionTimeout, `${name}.sessionTimeout`); 311 context.setSessionTimeout(sessionTimeout); 312 } 313} 314 315module.exports = { 316 configSecureContext, 317}; 318