1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23 24const { 25 ArrayIsArray, 26 ObjectCreate, 27} = primordials; 28 29const { parseCertString } = require('internal/tls'); 30const { isArrayBufferView } = require('internal/util/types'); 31const tls = require('tls'); 32const { 33 ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED, 34 ERR_INVALID_ARG_TYPE, 35 ERR_INVALID_OPT_VALUE, 36 ERR_TLS_INVALID_PROTOCOL_VERSION, 37 ERR_TLS_PROTOCOL_VERSION_CONFLICT, 38} = require('internal/errors').codes; 39const { 40 SSL_OP_CIPHER_SERVER_PREFERENCE, 41 TLS1_VERSION, 42 TLS1_1_VERSION, 43 TLS1_2_VERSION, 44 TLS1_3_VERSION, 45} = internalBinding('constants').crypto; 46 47// Lazily loaded from internal/crypto/util. 48let toBuf = null; 49 50function toV(which, v, def) { 51 if (v == null) v = def; 52 if (v === 'TLSv1') return TLS1_VERSION; 53 if (v === 'TLSv1.1') return TLS1_1_VERSION; 54 if (v === 'TLSv1.2') return TLS1_2_VERSION; 55 if (v === 'TLSv1.3') return TLS1_3_VERSION; 56 throw new ERR_TLS_INVALID_PROTOCOL_VERSION(v, which); 57} 58 59const { SecureContext: NativeSecureContext } = internalBinding('crypto'); 60function SecureContext(secureProtocol, secureOptions, minVersion, maxVersion) { 61 if (!(this instanceof SecureContext)) { 62 return new SecureContext(secureProtocol, secureOptions, minVersion, 63 maxVersion); 64 } 65 66 if (secureProtocol) { 67 if (minVersion != null) 68 throw new ERR_TLS_PROTOCOL_VERSION_CONFLICT(minVersion, secureProtocol); 69 if (maxVersion != null) 70 throw new ERR_TLS_PROTOCOL_VERSION_CONFLICT(maxVersion, secureProtocol); 71 } 72 73 this.context = new NativeSecureContext(); 74 this.context.init(secureProtocol, 75 toV('minimum', minVersion, tls.DEFAULT_MIN_VERSION), 76 toV('maximum', maxVersion, tls.DEFAULT_MAX_VERSION)); 77 78 if (secureOptions) this.context.setOptions(secureOptions); 79} 80 81function validateKeyOrCertOption(name, value) { 82 if (typeof value !== 'string' && !isArrayBufferView(value)) { 83 throw new ERR_INVALID_ARG_TYPE( 84 `options.${name}`, 85 ['string', 'Buffer', 'TypedArray', 'DataView'], 86 value 87 ); 88 } 89} 90 91exports.SecureContext = SecureContext; 92 93 94exports.createSecureContext = function createSecureContext(options) { 95 if (!options) options = {}; 96 97 let secureOptions = options.secureOptions; 98 if (options.honorCipherOrder) 99 secureOptions |= SSL_OP_CIPHER_SERVER_PREFERENCE; 100 101 const c = new SecureContext(options.secureProtocol, secureOptions, 102 options.minVersion, options.maxVersion); 103 104 // Add CA before the cert to be able to load cert's issuer in C++ code. 105 const { ca } = options; 106 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 } 116 } else { 117 c.context.addRootCerts(); 118 } 119 120 const { cert } = options; 121 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 } 131 } 132 133 // Set the key after the cert. 134 // `ssl_set_pkey` returns `0` when the key does not match the cert, but 135 // `ssl_set_cert` returns `1` and nullifies the key in the SSL structure 136 // which leads to the crash later on. 137 const key = options.key; 138 const passphrase = options.passphrase; 139 if (key) { 140 if (ArrayIsArray(key)) { 141 for (const val of key) { 142 // eslint-disable-next-line eqeqeq 143 const pem = (val != undefined && val.pem !== undefined ? val.pem : val); 144 validateKeyOrCertOption('key', pem); 145 c.context.setKey(pem, val.passphrase || passphrase); 146 } 147 } else { 148 validateKeyOrCertOption('key', key); 149 c.context.setKey(key, passphrase); 150 } 151 } 152 153 const sigalgs = options.sigalgs; 154 if (sigalgs !== undefined) { 155 if (typeof sigalgs !== 'string') { 156 throw new ERR_INVALID_ARG_TYPE('options.sigalgs', 'string', sigalgs); 157 } 158 159 if (sigalgs === '') { 160 throw new ERR_INVALID_OPT_VALUE('sigalgs', sigalgs); 161 } 162 163 c.context.setSigalgs(sigalgs); 164 } 165 166 const { privateKeyIdentifier, privateKeyEngine } = options; 167 if (privateKeyIdentifier !== undefined) { 168 if (privateKeyEngine === undefined) { 169 // Engine is required when privateKeyIdentifier is present 170 throw new ERR_INVALID_OPT_VALUE('privateKeyEngine', 171 privateKeyEngine); 172 } 173 if (key) { 174 // Both data key and engine key can't be set at the same time 175 throw new ERR_INVALID_OPT_VALUE('privateKeyIdentifier', 176 privateKeyIdentifier); 177 } 178 179 if (typeof privateKeyIdentifier === 'string' && 180 typeof privateKeyEngine === 'string') { 181 if (c.context.setEngineKey) 182 c.context.setEngineKey(privateKeyIdentifier, privateKeyEngine); 183 else 184 throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED(); 185 } else if (typeof privateKeyIdentifier !== 'string') { 186 throw new ERR_INVALID_ARG_TYPE('options.privateKeyIdentifier', 187 ['string', 'undefined'], 188 privateKeyIdentifier); 189 } else { 190 throw new ERR_INVALID_ARG_TYPE('options.privateKeyEngine', 191 ['string', 'undefined'], 192 privateKeyEngine); 193 } 194 } 195 196 if (options.ciphers && typeof options.ciphers !== 'string') { 197 throw new ERR_INVALID_ARG_TYPE( 198 'options.ciphers', 'string', options.ciphers); 199 } 200 201 // Work around an OpenSSL API quirk. cipherList is for TLSv1.2 and below, 202 // cipherSuites is for TLSv1.3 (and presumably any later versions). TLSv1.3 203 // cipher suites all have a standard name format beginning with TLS_, so split 204 // 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_OPT_VALUE('ciphers', ciphers); 214 } 215 216 c.context.setCipherSuites(cipherSuites); 217 c.context.setCiphers(cipherList); 218 219 if (cipherSuites === '' && c.context.getMaxProto() > TLS1_2_VERSION && 220 c.context.getMinProto() < TLS1_3_VERSION) 221 c.context.setMaxProto(TLS1_2_VERSION); 222 223 if (cipherList === '' && c.context.getMinProto() < TLS1_3_VERSION && 224 c.context.getMaxProto() > TLS1_2_VERSION) 225 c.context.setMinProto(TLS1_3_VERSION); 226 227 if (options.ecdhCurve === undefined) 228 c.context.setECDHCurve(tls.DEFAULT_ECDH_CURVE); 229 else if (options.ecdhCurve) 230 c.context.setECDHCurve(options.ecdhCurve); 231 232 if (options.dhparam) { 233 const warning = c.context.setDHParam(options.dhparam); 234 if (warning) 235 process.emitWarning(warning, 'SecurityWarning'); 236 } 237 238 if (options.crl) { 239 if (ArrayIsArray(options.crl)) { 240 for (const crl of options.crl) { 241 c.context.addCRL(crl); 242 } 243 } else { 244 c.context.addCRL(options.crl); 245 } 246 } 247 248 if (options.sessionIdContext) { 249 c.context.setSessionIdContext(options.sessionIdContext); 250 } 251 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)); 263 } else { 264 c.context.loadPKCS12(buf); 265 } 266 } 267 } 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 } 275 } 276 } 277 278 // Do not keep read/write buffers in free list for OpenSSL < 1.1.0. (For 279 // OpenSSL 1.1.0, buffers are malloced and freed without the use of a 280 // freelist.) 281 if (options.singleUse) { 282 c.singleUse = true; 283 c.context.setFreeListLength(0); 284 } 285 286 if (typeof options.clientCertEngine === 'string') { 287 if (c.context.setClientCertEngine) 288 c.context.setClientCertEngine(options.clientCertEngine); 289 else 290 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); 295 } 296 297 if (options.ticketKeys) { 298 c.context.setTicketKeys(options.ticketKeys); 299 } 300 301 if (options.sessionTimeout) { 302 c.context.setSessionTimeout(options.sessionTimeout); 303 } 304 305 return c; 306}; 307 308// Translate some fields from the handle's C-friendly format into more idiomatic 309// javascript object representations before passing them back to the user. Can 310// be used on any cert object, but changing the name would be semver-major. 311exports.translatePeerCertificate = function translatePeerCertificate(c) { 312 if (!c) 313 return null; 314 315 if (c.issuer != null) c.issuer = parseCertString(c.issuer); 316 if (c.issuerCertificate != null && c.issuerCertificate !== c) { 317 c.issuerCertificate = translatePeerCertificate(c.issuerCertificate); 318 } 319 if (c.subject != null) c.subject = parseCertString(c.subject); 320 if (c.infoAccess != null) { 321 const info = c.infoAccess; 322 c.infoAccess = ObjectCreate(null); 323 324 // XXX: More key validation? 325 info.replace(/([^\n:]*):([^\n]*)(?:\n|$)/g, (all, key, val) => { 326 if (key in c.infoAccess) 327 c.infoAccess[key].push(val); 328 else 329 c.infoAccess[key] = [val]; 330 }); 331 } 332 return c; 333}; 334