mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
src,crypto: refactoring of crypto_context, SecureContext
Cleaup and improvement of crypto_context and SecureContext. Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: https://github.com/nodejs/node/pull/35665 Reviewed-By: Alba Mendez <me@alba.sh> Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
This commit is contained in:
@@ -23,7 +23,11 @@
|
||||
|
||||
const {
|
||||
ArrayIsArray,
|
||||
ArrayPrototypeFilter,
|
||||
ArrayPrototypeJoin,
|
||||
ObjectCreate,
|
||||
StringPrototypeSplit,
|
||||
StringPrototypeStartsWith,
|
||||
} = primordials;
|
||||
|
||||
const { parseCertString } = require('internal/tls');
|
||||
@@ -44,8 +48,15 @@ const {
|
||||
TLS1_3_VERSION,
|
||||
} = internalBinding('constants').crypto;
|
||||
|
||||
// Lazily loaded from internal/crypto/util.
|
||||
let toBuf = null;
|
||||
const {
|
||||
validateString,
|
||||
validateInteger,
|
||||
validateInt32,
|
||||
} = require('internal/validators');
|
||||
|
||||
const {
|
||||
toBuf
|
||||
} = require('internal/crypto/util');
|
||||
|
||||
function toV(which, v, def) {
|
||||
if (v == null) v = def;
|
||||
@@ -75,7 +86,10 @@ function SecureContext(secureProtocol, secureOptions, minVersion, maxVersion) {
|
||||
toV('minimum', minVersion, tls.DEFAULT_MIN_VERSION),
|
||||
toV('maximum', maxVersion, tls.DEFAULT_MAX_VERSION));
|
||||
|
||||
if (secureOptions) this.context.setOptions(secureOptions);
|
||||
if (secureOptions) {
|
||||
validateInteger(secureOptions, 'secureOptions');
|
||||
this.context.setOptions(secureOptions);
|
||||
}
|
||||
}
|
||||
|
||||
function validateKeyOrCertOption(name, value) {
|
||||
@@ -90,80 +104,136 @@ function validateKeyOrCertOption(name, value) {
|
||||
|
||||
exports.SecureContext = SecureContext;
|
||||
|
||||
function setKey(context, key, passphrase) {
|
||||
validateKeyOrCertOption('key', key);
|
||||
if (passphrase != null)
|
||||
validateString(passphrase, 'options.passphrase');
|
||||
context.setKey(key, passphrase);
|
||||
}
|
||||
|
||||
function processCiphers(ciphers) {
|
||||
ciphers = StringPrototypeSplit(ciphers || tls.DEFAULT_CIPHERS, ':');
|
||||
|
||||
const cipherList =
|
||||
ArrayPrototypeJoin(
|
||||
ArrayPrototypeFilter(
|
||||
ciphers,
|
||||
(cipher) => {
|
||||
return cipher.length > 0 &&
|
||||
!StringPrototypeStartsWith(cipher, 'TLS_');
|
||||
}), ':');
|
||||
|
||||
const cipherSuites =
|
||||
ArrayPrototypeJoin(
|
||||
ArrayPrototypeFilter(
|
||||
ciphers,
|
||||
(cipher) => {
|
||||
return cipher.length > 0 &&
|
||||
StringPrototypeStartsWith(cipher, 'TLS_');
|
||||
}), ':');
|
||||
|
||||
// Specifying empty cipher suites for both TLS1.2 and TLS1.3 is invalid, its
|
||||
// not possible to handshake with no suites.
|
||||
if (cipherSuites === '' && cipherList === '')
|
||||
throw new ERR_INVALID_ARG_VALUE('options.ciphers', ciphers);
|
||||
|
||||
return { cipherList, cipherSuites };
|
||||
}
|
||||
|
||||
function addCACerts(context, ...certs) {
|
||||
for (const cert of certs) {
|
||||
validateKeyOrCertOption('ca', cert);
|
||||
context.addCACert(cert);
|
||||
}
|
||||
}
|
||||
|
||||
function setCerts(context, ...certs) {
|
||||
for (const cert of certs) {
|
||||
validateKeyOrCertOption('cert', cert);
|
||||
context.setCert(cert);
|
||||
}
|
||||
}
|
||||
|
||||
exports.createSecureContext = function createSecureContext(options) {
|
||||
if (!options) options = {};
|
||||
|
||||
let secureOptions = options.secureOptions;
|
||||
if (options.honorCipherOrder)
|
||||
const {
|
||||
ca,
|
||||
cert,
|
||||
ciphers,
|
||||
clientCertEngine,
|
||||
crl,
|
||||
dhparam,
|
||||
ecdhCurve = tls.DEFAULT_ECDH_CURVE,
|
||||
honorCipherOrder,
|
||||
key,
|
||||
minVersion,
|
||||
maxVersion,
|
||||
passphrase,
|
||||
pfx,
|
||||
privateKeyIdentifier,
|
||||
privateKeyEngine,
|
||||
secureProtocol,
|
||||
sessionIdContext,
|
||||
sessionTimeout,
|
||||
sigalgs,
|
||||
singleUse,
|
||||
ticketKeys,
|
||||
} = options;
|
||||
|
||||
let { secureOptions } = options;
|
||||
|
||||
if (honorCipherOrder)
|
||||
secureOptions |= SSL_OP_CIPHER_SERVER_PREFERENCE;
|
||||
|
||||
const c = new SecureContext(options.secureProtocol, secureOptions,
|
||||
options.minVersion, options.maxVersion);
|
||||
const c = new SecureContext(secureProtocol, secureOptions,
|
||||
minVersion, maxVersion);
|
||||
|
||||
// Add CA before the cert to be able to load cert's issuer in C++ code.
|
||||
const { ca } = options;
|
||||
// NOTE(@jasnell): ca, cert, and key are permitted to be falsy, so do not
|
||||
// change the checks to !== undefined checks.
|
||||
if (ca) {
|
||||
if (ArrayIsArray(ca)) {
|
||||
for (const val of ca) {
|
||||
validateKeyOrCertOption('ca', val);
|
||||
c.context.addCACert(val);
|
||||
}
|
||||
} else {
|
||||
validateKeyOrCertOption('ca', ca);
|
||||
c.context.addCACert(ca);
|
||||
}
|
||||
if (ArrayIsArray(ca))
|
||||
addCACerts(c.context, ...ca);
|
||||
else
|
||||
addCACerts(c.context, ca);
|
||||
} else {
|
||||
c.context.addRootCerts();
|
||||
}
|
||||
|
||||
const { cert } = options;
|
||||
if (cert) {
|
||||
if (ArrayIsArray(cert)) {
|
||||
for (const val of cert) {
|
||||
validateKeyOrCertOption('cert', val);
|
||||
c.context.setCert(val);
|
||||
}
|
||||
} else {
|
||||
validateKeyOrCertOption('cert', cert);
|
||||
c.context.setCert(cert);
|
||||
}
|
||||
if (ArrayIsArray(cert))
|
||||
setCerts(c.context, ...cert);
|
||||
else
|
||||
setCerts(c.context, cert);
|
||||
}
|
||||
|
||||
// Set the key after the cert.
|
||||
// `ssl_set_pkey` returns `0` when the key does not match the cert, but
|
||||
// `ssl_set_cert` returns `1` and nullifies the key in the SSL structure
|
||||
// which leads to the crash later on.
|
||||
const key = options.key;
|
||||
const passphrase = options.passphrase;
|
||||
if (key) {
|
||||
if (ArrayIsArray(key)) {
|
||||
for (const val of key) {
|
||||
// eslint-disable-next-line eqeqeq
|
||||
const pem = (val != undefined && val.pem !== undefined ? val.pem : val);
|
||||
validateKeyOrCertOption('key', pem);
|
||||
c.context.setKey(pem, val.passphrase || passphrase);
|
||||
setKey(c.context, pem, val.passphrase || passphrase);
|
||||
}
|
||||
} else {
|
||||
validateKeyOrCertOption('key', key);
|
||||
c.context.setKey(key, passphrase);
|
||||
setKey(c.context, key, passphrase);
|
||||
}
|
||||
}
|
||||
|
||||
const sigalgs = options.sigalgs;
|
||||
if (sigalgs !== undefined) {
|
||||
if (typeof sigalgs !== 'string') {
|
||||
if (typeof sigalgs !== 'string')
|
||||
throw new ERR_INVALID_ARG_TYPE('options.sigalgs', 'string', sigalgs);
|
||||
}
|
||||
|
||||
if (sigalgs === '') {
|
||||
if (sigalgs === '')
|
||||
throw new ERR_INVALID_ARG_VALUE('options.sigalgs', sigalgs);
|
||||
}
|
||||
|
||||
c.context.setSigalgs(sigalgs);
|
||||
}
|
||||
|
||||
const { privateKeyIdentifier, privateKeyEngine } = options;
|
||||
if (privateKeyIdentifier !== undefined) {
|
||||
if (privateKeyEngine === undefined) {
|
||||
// Engine is required when privateKeyIdentifier is present
|
||||
@@ -193,113 +263,113 @@ exports.createSecureContext = function createSecureContext(options) {
|
||||
}
|
||||
}
|
||||
|
||||
if (options.ciphers && typeof options.ciphers !== 'string') {
|
||||
throw new ERR_INVALID_ARG_TYPE(
|
||||
'options.ciphers', 'string', options.ciphers);
|
||||
}
|
||||
if (ciphers !== undefined)
|
||||
validateString(ciphers, 'options.ciphers');
|
||||
|
||||
// Work around an OpenSSL API quirk. cipherList is for TLSv1.2 and below,
|
||||
// cipherSuites is for TLSv1.3 (and presumably any later versions). TLSv1.3
|
||||
// cipher suites all have a standard name format beginning with TLS_, so split
|
||||
// the ciphers and pass them to the appropriate API.
|
||||
const ciphers = (options.ciphers || tls.DEFAULT_CIPHERS).split(':');
|
||||
const cipherList = ciphers.filter((_) => !_.match(/^TLS_/) &&
|
||||
_.length > 0).join(':');
|
||||
const cipherSuites = ciphers.filter((_) => _.match(/^TLS_/)).join(':');
|
||||
|
||||
if (cipherSuites === '' && cipherList === '') {
|
||||
// Specifying empty cipher suites for both TLS1.2 and TLS1.3 is invalid, its
|
||||
// not possible to handshake with no suites.
|
||||
throw new ERR_INVALID_ARG_VALUE('options.ciphers', ciphers);
|
||||
}
|
||||
const { cipherList, cipherSuites } = processCiphers(ciphers);
|
||||
|
||||
c.context.setCipherSuites(cipherSuites);
|
||||
c.context.setCiphers(cipherList);
|
||||
|
||||
if (cipherSuites === '' && c.context.getMaxProto() > TLS1_2_VERSION &&
|
||||
c.context.getMinProto() < TLS1_3_VERSION)
|
||||
if (cipherSuites === '' &&
|
||||
c.context.getMaxProto() > TLS1_2_VERSION &&
|
||||
c.context.getMinProto() < TLS1_3_VERSION) {
|
||||
c.context.setMaxProto(TLS1_2_VERSION);
|
||||
}
|
||||
|
||||
if (cipherList === '' && c.context.getMinProto() < TLS1_3_VERSION &&
|
||||
c.context.getMaxProto() > TLS1_2_VERSION)
|
||||
if (cipherList === '' &&
|
||||
c.context.getMinProto() < TLS1_3_VERSION &&
|
||||
c.context.getMaxProto() > TLS1_2_VERSION) {
|
||||
c.context.setMinProto(TLS1_3_VERSION);
|
||||
}
|
||||
|
||||
if (options.ecdhCurve === undefined)
|
||||
c.context.setECDHCurve(tls.DEFAULT_ECDH_CURVE);
|
||||
else if (options.ecdhCurve)
|
||||
c.context.setECDHCurve(options.ecdhCurve);
|
||||
validateString(ecdhCurve, 'options.ecdhCurve');
|
||||
c.context.setECDHCurve(ecdhCurve);
|
||||
|
||||
if (options.dhparam) {
|
||||
const warning = c.context.setDHParam(options.dhparam);
|
||||
if (dhparam !== undefined) {
|
||||
validateKeyOrCertOption('dhparam', dhparam);
|
||||
const warning = c.context.setDHParam(dhparam);
|
||||
if (warning)
|
||||
process.emitWarning(warning, 'SecurityWarning');
|
||||
}
|
||||
|
||||
if (options.crl) {
|
||||
if (ArrayIsArray(options.crl)) {
|
||||
for (const crl of options.crl) {
|
||||
c.context.addCRL(crl);
|
||||
if (crl !== undefined) {
|
||||
if (ArrayIsArray(crl)) {
|
||||
for (const val of crl) {
|
||||
validateKeyOrCertOption('crl', val);
|
||||
c.context.addCRL(val);
|
||||
}
|
||||
} else {
|
||||
c.context.addCRL(options.crl);
|
||||
validateKeyOrCertOption('crl', crl);
|
||||
c.context.addCRL(crl);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.sessionIdContext) {
|
||||
c.context.setSessionIdContext(options.sessionIdContext);
|
||||
if (sessionIdContext !== undefined) {
|
||||
validateString(sessionIdContext, 'options.sessionIdContext');
|
||||
c.context.setSessionIdContext(sessionIdContext);
|
||||
}
|
||||
|
||||
if (options.pfx) {
|
||||
if (!toBuf)
|
||||
toBuf = require('internal/crypto/util').toBuf;
|
||||
|
||||
if (ArrayIsArray(options.pfx)) {
|
||||
for (const pfx of options.pfx) {
|
||||
const raw = pfx.buf ? pfx.buf : pfx;
|
||||
const buf = toBuf(raw);
|
||||
const passphrase = pfx.passphrase || options.passphrase;
|
||||
if (passphrase) {
|
||||
c.context.loadPKCS12(buf, toBuf(passphrase));
|
||||
if (pfx !== undefined) {
|
||||
if (ArrayIsArray(pfx)) {
|
||||
for (const val of pfx) {
|
||||
const raw = val.buf ? val.buf : val;
|
||||
const pass = val.passphrase || passphrase;
|
||||
if (pass !== undefined) {
|
||||
c.context.loadPKCS12(toBuf(raw), toBuf(pass));
|
||||
} else {
|
||||
c.context.loadPKCS12(buf);
|
||||
c.context.loadPKCS12(toBuf(raw));
|
||||
}
|
||||
}
|
||||
} else if (passphrase) {
|
||||
c.context.loadPKCS12(toBuf(pfx), toBuf(passphrase));
|
||||
} else {
|
||||
const buf = toBuf(options.pfx);
|
||||
const passphrase = options.passphrase;
|
||||
if (passphrase) {
|
||||
c.context.loadPKCS12(buf, toBuf(passphrase));
|
||||
} else {
|
||||
c.context.loadPKCS12(buf);
|
||||
}
|
||||
c.context.loadPKCS12(toBuf(pfx));
|
||||
}
|
||||
}
|
||||
|
||||
// Do not keep read/write buffers in free list for OpenSSL < 1.1.0. (For
|
||||
// OpenSSL 1.1.0, buffers are malloced and freed without the use of a
|
||||
// freelist.)
|
||||
if (options.singleUse) {
|
||||
if (singleUse) {
|
||||
c.singleUse = true;
|
||||
c.context.setFreeListLength(0);
|
||||
}
|
||||
|
||||
if (typeof options.clientCertEngine === 'string') {
|
||||
if (c.context.setClientCertEngine)
|
||||
c.context.setClientCertEngine(options.clientCertEngine);
|
||||
else
|
||||
if (clientCertEngine !== undefined) {
|
||||
if (typeof c.context.setClientCertEngine !== 'function')
|
||||
throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED();
|
||||
} else if (options.clientCertEngine != null) {
|
||||
throw new ERR_INVALID_ARG_TYPE('options.clientCertEngine',
|
||||
['string', 'null', 'undefined'],
|
||||
options.clientCertEngine);
|
||||
if (typeof clientCertEngine !== 'string') {
|
||||
throw new ERR_INVALID_ARG_TYPE('options.clientCertEngine',
|
||||
['string', 'null', 'undefined'],
|
||||
clientCertEngine);
|
||||
}
|
||||
c.context.setClientCertEngine(clientCertEngine);
|
||||
}
|
||||
|
||||
if (options.ticketKeys) {
|
||||
c.context.setTicketKeys(options.ticketKeys);
|
||||
if (ticketKeys !== undefined) {
|
||||
if (!isArrayBufferView(ticketKeys)) {
|
||||
throw new ERR_INVALID_ARG_TYPE(
|
||||
'options.ticketKeys',
|
||||
['Buffer', 'TypedArray', 'DataView'],
|
||||
ticketKeys);
|
||||
}
|
||||
if (ticketKeys.byteLength !== 48) {
|
||||
throw new ERR_INVALID_ARG_VALUE(
|
||||
'options.ticketKeys',
|
||||
ticketKeys.byteLenth,
|
||||
'must be exactly 48 bytes');
|
||||
}
|
||||
c.context.setTicketKeys(ticketKeys);
|
||||
}
|
||||
|
||||
if (options.sessionTimeout) {
|
||||
c.context.setSessionTimeout(options.sessionTimeout);
|
||||
if (sessionTimeout !== undefined) {
|
||||
validateInt32(sessionTimeout, 'options.sessionTimeout');
|
||||
c.context.setSessionTimeout(sessionTimeout);
|
||||
}
|
||||
|
||||
return c;
|
||||
|
||||
@@ -56,7 +56,7 @@ BIOPointer LoadBIO(Environment* env, Local<Value> v) {
|
||||
HandleScope scope(env->isolate());
|
||||
|
||||
if (v->IsString()) {
|
||||
const node::Utf8Value s(env->isolate(), v);
|
||||
Utf8Value s(env->isolate(), v);
|
||||
return NodeBIO::NewFixed(*s, s.length());
|
||||
}
|
||||
|
||||
@@ -365,7 +365,7 @@ void SecureContext::Init(const FunctionCallbackInfo<Value>& args) {
|
||||
max_version = kMaxSupportedVersion;
|
||||
|
||||
if (args[0]->IsString()) {
|
||||
const node::Utf8Value sslmethod(env->isolate(), args[0]);
|
||||
Utf8Value sslmethod(env->isolate(), args[0]);
|
||||
|
||||
// Note that SSLv2 and SSLv3 are disallowed but SSLv23_method and friends
|
||||
// are still accepted. They are OpenSSL's way of saying that all known
|
||||
@@ -471,7 +471,8 @@ void SecureContext::Init(const FunctionCallbackInfo<Value>& args) {
|
||||
if (RAND_bytes(sc->ticket_key_name_, sizeof(sc->ticket_key_name_)) <= 0 ||
|
||||
RAND_bytes(sc->ticket_key_hmac_, sizeof(sc->ticket_key_hmac_)) <= 0 ||
|
||||
RAND_bytes(sc->ticket_key_aes_, sizeof(sc->ticket_key_aes_)) <= 0) {
|
||||
return env->ThrowError("Error generating ticket keys");
|
||||
return THROW_ERR_CRYPTO_OPERATION_FAILED(
|
||||
env, "Error generating ticket keys");
|
||||
}
|
||||
SSL_CTX_set_tlsext_ticket_key_cb(sc->ctx_.get(), TicketCompatibilityCallback);
|
||||
}
|
||||
@@ -502,21 +503,7 @@ void SecureContext::SetKey(const FunctionCallbackInfo<Value>& args) {
|
||||
SecureContext* sc;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
||||
|
||||
unsigned int len = args.Length();
|
||||
if (len < 1) {
|
||||
return THROW_ERR_MISSING_ARGS(env, "Private key argument is mandatory");
|
||||
}
|
||||
|
||||
if (len > 2) {
|
||||
return env->ThrowError("Only private key and pass phrase are expected");
|
||||
}
|
||||
|
||||
if (len == 2) {
|
||||
if (args[1]->IsUndefined() || args[1]->IsNull())
|
||||
len = 1;
|
||||
else
|
||||
THROW_AND_RETURN_IF_NOT_STRING(env, args[1], "Pass phrase");
|
||||
}
|
||||
CHECK_GE(args.Length(), 1); // Private key argument is mandatory
|
||||
|
||||
BIOPointer bio(LoadBIO(env, args[0]));
|
||||
if (!bio)
|
||||
@@ -536,17 +523,11 @@ void SecureContext::SetKey(const FunctionCallbackInfo<Value>& args) {
|
||||
PasswordCallback,
|
||||
&pass_ptr));
|
||||
|
||||
if (!key) {
|
||||
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
|
||||
return ThrowCryptoError(env, err, "PEM_read_bio_PrivateKey");
|
||||
}
|
||||
if (!key)
|
||||
return ThrowCryptoError(env, ERR_get_error(), "PEM_read_bio_PrivateKey");
|
||||
|
||||
int rv = SSL_CTX_use_PrivateKey(sc->ctx_.get(), key.get());
|
||||
|
||||
if (!rv) {
|
||||
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
|
||||
return ThrowCryptoError(env, err, "SSL_CTX_use_PrivateKey");
|
||||
}
|
||||
if (!SSL_CTX_use_PrivateKey(sc->ctx_.get(), key.get()))
|
||||
return ThrowCryptoError(env, ERR_get_error(), "SSL_CTX_use_PrivateKey");
|
||||
}
|
||||
|
||||
void SecureContext::SetSigalgs(const FunctionCallbackInfo<Value>& args) {
|
||||
@@ -558,13 +539,10 @@ void SecureContext::SetSigalgs(const FunctionCallbackInfo<Value>& args) {
|
||||
CHECK_EQ(args.Length(), 1);
|
||||
CHECK(args[0]->IsString());
|
||||
|
||||
const node::Utf8Value sigalgs(env->isolate(), args[0]);
|
||||
const Utf8Value sigalgs(env->isolate(), args[0]);
|
||||
|
||||
int rv = SSL_CTX_set1_sigalgs_list(sc->ctx_.get(), *sigalgs);
|
||||
|
||||
if (rv == 0) {
|
||||
if (!SSL_CTX_set1_sigalgs_list(sc->ctx_.get(), *sigalgs))
|
||||
return ThrowCryptoError(env, ERR_get_error());
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef OPENSSL_NO_ENGINE
|
||||
@@ -577,7 +555,7 @@ void SecureContext::SetEngineKey(const FunctionCallbackInfo<Value>& args) {
|
||||
CHECK_EQ(args.Length(), 2);
|
||||
|
||||
CryptoErrorVector errors;
|
||||
const Utf8Value engine_id(env->isolate(), args[1]);
|
||||
Utf8Value engine_id(env->isolate(), args[1]);
|
||||
EnginePointer engine = LoadEngineById(*engine_id, &errors);
|
||||
if (!engine) {
|
||||
Local<Value> exception;
|
||||
@@ -587,24 +565,21 @@ void SecureContext::SetEngineKey(const FunctionCallbackInfo<Value>& args) {
|
||||
}
|
||||
|
||||
if (!ENGINE_init(engine.get())) {
|
||||
return env->ThrowError("ENGINE_init");
|
||||
return THROW_ERR_CRYPTO_OPERATION_FAILED(
|
||||
env, "Failure to initialize engine");
|
||||
}
|
||||
|
||||
engine.finish_on_exit = true;
|
||||
|
||||
const Utf8Value key_name(env->isolate(), args[0]);
|
||||
Utf8Value key_name(env->isolate(), args[0]);
|
||||
EVPKeyPointer key(ENGINE_load_private_key(engine.get(), *key_name,
|
||||
nullptr, nullptr));
|
||||
|
||||
if (!key) {
|
||||
if (!key)
|
||||
return ThrowCryptoError(env, ERR_get_error(), "ENGINE_load_private_key");
|
||||
}
|
||||
|
||||
int rv = SSL_CTX_use_PrivateKey(sc->ctx_.get(), key.get());
|
||||
|
||||
if (rv == 0) {
|
||||
if (!SSL_CTX_use_PrivateKey(sc->ctx_.get(), key.get()))
|
||||
return ThrowCryptoError(env, ERR_get_error(), "SSL_CTX_use_PrivateKey");
|
||||
}
|
||||
|
||||
sc->private_key_engine_ = std::move(engine);
|
||||
}
|
||||
@@ -616,9 +591,7 @@ void SecureContext::SetCert(const FunctionCallbackInfo<Value>& args) {
|
||||
SecureContext* sc;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
||||
|
||||
if (args.Length() != 1) {
|
||||
return THROW_ERR_MISSING_ARGS(env, "Certificate argument is mandatory");
|
||||
}
|
||||
CHECK_GE(args.Length(), 1); // Certificate argument is mandator
|
||||
|
||||
BIOPointer bio(LoadBIO(env, args[0]));
|
||||
if (!bio)
|
||||
@@ -627,14 +600,15 @@ void SecureContext::SetCert(const FunctionCallbackInfo<Value>& args) {
|
||||
sc->cert_.reset();
|
||||
sc->issuer_.reset();
|
||||
|
||||
int rv = SSL_CTX_use_certificate_chain(sc->ctx_.get(),
|
||||
std::move(bio),
|
||||
&sc->cert_,
|
||||
&sc->issuer_);
|
||||
|
||||
if (!rv) {
|
||||
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
|
||||
return ThrowCryptoError(env, err, "SSL_CTX_use_certificate_chain");
|
||||
if (!SSL_CTX_use_certificate_chain(
|
||||
sc->ctx_.get(),
|
||||
std::move(bio),
|
||||
&sc->cert_,
|
||||
&sc->issuer_)) {
|
||||
return ThrowCryptoError(
|
||||
env,
|
||||
ERR_get_error(),
|
||||
"SSL_CTX_use_certificate_chain");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -645,9 +619,7 @@ void SecureContext::AddCACert(const FunctionCallbackInfo<Value>& args) {
|
||||
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
||||
ClearErrorOnReturn clear_error_on_return;
|
||||
|
||||
if (args.Length() != 1) {
|
||||
return THROW_ERR_MISSING_ARGS(env, "CA certificate argument is mandatory");
|
||||
}
|
||||
CHECK_GE(args.Length(), 1); // CA certificate argument is mandatory
|
||||
|
||||
BIOPointer bio(LoadBIO(env, args[0]));
|
||||
if (!bio)
|
||||
@@ -672,9 +644,7 @@ void SecureContext::AddCRL(const FunctionCallbackInfo<Value>& args) {
|
||||
SecureContext* sc;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
||||
|
||||
if (args.Length() != 1) {
|
||||
return THROW_ERR_MISSING_ARGS(env, "CRL argument is mandatory");
|
||||
}
|
||||
CHECK_GE(args.Length(), 1); // CRL argument is mandatory
|
||||
|
||||
ClearErrorOnReturn clear_error_on_return;
|
||||
|
||||
@@ -686,7 +656,7 @@ void SecureContext::AddCRL(const FunctionCallbackInfo<Value>& args) {
|
||||
PEM_read_bio_X509_CRL(bio.get(), nullptr, NoPasswordCallback, nullptr));
|
||||
|
||||
if (!crl)
|
||||
return env->ThrowError("Failed to parse CRL");
|
||||
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to parse CRL");
|
||||
|
||||
X509_STORE* cert_store = SSL_CTX_get_cert_store(sc->ctx_.get());
|
||||
if (cert_store == root_cert_store) {
|
||||
@@ -699,59 +669,6 @@ void SecureContext::AddCRL(const FunctionCallbackInfo<Value>& args) {
|
||||
X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
|
||||
}
|
||||
|
||||
static unsigned long AddCertsFromFile( // NOLINT(runtime/int)
|
||||
X509_STORE* store,
|
||||
const char* file) {
|
||||
ERR_clear_error();
|
||||
MarkPopErrorOnReturn mark_pop_error_on_return;
|
||||
|
||||
BIOPointer bio(BIO_new_file(file, "r"));
|
||||
if (!bio)
|
||||
return ERR_get_error();
|
||||
|
||||
while (X509* x509 =
|
||||
PEM_read_bio_X509(bio.get(), nullptr, NoPasswordCallback, nullptr)) {
|
||||
X509_STORE_add_cert(store, x509);
|
||||
X509_free(x509);
|
||||
}
|
||||
|
||||
unsigned long err = ERR_peek_error(); // NOLINT(runtime/int)
|
||||
// Ignore error if its EOF/no start line found.
|
||||
if (ERR_GET_LIB(err) == ERR_LIB_PEM &&
|
||||
ERR_GET_REASON(err) == PEM_R_NO_START_LINE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void UseExtraCaCerts(const std::string& file) {
|
||||
ClearErrorOnReturn clear_error_on_return;
|
||||
|
||||
if (root_cert_store == nullptr) {
|
||||
root_cert_store = NewRootCertStore();
|
||||
|
||||
if (!file.empty()) {
|
||||
unsigned long err = AddCertsFromFile( // NOLINT(runtime/int)
|
||||
root_cert_store,
|
||||
file.c_str());
|
||||
if (err) {
|
||||
fprintf(stderr,
|
||||
"Warning: Ignoring extra certs from `%s`, load failed: %s\n",
|
||||
file.c_str(),
|
||||
ERR_error_string(err, nullptr));
|
||||
} else {
|
||||
extra_root_certs_loaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IsExtraRootCertsFileLoaded(
|
||||
const FunctionCallbackInfo<Value>& args) {
|
||||
return args.GetReturnValue().Set(extra_root_certs_loaded);
|
||||
}
|
||||
|
||||
void SecureContext::AddRootCerts(const FunctionCallbackInfo<Value>& args) {
|
||||
SecureContext* sc;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
||||
@@ -777,11 +694,9 @@ void SecureContext::SetCipherSuites(const FunctionCallbackInfo<Value>& args) {
|
||||
CHECK_EQ(args.Length(), 1);
|
||||
CHECK(args[0]->IsString());
|
||||
|
||||
const node::Utf8Value ciphers(args.GetIsolate(), args[0]);
|
||||
if (!SSL_CTX_set_ciphersuites(sc->ctx_.get(), *ciphers)) {
|
||||
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
|
||||
return ThrowCryptoError(env, err, "Failed to set ciphers");
|
||||
}
|
||||
const Utf8Value ciphers(env->isolate(), args[0]);
|
||||
if (!SSL_CTX_set_ciphersuites(sc->ctx_.get(), *ciphers))
|
||||
return ThrowCryptoError(env, ERR_get_error(), "Failed to set ciphers");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -794,7 +709,7 @@ void SecureContext::SetCiphers(const FunctionCallbackInfo<Value>& args) {
|
||||
CHECK_EQ(args.Length(), 1);
|
||||
CHECK(args[0]->IsString());
|
||||
|
||||
const node::Utf8Value ciphers(args.GetIsolate(), args[0]);
|
||||
Utf8Value ciphers(env->isolate(), args[0]);
|
||||
if (!SSL_CTX_set_cipher_list(sc->ctx_.get(), *ciphers)) {
|
||||
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
|
||||
|
||||
@@ -814,18 +729,15 @@ void SecureContext::SetECDHCurve(const FunctionCallbackInfo<Value>& args) {
|
||||
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
||||
Environment* env = sc->env();
|
||||
|
||||
if (args.Length() != 1)
|
||||
return THROW_ERR_MISSING_ARGS(env, "ECDH curve name argument is mandatory");
|
||||
CHECK_GE(args.Length(), 1); // ECDH curve name argument is mandatory
|
||||
CHECK(args[0]->IsString());
|
||||
|
||||
THROW_AND_RETURN_IF_NOT_STRING(env, args[0], "ECDH curve name");
|
||||
Utf8Value curve(env->isolate(), args[0]);
|
||||
|
||||
node::Utf8Value curve(env->isolate(), args[0]);
|
||||
|
||||
if (strcmp(*curve, "auto") == 0)
|
||||
return;
|
||||
|
||||
if (!SSL_CTX_set1_curves_list(sc->ctx_.get(), *curve))
|
||||
return env->ThrowError("Failed to set ECDH curve");
|
||||
if (strcmp(*curve, "auto") != 0 &&
|
||||
!SSL_CTX_set1_curves_list(sc->ctx_.get(), *curve)) {
|
||||
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to set ECDH curve");
|
||||
}
|
||||
}
|
||||
|
||||
void SecureContext::SetDHParam(const FunctionCallbackInfo<Value>& args) {
|
||||
@@ -834,10 +746,7 @@ void SecureContext::SetDHParam(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = sc->env();
|
||||
ClearErrorOnReturn clear_error_on_return;
|
||||
|
||||
// Auto DH is not supported in openssl 1.0.1, so dhparam needs
|
||||
// to be specified explicitly
|
||||
if (args.Length() != 1)
|
||||
return THROW_ERR_MISSING_ARGS(env, "DH argument is mandatory");
|
||||
CHECK_GE(args.Length(), 1); // DH argument is mandatory
|
||||
|
||||
DHPointer dh;
|
||||
{
|
||||
@@ -864,10 +773,11 @@ void SecureContext::SetDHParam(const FunctionCallbackInfo<Value>& args) {
|
||||
}
|
||||
|
||||
SSL_CTX_set_options(sc->ctx_.get(), SSL_OP_SINGLE_DH_USE);
|
||||
int r = SSL_CTX_set_tmp_dh(sc->ctx_.get(), dh.get());
|
||||
|
||||
if (!r)
|
||||
return env->ThrowTypeError("Error setting temp DH parameter");
|
||||
if (!SSL_CTX_set_tmp_dh(sc->ctx_.get(), dh.get())) {
|
||||
return THROW_ERR_CRYPTO_OPERATION_FAILED(
|
||||
env, "Error setting temp DH parameter");
|
||||
}
|
||||
}
|
||||
|
||||
void SecureContext::SetMinProto(const FunctionCallbackInfo<Value>& args) {
|
||||
@@ -917,15 +827,14 @@ void SecureContext::GetMaxProto(const FunctionCallbackInfo<Value>& args) {
|
||||
}
|
||||
|
||||
void SecureContext::SetOptions(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
SecureContext* sc;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
||||
int64_t val;
|
||||
|
||||
if (args.Length() != 1 ||
|
||||
!args[0]->IntegerValue(args.GetIsolate()->GetCurrentContext()).To(&val)) {
|
||||
return THROW_ERR_INVALID_ARG_TYPE(
|
||||
sc->env(), "Options must be an integer value");
|
||||
}
|
||||
CHECK_GE(args.Length(), 1);
|
||||
CHECK(args[0]->IsNumber());
|
||||
|
||||
int64_t val = args[0]->IntegerValue(env->context()).FromMaybe(0);
|
||||
|
||||
SSL_CTX_set_options(sc->ctx_.get(),
|
||||
static_cast<long>(val)); // NOLINT(runtime/int)
|
||||
@@ -937,20 +846,15 @@ void SecureContext::SetSessionIdContext(
|
||||
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
||||
Environment* env = sc->env();
|
||||
|
||||
if (args.Length() != 1) {
|
||||
return THROW_ERR_MISSING_ARGS(
|
||||
env, "Session ID context argument is mandatory");
|
||||
}
|
||||
CHECK_GE(args.Length(), 1);
|
||||
CHECK(args[0]->IsString());
|
||||
|
||||
THROW_AND_RETURN_IF_NOT_STRING(env, args[0], "Session ID context");
|
||||
|
||||
const node::Utf8Value sessionIdContext(args.GetIsolate(), args[0]);
|
||||
const Utf8Value sessionIdContext(env->isolate(), args[0]);
|
||||
const unsigned char* sid_ctx =
|
||||
reinterpret_cast<const unsigned char*>(*sessionIdContext);
|
||||
unsigned int sid_ctx_len = sessionIdContext.length();
|
||||
|
||||
int r = SSL_CTX_set_session_id_context(sc->ctx_.get(), sid_ctx, sid_ctx_len);
|
||||
if (r == 1)
|
||||
if (SSL_CTX_set_session_id_context(sc->ctx_.get(), sid_ctx, sid_ctx_len) == 1)
|
||||
return;
|
||||
|
||||
BUF_MEM* mem;
|
||||
@@ -958,25 +862,23 @@ void SecureContext::SetSessionIdContext(
|
||||
|
||||
BIOPointer bio(BIO_new(BIO_s_mem()));
|
||||
if (!bio) {
|
||||
message = FIXED_ONE_BYTE_STRING(args.GetIsolate(),
|
||||
message = FIXED_ONE_BYTE_STRING(env->isolate(),
|
||||
"SSL_CTX_set_session_id_context error");
|
||||
} else {
|
||||
ERR_print_errors(bio.get());
|
||||
BIO_get_mem_ptr(bio.get(), &mem);
|
||||
message = OneByteString(args.GetIsolate(), mem->data, mem->length);
|
||||
message = OneByteString(env->isolate(), mem->data, mem->length);
|
||||
}
|
||||
|
||||
args.GetIsolate()->ThrowException(Exception::TypeError(message));
|
||||
env->isolate()->ThrowException(Exception::TypeError(message));
|
||||
}
|
||||
|
||||
void SecureContext::SetSessionTimeout(const FunctionCallbackInfo<Value>& args) {
|
||||
SecureContext* sc;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
||||
|
||||
if (args.Length() != 1 || !args[0]->IsInt32()) {
|
||||
return THROW_ERR_INVALID_ARG_TYPE(
|
||||
sc->env(), "Session timeout must be a 32-bit integer");
|
||||
}
|
||||
CHECK_GE(args.Length(), 1);
|
||||
CHECK(args[0]->IsInt32());
|
||||
|
||||
int32_t sessionTimeout = args[0].As<Int32>()->Value();
|
||||
SSL_CTX_set_timeout(sc->ctx_.get(), sessionTimeout);
|
||||
@@ -1004,8 +906,10 @@ void SecureContext::LoadPKCS12(const FunctionCallbackInfo<Value>& args) {
|
||||
}
|
||||
|
||||
BIOPointer in(LoadBIO(env, args[0]));
|
||||
if (!in)
|
||||
return env->ThrowError("Unable to load BIO");
|
||||
if (!in) {
|
||||
return THROW_ERR_CRYPTO_OPERATION_FAILED(
|
||||
env, "Unable to load PFX certificate");
|
||||
}
|
||||
|
||||
if (args.Length() >= 2) {
|
||||
THROW_AND_RETURN_IF_NOT_BUFFER(env, args[1], "Pass phrase");
|
||||
@@ -1060,6 +964,7 @@ void SecureContext::LoadPKCS12(const FunctionCallbackInfo<Value>& args) {
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
// TODO(@jasnell): Should this use ThrowCryptoError?
|
||||
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
|
||||
const char* str = ERR_reason_error_string(err);
|
||||
return env->ThrowError(str);
|
||||
@@ -1083,13 +988,10 @@ void SecureContext::SetClientCertEngine(
|
||||
// internal context variable.
|
||||
// Instead of trying to fix up this problem we in turn also do not
|
||||
// support multiple calls to SetClientCertEngine.
|
||||
if (sc->client_cert_engine_provided_) {
|
||||
return env->ThrowError(
|
||||
"Multiple calls to SetClientCertEngine are not allowed");
|
||||
}
|
||||
CHECK(!sc->client_cert_engine_provided_);
|
||||
|
||||
CryptoErrorVector errors;
|
||||
const node::Utf8Value engine_id(env->isolate(), args[0]);
|
||||
const Utf8Value engine_id(env->isolate(), args[0]);
|
||||
EnginePointer engine = LoadEngineById(*engine_id, &errors);
|
||||
if (!engine) {
|
||||
Local<Value> exception;
|
||||
@@ -1099,8 +1001,7 @@ void SecureContext::SetClientCertEngine(
|
||||
}
|
||||
|
||||
// Note that this takes another reference to `engine`.
|
||||
int r = SSL_CTX_set_client_cert_engine(sc->ctx_.get(), engine.get());
|
||||
if (r == 0)
|
||||
if (!SSL_CTX_set_client_cert_engine(sc->ctx_.get(), engine.get()))
|
||||
return ThrowCryptoError(env, ERR_get_error());
|
||||
sc->client_cert_engine_provided_ = true;
|
||||
}
|
||||
@@ -1128,20 +1029,12 @@ void SecureContext::SetTicketKeys(const FunctionCallbackInfo<Value>& args) {
|
||||
#if !defined(OPENSSL_NO_TLSEXT) && defined(SSL_CTX_get_tlsext_ticket_keys)
|
||||
SecureContext* wrap;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
|
||||
Environment* env = wrap->env();
|
||||
|
||||
// TODO(@sam-github) Move type and len check to js, and CHECK() in C++.
|
||||
if (args.Length() < 1) {
|
||||
return THROW_ERR_MISSING_ARGS(env, "Ticket keys argument is mandatory");
|
||||
}
|
||||
|
||||
THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "Ticket keys");
|
||||
CHECK_GE(args.Length(), 1); // Ticket keys argument is mandatory
|
||||
CHECK(args[0]->IsArrayBufferView());
|
||||
ArrayBufferViewContents<char> buf(args[0].As<ArrayBufferView>());
|
||||
|
||||
if (buf.length() != 48) {
|
||||
return THROW_ERR_INVALID_ARG_VALUE(
|
||||
env, "Ticket keys length must be 48 bytes");
|
||||
}
|
||||
CHECK_EQ(buf.length(), 48);
|
||||
|
||||
memcpy(wrap->ticket_key_name_, buf.data(), 16);
|
||||
memcpy(wrap->ticket_key_hmac_, buf.data() + 16, 16);
|
||||
@@ -1332,5 +1225,62 @@ void SecureContext::GetCertificate(const FunctionCallbackInfo<Value>& args) {
|
||||
args.GetReturnValue().Set(buff);
|
||||
}
|
||||
|
||||
namespace {
|
||||
unsigned long AddCertsFromFile( // NOLINT(runtime/int)
|
||||
X509_STORE* store,
|
||||
const char* file) {
|
||||
ERR_clear_error();
|
||||
MarkPopErrorOnReturn mark_pop_error_on_return;
|
||||
|
||||
BIOPointer bio(BIO_new_file(file, "r"));
|
||||
if (!bio)
|
||||
return ERR_get_error();
|
||||
|
||||
while (X509* x509 =
|
||||
PEM_read_bio_X509(bio.get(), nullptr, NoPasswordCallback, nullptr)) {
|
||||
X509_STORE_add_cert(store, x509);
|
||||
X509_free(x509);
|
||||
}
|
||||
|
||||
unsigned long err = ERR_peek_error(); // NOLINT(runtime/int)
|
||||
// Ignore error if its EOF/no start line found.
|
||||
if (ERR_GET_LIB(err) == ERR_LIB_PEM &&
|
||||
ERR_GET_REASON(err) == PEM_R_NO_START_LINE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// UseExtraCaCerts is called only once at the start of the Node.js process.
|
||||
void UseExtraCaCerts(const std::string& file) {
|
||||
ClearErrorOnReturn clear_error_on_return;
|
||||
|
||||
if (root_cert_store == nullptr) {
|
||||
root_cert_store = NewRootCertStore();
|
||||
|
||||
if (!file.empty()) {
|
||||
unsigned long err = AddCertsFromFile( // NOLINT(runtime/int)
|
||||
root_cert_store,
|
||||
file.c_str());
|
||||
if (err) {
|
||||
fprintf(stderr,
|
||||
"Warning: Ignoring extra certs from `%s`, load failed: %s\n",
|
||||
file.c_str(),
|
||||
ERR_error_string(err, nullptr));
|
||||
} else {
|
||||
extra_root_certs_loaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exposed to JavaScript strictly for testing purposes.
|
||||
void IsExtraRootCertsFileLoaded(
|
||||
const FunctionCallbackInfo<Value>& args) {
|
||||
return args.GetReturnValue().Set(extra_root_certs_loaded);
|
||||
}
|
||||
|
||||
} // namespace crypto
|
||||
} // namespace node
|
||||
|
||||
@@ -30,7 +30,7 @@ assert.throws(
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: 'Pass phrase must be a string'
|
||||
message: /The "options\.passphrase" property must be of type string/
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
@@ -38,7 +38,7 @@ assert.throws(
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: 'Pass phrase must be a string'
|
||||
message: /The "options\.passphrase" property must be of type string/
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
@@ -46,7 +46,7 @@ assert.throws(
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: 'ECDH curve name must be a string'
|
||||
message: /The "options\.ecdhCurve" property must be of type string/
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
@@ -64,7 +64,7 @@ assert.throws(
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: 'Session timeout must be a 32-bit integer'
|
||||
message: /The "options\.sessionTimeout" property must be of type number/
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
@@ -72,11 +72,13 @@ assert.throws(
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
name: 'TypeError',
|
||||
message: 'Ticket keys must be a buffer'
|
||||
message: /The "options\.ticketKeys" property must be an instance of/
|
||||
});
|
||||
|
||||
assert.throws(() => tls.createServer({ ticketKeys: Buffer.alloc(0) }),
|
||||
/TypeError: Ticket keys length must be 48 bytes/);
|
||||
assert.throws(() => tls.createServer({ ticketKeys: Buffer.alloc(0) }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
message: /The property 'options\.ticketKeys' must be exactly 48 bytes/
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
() => tls.createSecurePair({}),
|
||||
|
||||
Reference in New Issue
Block a user