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 JSONParse, 27 ObjectCreate, 28} = primordials; 29 30const { parseCertString } = require('internal/tls'); 31const { isArrayBufferView } = require('internal/util/types'); 32const tls = require('tls'); 33const { 34 ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED, 35 ERR_INVALID_ARG_TYPE, 36 ERR_INVALID_OPT_VALUE, 37 ERR_TLS_INVALID_PROTOCOL_VERSION, 38 ERR_TLS_PROTOCOL_VERSION_CONFLICT, 39} = require('internal/errors').codes; 40const { 41 SSL_OP_CIPHER_SERVER_PREFERENCE, 42 TLS1_VERSION, 43 TLS1_1_VERSION, 44 TLS1_2_VERSION, 45 TLS1_3_VERSION, 46} = internalBinding('constants').crypto; 47 48// Lazily loaded from internal/crypto/util. 49let toBuf = null; 50 51function toV(which, v, def) { 52 if (v == null) v = def; 53 if (v === 'TLSv1') return TLS1_VERSION; 54 if (v === 'TLSv1.1') return TLS1_1_VERSION; 55 if (v === 'TLSv1.2') return TLS1_2_VERSION; 56 if (v === 'TLSv1.3') return TLS1_3_VERSION; 57 throw new ERR_TLS_INVALID_PROTOCOL_VERSION(v, which); 58} 59 60const { SecureContext: NativeSecureContext } = internalBinding('crypto'); 61function SecureContext(secureProtocol, secureOptions, minVersion, maxVersion) { 62 if (!(this instanceof SecureContext)) { 63 return new SecureContext(secureProtocol, secureOptions, minVersion, 64 maxVersion); 65 } 66 67 if (secureProtocol) { 68 if (minVersion != null) 69 throw new ERR_TLS_PROTOCOL_VERSION_CONFLICT(minVersion, secureProtocol); 70 if (maxVersion != null) 71 throw new ERR_TLS_PROTOCOL_VERSION_CONFLICT(maxVersion, secureProtocol); 72 } 73 74 this.context = new NativeSecureContext(); 75 this.context.init(secureProtocol, 76 toV('minimum', minVersion, tls.DEFAULT_MIN_VERSION), 77 toV('maximum', maxVersion, tls.DEFAULT_MAX_VERSION)); 78 79 if (secureOptions) this.context.setOptions(secureOptions); 80} 81 82function validateKeyOrCertOption(name, value) { 83 if (typeof value !== 'string' && !isArrayBufferView(value)) { 84 throw new ERR_INVALID_ARG_TYPE( 85 `options.${name}`, 86 ['string', 'Buffer', 'TypedArray', 'DataView'], 87 value 88 ); 89 } 90} 91 92exports.SecureContext = SecureContext; 93 94 95exports.createSecureContext = function createSecureContext(options) { 96 if (!options) options = {}; 97 98 let secureOptions = options.secureOptions; 99 if (options.honorCipherOrder) 100 secureOptions |= SSL_OP_CIPHER_SERVER_PREFERENCE; 101 102 const c = new SecureContext(options.secureProtocol, secureOptions, 103 options.minVersion, options.maxVersion); 104 105 // Add CA before the cert to be able to load cert's issuer in C++ code. 106 const { ca } = options; 107 if (ca) { 108 if (ArrayIsArray(ca)) { 109 for (const val of ca) { 110 validateKeyOrCertOption('ca', val); 111 c.context.addCACert(val); 112 } 113 } else { 114 validateKeyOrCertOption('ca', ca); 115 c.context.addCACert(ca); 116 } 117 } else { 118 c.context.addRootCerts(); 119 } 120 121 const { cert } = options; 122 if (cert) { 123 if (ArrayIsArray(cert)) { 124 for (const val of cert) { 125 validateKeyOrCertOption('cert', val); 126 c.context.setCert(val); 127 } 128 } else { 129 validateKeyOrCertOption('cert', cert); 130 c.context.setCert(cert); 131 } 132 } 133 134 // Set the key after the cert. 135 // `ssl_set_pkey` returns `0` when the key does not match the cert, but 136 // `ssl_set_cert` returns `1` and nullifies the key in the SSL structure 137 // which leads to the crash later on. 138 const key = options.key; 139 const passphrase = options.passphrase; 140 if (key) { 141 if (ArrayIsArray(key)) { 142 for (const val of key) { 143 // eslint-disable-next-line eqeqeq 144 const pem = (val != undefined && val.pem !== undefined ? val.pem : val); 145 validateKeyOrCertOption('key', pem); 146 c.context.setKey(pem, val.passphrase || passphrase); 147 } 148 } else { 149 validateKeyOrCertOption('key', key); 150 c.context.setKey(key, passphrase); 151 } 152 } 153 154 const sigalgs = options.sigalgs; 155 if (sigalgs !== undefined) { 156 if (typeof sigalgs !== 'string') { 157 throw new ERR_INVALID_ARG_TYPE('options.sigalgs', 'string', sigalgs); 158 } 159 160 if (sigalgs === '') { 161 throw new ERR_INVALID_OPT_VALUE('sigalgs', sigalgs); 162 } 163 164 c.context.setSigalgs(sigalgs); 165 } 166 167 const { privateKeyIdentifier, privateKeyEngine } = options; 168 if (privateKeyIdentifier !== undefined) { 169 if (privateKeyEngine === undefined) { 170 // Engine is required when privateKeyIdentifier is present 171 throw new ERR_INVALID_OPT_VALUE('privateKeyEngine', 172 privateKeyEngine); 173 } 174 if (key) { 175 // Both data key and engine key can't be set at the same time 176 throw new ERR_INVALID_OPT_VALUE('privateKeyIdentifier', 177 privateKeyIdentifier); 178 } 179 180 if (typeof privateKeyIdentifier === 'string' && 181 typeof privateKeyEngine === 'string') { 182 if (c.context.setEngineKey) 183 c.context.setEngineKey(privateKeyIdentifier, privateKeyEngine); 184 else 185 throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED(); 186 } else if (typeof privateKeyIdentifier !== 'string') { 187 throw new ERR_INVALID_ARG_TYPE('options.privateKeyIdentifier', 188 ['string', 'undefined'], 189 privateKeyIdentifier); 190 } else { 191 throw new ERR_INVALID_ARG_TYPE('options.privateKeyEngine', 192 ['string', 'undefined'], 193 privateKeyEngine); 194 } 195 } 196 197 if (options.ciphers && typeof options.ciphers !== 'string') { 198 throw new ERR_INVALID_ARG_TYPE( 199 'options.ciphers', 'string', options.ciphers); 200 } 201 202 // Work around an OpenSSL API quirk. cipherList is for TLSv1.2 and below, 203 // cipherSuites is for TLSv1.3 (and presumably any later versions). TLSv1.3 204 // cipher suites all have a standard name format beginning with TLS_, so split 205 // the ciphers and pass them to the appropriate API. 206 const ciphers = (options.ciphers || tls.DEFAULT_CIPHERS).split(':'); 207 const cipherList = ciphers.filter((_) => !_.match(/^TLS_/) && 208 _.length > 0).join(':'); 209 const cipherSuites = ciphers.filter((_) => _.match(/^TLS_/)).join(':'); 210 211 if (cipherSuites === '' && cipherList === '') { 212 // Specifying empty cipher suites for both TLS1.2 and TLS1.3 is invalid, its 213 // not possible to handshake with no suites. 214 throw new ERR_INVALID_OPT_VALUE('ciphers', ciphers); 215 } 216 217 c.context.setCipherSuites(cipherSuites); 218 c.context.setCiphers(cipherList); 219 220 if (cipherSuites === '' && c.context.getMaxProto() > TLS1_2_VERSION && 221 c.context.getMinProto() < TLS1_3_VERSION) 222 c.context.setMaxProto(TLS1_2_VERSION); 223 224 if (cipherList === '' && c.context.getMinProto() < TLS1_3_VERSION && 225 c.context.getMaxProto() > TLS1_2_VERSION) 226 c.context.setMinProto(TLS1_3_VERSION); 227 228 if (options.ecdhCurve === undefined) 229 c.context.setECDHCurve(tls.DEFAULT_ECDH_CURVE); 230 else if (options.ecdhCurve) 231 c.context.setECDHCurve(options.ecdhCurve); 232 233 if (options.dhparam) { 234 const warning = c.context.setDHParam(options.dhparam); 235 if (warning) 236 process.emitWarning(warning, 'SecurityWarning'); 237 } 238 239 if (options.crl) { 240 if (ArrayIsArray(options.crl)) { 241 for (const crl of options.crl) { 242 c.context.addCRL(crl); 243 } 244 } else { 245 c.context.addCRL(options.crl); 246 } 247 } 248 249 if (options.sessionIdContext) { 250 c.context.setSessionIdContext(options.sessionIdContext); 251 } 252 253 if (options.pfx) { 254 if (!toBuf) 255 toBuf = require('internal/crypto/util').toBuf; 256 257 if (ArrayIsArray(options.pfx)) { 258 for (const pfx of options.pfx) { 259 const raw = pfx.buf ? pfx.buf : pfx; 260 const buf = toBuf(raw); 261 const passphrase = pfx.passphrase || options.passphrase; 262 if (passphrase) { 263 c.context.loadPKCS12(buf, toBuf(passphrase)); 264 } else { 265 c.context.loadPKCS12(buf); 266 } 267 } 268 } else { 269 const buf = toBuf(options.pfx); 270 const passphrase = options.passphrase; 271 if (passphrase) { 272 c.context.loadPKCS12(buf, toBuf(passphrase)); 273 } else { 274 c.context.loadPKCS12(buf); 275 } 276 } 277 } 278 279 // Do not keep read/write buffers in free list for OpenSSL < 1.1.0. (For 280 // OpenSSL 1.1.0, buffers are malloced and freed without the use of a 281 // freelist.) 282 if (options.singleUse) { 283 c.singleUse = true; 284 c.context.setFreeListLength(0); 285 } 286 287 if (typeof options.clientCertEngine === 'string') { 288 if (c.context.setClientCertEngine) 289 c.context.setClientCertEngine(options.clientCertEngine); 290 else 291 throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED(); 292 } else if (options.clientCertEngine != null) { 293 throw new ERR_INVALID_ARG_TYPE('options.clientCertEngine', 294 ['string', 'null', 'undefined'], 295 options.clientCertEngine); 296 } 297 298 if (options.ticketKeys) { 299 c.context.setTicketKeys(options.ticketKeys); 300 } 301 302 if (options.sessionTimeout) { 303 c.context.setSessionTimeout(options.sessionTimeout); 304 } 305 306 return c; 307}; 308 309// Translate some fields from the handle's C-friendly format into more idiomatic 310// javascript object representations before passing them back to the user. Can 311// be used on any cert object, but changing the name would be semver-major. 312exports.translatePeerCertificate = function translatePeerCertificate(c) { 313 if (!c) 314 return null; 315 316 // TODO(tniessen): can we remove parseCertString without breaking anything? 317 if (typeof c.issuer === 'string') c.issuer = parseCertString(c.issuer); 318 if (c.issuerCertificate != null && c.issuerCertificate !== c) { 319 c.issuerCertificate = translatePeerCertificate(c.issuerCertificate); 320 } 321 // TODO(tniessen): can we remove parseCertString without breaking anything? 322 if (typeof c.subject === 'string') c.subject = parseCertString(c.subject); 323 if (c.infoAccess != null) { 324 const info = c.infoAccess; 325 c.infoAccess = ObjectCreate(null); 326 327 // XXX: More key validation? 328 info.replace(/([^\n:]*):([^\n]*)(?:\n|$)/g, (all, key, val) => { 329 if (val.charCodeAt(0) === 0x22) { 330 // The translatePeerCertificate function is only 331 // used on internally created legacy certificate 332 // objects, and any value that contains a quote 333 // will always be a valid JSON string literal, 334 // so this should never throw. 335 val = JSONParse(val); 336 } 337 if (key in c.infoAccess) 338 c.infoAccess[key].push(val); 339 else 340 c.infoAccess[key] = [val]; 341 }); 342 } 343 return c; 344}; 345