nstdlib-nightly
Version:
Node.js standard library converted to runtime-agnostic ES modules.
305 lines (267 loc) • 9.33 kB
JavaScript
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/tls/secure-context.js
import { codes as __codes__ } from "nstdlib/lib/internal/errors";
import { kEmptyObject } from "nstdlib/lib/internal/util";
import { isArrayBufferView } from "nstdlib/lib/internal/util/types";
import {
validateBuffer,
validateInt32,
validateObject,
validateString,
} from "nstdlib/lib/internal/validators";
import { toBuf } from "nstdlib/lib/internal/crypto/util";
import { crypto as __crypto__ } from "nstdlib/stub/binding/constants";
import * as __hoisted_tls__ from "nstdlib/lib/tls";
const {
ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
} = __codes__;
const { TLS1_2_VERSION, TLS1_3_VERSION } = __crypto__;
function getDefaultEcdhCurve() {
// We do it this way because DEFAULT_ECDH_CURVE can be
// changed by users, so we need to grab the current
// value, but we want the evaluation to be lazy.
return __hoisted_tls__.DEFAULT_ECDH_CURVE || "auto";
}
function getDefaultCiphers() {
// We do it this way because DEFAULT_CIPHERS can be
// changed by users, so we need to grab the current
// value, but we want the evaluation to be lazy.
return __hoisted_tls__.DEFAULT_CIPHERS;
}
function addCACerts(context, certs, name) {
Array.prototype.forEach.call(certs, (cert) => {
validateKeyOrCertOption(name, cert);
context.addCACert(cert);
});
}
function setCerts(context, certs, name) {
Array.prototype.forEach.call(certs, (cert) => {
validateKeyOrCertOption(name, cert);
context.setCert(cert);
});
}
function validateKeyOrCertOption(name, value) {
if (typeof value !== "string" && !isArrayBufferView(value)) {
throw new ERR_INVALID_ARG_TYPE(
name,
["string", "Buffer", "TypedArray", "DataView"],
value,
);
}
}
function setKey(context, key, passphrase, name) {
validateKeyOrCertOption(`${name}.key`, key);
if (passphrase !== undefined && passphrase !== null)
validateString(passphrase, `${name}.passphrase`);
context.setKey(key, passphrase);
}
function processCiphers(ciphers, name) {
ciphers = String.prototype.split.call(ciphers || getDefaultCiphers(), ":");
const cipherList = Array.prototype.join.call(
Array.prototype.filter.call(ciphers, (cipher) => {
if (cipher.length === 0) return false;
if (String.prototype.startsWith.call(cipher, "TLS_")) return false;
if (String.prototype.startsWith.call(cipher, "!TLS_")) return false;
return true;
}),
":",
);
const cipherSuites = Array.prototype.join.call(
Array.prototype.filter.call(ciphers, (cipher) => {
if (cipher.length === 0) return false;
if (String.prototype.startsWith.call(cipher, "TLS_")) return true;
if (String.prototype.startsWith.call(cipher, "!TLS_")) return true;
return false;
}),
":",
);
// 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(name, ciphers);
return { cipherList, cipherSuites };
}
function configSecureContext(
context,
options = kEmptyObject,
name = "options",
) {
validateObject(options, name);
const {
ca,
cert,
ciphers = getDefaultCiphers(),
clientCertEngine,
crl,
dhparam,
ecdhCurve = getDefaultEcdhCurve(),
key,
passphrase,
pfx,
privateKeyIdentifier,
privateKeyEngine,
sessionIdContext,
sessionTimeout,
sigalgs,
ticketKeys,
} = options;
// Set the cipher list and cipher suite before anything else because
// @SECLEVEL=<n> changes the security level and that affects subsequent
// operations.
if (ciphers !== undefined && ciphers !== null)
validateString(ciphers, `${name}.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 { cipherList, cipherSuites } = processCiphers(
ciphers,
`${name}.ciphers`,
);
if (cipherSuites !== "") context.setCipherSuites(cipherSuites);
context.setCiphers(cipherList);
if (
cipherList === "" &&
context.getMinProto() < TLS1_3_VERSION &&
context.getMaxProto() > TLS1_2_VERSION
) {
context.setMinProto(TLS1_3_VERSION);
}
// Add CA before the cert to be able to load cert's issuer in C++ code.
// NOTE(@jasnell): ca, cert, and key are permitted to be falsy, so do not
// change the checks to !== undefined checks.
if (ca) {
addCACerts(context, Array.isArray(ca) ? ca : [ca], `${name}.ca`);
} else {
context.addRootCerts();
}
if (cert) {
setCerts(context, Array.isArray(cert) ? cert : [cert], `${name}.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.
if (key) {
if (Array.isArray(key)) {
for (let i = 0; i < key.length; ++i) {
const val = key[i];
const pem = val?.pem !== undefined ? val.pem : val;
const pass =
val?.passphrase !== undefined ? val.passphrase : passphrase;
setKey(context, pem, pass, name);
}
} else {
setKey(context, key, passphrase, name);
}
}
if (sigalgs !== undefined && sigalgs !== null) {
validateString(sigalgs, `${name}.sigalgs`);
if (sigalgs === "")
throw new ERR_INVALID_ARG_VALUE(`${name}.sigalgs`, sigalgs);
context.setSigalgs(sigalgs);
}
if (privateKeyIdentifier !== undefined && privateKeyIdentifier !== null) {
if (privateKeyEngine === undefined || privateKeyEngine === null) {
// Engine is required when privateKeyIdentifier is present
throw new ERR_INVALID_ARG_VALUE(
`${name}.privateKeyEngine`,
privateKeyEngine,
);
}
if (key) {
// Both data key and engine key can't be set at the same time
throw new ERR_INVALID_ARG_VALUE(
`${name}.privateKeyIdentifier`,
privateKeyIdentifier,
);
}
if (
typeof privateKeyIdentifier === "string" &&
typeof privateKeyEngine === "string"
) {
if (context.setEngineKey)
context.setEngineKey(privateKeyIdentifier, privateKeyEngine);
else throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED();
} else if (typeof privateKeyIdentifier !== "string") {
throw new ERR_INVALID_ARG_TYPE(
`${name}.privateKeyIdentifier`,
["string", "null", "undefined"],
privateKeyIdentifier,
);
} else {
throw new ERR_INVALID_ARG_TYPE(
`${name}.privateKeyEngine`,
["string", "null", "undefined"],
privateKeyEngine,
);
}
}
validateString(ecdhCurve, `${name}.ecdhCurve`);
context.setECDHCurve(ecdhCurve);
if (dhparam !== undefined && dhparam !== null) {
validateKeyOrCertOption(`${name}.dhparam`, dhparam);
const warning = context.setDHParam(dhparam === "auto" || dhparam);
if (warning) process.emitWarning(warning, "SecurityWarning");
}
if (crl !== undefined && crl !== null) {
if (Array.isArray(crl)) {
for (const val of crl) {
validateKeyOrCertOption(`${name}.crl`, val);
context.addCRL(val);
}
} else {
validateKeyOrCertOption(`${name}.crl`, crl);
context.addCRL(crl);
}
}
if (sessionIdContext !== undefined && sessionIdContext !== null) {
validateString(sessionIdContext, `${name}.sessionIdContext`);
context.setSessionIdContext(sessionIdContext);
}
if (pfx !== undefined && pfx !== null) {
if (Array.isArray(pfx)) {
Array.prototype.forEach.call(pfx, (val) => {
const raw = val.buf || val;
const pass = val.passphrase || passphrase;
if (pass !== undefined && pass !== null) {
context.loadPKCS12(toBuf(raw), toBuf(pass));
} else {
context.loadPKCS12(toBuf(raw));
}
});
} else if (passphrase) {
context.loadPKCS12(toBuf(pfx), toBuf(passphrase));
} else {
context.loadPKCS12(toBuf(pfx));
}
}
if (typeof clientCertEngine === "string") {
if (typeof context.setClientCertEngine !== "function")
throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED();
else context.setClientCertEngine(clientCertEngine);
} else if (clientCertEngine !== undefined && clientCertEngine !== null) {
throw new ERR_INVALID_ARG_TYPE(
`${name}.clientCertEngine`,
["string", "null", "undefined"],
clientCertEngine,
);
}
if (ticketKeys !== undefined && ticketKeys !== null) {
validateBuffer(ticketKeys, `${name}.ticketKeys`);
if (ticketKeys.byteLength !== 48) {
throw new ERR_INVALID_ARG_VALUE(
`${name}.ticketKeys`,
ticketKeys.byteLength,
"must be exactly 48 bytes",
);
}
context.setTicketKeys(ticketKeys);
}
if (sessionTimeout !== undefined && sessionTimeout !== null) {
validateInt32(sessionTimeout, `${name}.sessionTimeout`, 0);
context.setSessionTimeout(sessionTimeout);
}
}
export { configSecureContext };