mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 18:25:13 +08:00
crypto: harden X509Certificate state
Construct X509Certificate instances directly on the existing native X.509 wrapper and keep lazy cached values in private state. Use a non-throwing native brand check while preserving the existing structured-clone behavior. Preserve derived-constructor prototype semantics without rereading new.target.prototype. Add coverage for receiver validation, hidden state, prototype edge cases, subclassing, and structured cloning through workers and message ports. Replace the key-only instanceof lint rule with a module-aware crypto class guard and extend it to X509Certificate. Resolve references by lexical binding so shadowed constructor names remain valid. Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: https://github.com/nodejs/node/pull/65518 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
This commit is contained in:
1 parent
3541d5d00d
commit
fe6a4cd9c5
14 files changed
+1059
-483
No files matched your search
@@ -445,8 +445,8 @@ export default [
|
||||
'node-core/lowercase-name-for-primitive': 'error',
|
||||
'node-core/non-ascii-character': 'error',
|
||||
'node-core/no-array-destructuring': 'error',
|
||||
'node-core/no-crypto-class-instanceof': 'error',
|
||||
'node-core/no-cryptokey-public-accessors': 'error',
|
||||
'node-core/no-keyobject-cryptokey-instanceof': 'error',
|
||||
'node-core/no-keyobject-public-accessors': 'error',
|
||||
'node-core/prefer-primordials': [
|
||||
'error',
|
||||
|
||||
+295
-271
@@ -1,13 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
FunctionPrototypeCall,
|
||||
ObjectGetPrototypeOf,
|
||||
ObjectSetPrototypeOf,
|
||||
SafeMap,
|
||||
Symbol,
|
||||
SafeWeakMap,
|
||||
} = primordials;
|
||||
|
||||
const {
|
||||
parseX509,
|
||||
createX509CertificateClass,
|
||||
isX509Certificate: isNativeX509Certificate,
|
||||
X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT,
|
||||
X509_CHECK_FLAG_NEVER_CHECK_SUBJECT,
|
||||
X509_CHECK_FLAG_NO_WILDCARDS,
|
||||
@@ -46,26 +48,13 @@ const {
|
||||
codes: {
|
||||
ERR_INVALID_ARG_TYPE,
|
||||
ERR_INVALID_ARG_VALUE,
|
||||
ERR_INVALID_THIS,
|
||||
},
|
||||
} = require('internal/errors');
|
||||
|
||||
const {
|
||||
markTransferMode,
|
||||
kClone,
|
||||
kDeserialize,
|
||||
} = require('internal/worker/js_transferable');
|
||||
|
||||
const {
|
||||
kHandle,
|
||||
} = require('internal/crypto/util');
|
||||
|
||||
let lazyTranslatePeerCertificate;
|
||||
|
||||
const kInternalState = Symbol('kInternalState');
|
||||
|
||||
function isX509Certificate(value) {
|
||||
return value[kInternalState] !== undefined;
|
||||
}
|
||||
let isX509Certificate;
|
||||
|
||||
function getFlags(options = kEmptyObject) {
|
||||
validateObject(options, 'options');
|
||||
@@ -96,312 +85,347 @@ function getFlags(options = kEmptyObject) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
class InternalX509Certificate {
|
||||
[kInternalState] = new SafeMap();
|
||||
const crossRealmState = new SafeWeakMap();
|
||||
|
||||
constructor(handle) {
|
||||
markTransferMode(this, true, false);
|
||||
this[kHandle] = handle;
|
||||
}
|
||||
const kSlotSubject = 0;
|
||||
const kSlotSubjectAltName = 1;
|
||||
const kSlotIssuer = 2;
|
||||
const kSlotIssuerCertificate = 3;
|
||||
const kSlotInfoAccess = 4;
|
||||
const kSlotValidFrom = 5;
|
||||
const kSlotValidTo = 6;
|
||||
const kSlotValidFromDate = 7;
|
||||
const kSlotValidToDate = 8;
|
||||
const kSlotFingerprint = 9;
|
||||
const kSlotFingerprint256 = 10;
|
||||
const kSlotFingerprint512 = 11;
|
||||
const kSlotKeyUsage = 12;
|
||||
const kSlotSerialNumber = 13;
|
||||
const kSlotSignatureAlgorithm = 14;
|
||||
const kSlotSignatureAlgorithmOid = 15;
|
||||
const kSlotRaw = 16;
|
||||
const kSlotPublicKey = 17;
|
||||
const kSlotPem = 18;
|
||||
const kSlotCA = 19;
|
||||
const kStateCacheMask = 20;
|
||||
|
||||
function createState() {
|
||||
const state = [];
|
||||
state[kStateCacheMask] = 0;
|
||||
return state;
|
||||
}
|
||||
|
||||
class X509Certificate {
|
||||
[kInternalState] = new SafeMap();
|
||||
let InternalX509Certificate;
|
||||
|
||||
constructor(buffer) {
|
||||
if (typeof buffer === 'string')
|
||||
buffer = Buffer.from(buffer);
|
||||
if (!isArrayBufferView(buffer)) {
|
||||
throw new ERR_INVALID_ARG_TYPE(
|
||||
'buffer',
|
||||
['string', 'Buffer', 'TypedArray', 'DataView'],
|
||||
buffer);
|
||||
const {
|
||||
0: X509Certificate,
|
||||
1: InternalX509CertificateConstructor,
|
||||
} = createX509CertificateClass((NativeX509Certificate) => {
|
||||
const {
|
||||
checkCA: nativeCheckCA,
|
||||
checkEmail: nativeCheckEmail,
|
||||
checkHost: nativeCheckHost,
|
||||
checkIP: nativeCheckIP,
|
||||
checkIssued: nativeCheckIssued,
|
||||
checkPrivateKey: nativeCheckPrivateKey,
|
||||
fingerprint: nativeFingerprint,
|
||||
fingerprint256: nativeFingerprint256,
|
||||
fingerprint512: nativeFingerprint512,
|
||||
getIssuerCert: nativeGetIssuerCert,
|
||||
infoAccess: nativeInfoAccess,
|
||||
issuer: nativeIssuer,
|
||||
keyUsage: nativeKeyUsage,
|
||||
pem: nativePem,
|
||||
publicKey: nativePublicKey,
|
||||
raw: nativeRaw,
|
||||
serialNumber: nativeSerialNumber,
|
||||
signatureAlgorithm: nativeSignatureAlgorithm,
|
||||
signatureAlgorithmOid: nativeSignatureAlgorithmOid,
|
||||
subject: nativeSubject,
|
||||
subjectAltName: nativeSubjectAltName,
|
||||
toLegacy: nativeToLegacy,
|
||||
validFrom: nativeValidFrom,
|
||||
validFromDate: nativeValidFromDate,
|
||||
validTo: nativeValidTo,
|
||||
validToDate: nativeValidToDate,
|
||||
verify: nativeVerify,
|
||||
} = NativeX509Certificate.prototype;
|
||||
|
||||
let getState;
|
||||
|
||||
function getCachedValue(cert, index, getter) {
|
||||
const state = getState(cert);
|
||||
const bit = 1 << index;
|
||||
if ((state[kStateCacheMask] & bit) === 0) {
|
||||
state[index] = FunctionPrototypeCall(getter, cert);
|
||||
state[kStateCacheMask] |= bit;
|
||||
}
|
||||
markTransferMode(this, true, false);
|
||||
this[kHandle] = parseX509(buffer);
|
||||
return state[index];
|
||||
}
|
||||
|
||||
[kInspect](depth, options) {
|
||||
if (depth < 0)
|
||||
return this;
|
||||
|
||||
const opts = {
|
||||
...options,
|
||||
depth: options.depth == null ? null : options.depth - 1,
|
||||
};
|
||||
|
||||
return `X509Certificate ${inspect({
|
||||
subject: this.subject,
|
||||
subjectAltName: this.subjectAltName,
|
||||
issuer: this.issuer,
|
||||
infoAccess: this.infoAccess,
|
||||
validFrom: this.validFrom,
|
||||
validTo: this.validTo,
|
||||
validFromDate: this.validFromDate,
|
||||
validToDate: this.validToDate,
|
||||
fingerprint: this.fingerprint,
|
||||
fingerprint256: this.fingerprint256,
|
||||
fingerprint512: this.fingerprint512,
|
||||
keyUsage: this.keyUsage,
|
||||
serialNumber: this.serialNumber,
|
||||
signatureAlgorithm: this.signatureAlgorithm,
|
||||
signatureAlgorithmOid: this.signatureAlgorithmOid,
|
||||
}, opts)}`;
|
||||
}
|
||||
|
||||
[kClone]() {
|
||||
const handle = this[kHandle];
|
||||
return {
|
||||
data: { handle },
|
||||
deserializeInfo: 'internal/crypto/x509:InternalX509Certificate',
|
||||
};
|
||||
}
|
||||
|
||||
[kDeserialize]({ handle }) {
|
||||
this[kHandle] = handle;
|
||||
}
|
||||
|
||||
get subject() {
|
||||
let value = this[kInternalState].get('subject');
|
||||
if (value === undefined) {
|
||||
value = this[kHandle].subject();
|
||||
this[kInternalState].set('subject', value);
|
||||
class X509Certificate {
|
||||
constructor(buffer) {
|
||||
if (typeof buffer === 'string')
|
||||
buffer = Buffer.from(buffer);
|
||||
if (!isArrayBufferView(buffer)) {
|
||||
throw new ERR_INVALID_ARG_TYPE(
|
||||
'buffer',
|
||||
['string', 'Buffer', 'TypedArray', 'DataView'],
|
||||
buffer);
|
||||
}
|
||||
const prototype = ObjectGetPrototypeOf(this);
|
||||
const certificate = new InternalX509Certificate(buffer);
|
||||
ObjectSetPrototypeOf(certificate, prototype);
|
||||
// eslint-disable-next-line no-constructor-return
|
||||
return certificate;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get subjectAltName() {
|
||||
let value = this[kInternalState].get('subjectAltName');
|
||||
if (value === undefined) {
|
||||
value = this[kHandle].subjectAltName();
|
||||
this[kInternalState].set('subjectAltName', value);
|
||||
[kInspect](depth, options) {
|
||||
if (depth < 0)
|
||||
return this;
|
||||
|
||||
const opts = {
|
||||
...options,
|
||||
depth: options.depth == null ? null : options.depth - 1,
|
||||
};
|
||||
|
||||
return `X509Certificate ${inspect({
|
||||
subject: getCachedValue(this, kSlotSubject, nativeSubject),
|
||||
subjectAltName:
|
||||
getCachedValue(this, kSlotSubjectAltName, nativeSubjectAltName),
|
||||
issuer: getCachedValue(this, kSlotIssuer, nativeIssuer),
|
||||
infoAccess: getCachedValue(this, kSlotInfoAccess, nativeInfoAccess),
|
||||
validFrom: getCachedValue(this, kSlotValidFrom, nativeValidFrom),
|
||||
validTo: getCachedValue(this, kSlotValidTo, nativeValidTo),
|
||||
validFromDate:
|
||||
getCachedValue(this, kSlotValidFromDate, nativeValidFromDate),
|
||||
validToDate:
|
||||
getCachedValue(this, kSlotValidToDate, nativeValidToDate),
|
||||
fingerprint:
|
||||
getCachedValue(this, kSlotFingerprint, nativeFingerprint),
|
||||
fingerprint256:
|
||||
getCachedValue(this, kSlotFingerprint256, nativeFingerprint256),
|
||||
fingerprint512:
|
||||
getCachedValue(this, kSlotFingerprint512, nativeFingerprint512),
|
||||
keyUsage: getCachedValue(this, kSlotKeyUsage, nativeKeyUsage),
|
||||
serialNumber:
|
||||
getCachedValue(this, kSlotSerialNumber, nativeSerialNumber),
|
||||
signatureAlgorithm:
|
||||
getCachedValue(
|
||||
this, kSlotSignatureAlgorithm, nativeSignatureAlgorithm),
|
||||
signatureAlgorithmOid:
|
||||
getCachedValue(
|
||||
this, kSlotSignatureAlgorithmOid, nativeSignatureAlgorithmOid),
|
||||
}, opts)}`;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get issuer() {
|
||||
let value = this[kInternalState].get('issuer');
|
||||
if (value === undefined) {
|
||||
value = this[kHandle].issuer();
|
||||
this[kInternalState].set('issuer', value);
|
||||
get subject() {
|
||||
return getCachedValue(this, kSlotSubject, nativeSubject);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get issuerCertificate() {
|
||||
let value = this[kInternalState].get('issuerCertificate');
|
||||
if (value === undefined) {
|
||||
const cert = this[kHandle].getIssuerCert();
|
||||
if (cert)
|
||||
value = new InternalX509Certificate(cert);
|
||||
this[kInternalState].set('issuerCertificate', value);
|
||||
get subjectAltName() {
|
||||
return getCachedValue(
|
||||
this, kSlotSubjectAltName, nativeSubjectAltName);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get infoAccess() {
|
||||
let value = this[kInternalState].get('infoAccess');
|
||||
if (value === undefined) {
|
||||
value = this[kHandle].infoAccess();
|
||||
this[kInternalState].set('infoAccess', value);
|
||||
get issuer() {
|
||||
return getCachedValue(this, kSlotIssuer, nativeIssuer);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get validFrom() {
|
||||
let value = this[kInternalState].get('validFrom');
|
||||
if (value === undefined) {
|
||||
value = this[kHandle].validFrom();
|
||||
this[kInternalState].set('validFrom', value);
|
||||
get issuerCertificate() {
|
||||
const state = getState(this);
|
||||
const bit = 1 << kSlotIssuerCertificate;
|
||||
if ((state[kStateCacheMask] & bit) === 0) {
|
||||
const cert = FunctionPrototypeCall(nativeGetIssuerCert, this);
|
||||
state[kSlotIssuerCertificate] = cert ?
|
||||
new InternalX509Certificate(cert) : undefined;
|
||||
state[kStateCacheMask] |= bit;
|
||||
}
|
||||
return state[kSlotIssuerCertificate];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get validTo() {
|
||||
let value = this[kInternalState].get('validTo');
|
||||
if (value === undefined) {
|
||||
value = this[kHandle].validTo();
|
||||
this[kInternalState].set('validTo', value);
|
||||
get infoAccess() {
|
||||
return getCachedValue(this, kSlotInfoAccess, nativeInfoAccess);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get validFromDate() {
|
||||
let value = this[kInternalState].get('validFromDate');
|
||||
if (value === undefined) {
|
||||
value = this[kHandle].validFromDate();
|
||||
this[kInternalState].set('validFromDate', value);
|
||||
get validFrom() {
|
||||
return getCachedValue(this, kSlotValidFrom, nativeValidFrom);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get validToDate() {
|
||||
let value = this[kInternalState].get('validToDate');
|
||||
if (value === undefined) {
|
||||
value = this[kHandle].validToDate();
|
||||
this[kInternalState].set('validToDate', value);
|
||||
get validTo() {
|
||||
return getCachedValue(this, kSlotValidTo, nativeValidTo);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get fingerprint() {
|
||||
let value = this[kInternalState].get('fingerprint');
|
||||
if (value === undefined) {
|
||||
value = this[kHandle].fingerprint();
|
||||
this[kInternalState].set('fingerprint', value);
|
||||
get validFromDate() {
|
||||
return getCachedValue(this, kSlotValidFromDate, nativeValidFromDate);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get fingerprint256() {
|
||||
let value = this[kInternalState].get('fingerprint256');
|
||||
if (value === undefined) {
|
||||
value = this[kHandle].fingerprint256();
|
||||
this[kInternalState].set('fingerprint256', value);
|
||||
get validToDate() {
|
||||
return getCachedValue(this, kSlotValidToDate, nativeValidToDate);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get fingerprint512() {
|
||||
let value = this[kInternalState].get('fingerprint512');
|
||||
if (value === undefined) {
|
||||
value = this[kHandle].fingerprint512();
|
||||
this[kInternalState].set('fingerprint512', value);
|
||||
get fingerprint() {
|
||||
return getCachedValue(this, kSlotFingerprint, nativeFingerprint);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get keyUsage() {
|
||||
let value = this[kInternalState].get('keyUsage');
|
||||
if (value === undefined) {
|
||||
value = this[kHandle].keyUsage();
|
||||
this[kInternalState].set('keyUsage', value);
|
||||
get fingerprint256() {
|
||||
return getCachedValue(this, kSlotFingerprint256, nativeFingerprint256);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get serialNumber() {
|
||||
let value = this[kInternalState].get('serialNumber');
|
||||
if (value === undefined) {
|
||||
value = this[kHandle].serialNumber();
|
||||
this[kInternalState].set('serialNumber', value);
|
||||
get fingerprint512() {
|
||||
return getCachedValue(this, kSlotFingerprint512, nativeFingerprint512);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get signatureAlgorithm() {
|
||||
let value = this[kInternalState].get('signatureAlgorithm');
|
||||
if (value === undefined) {
|
||||
value = this[kHandle].signatureAlgorithm();
|
||||
this[kInternalState].set('signatureAlgorithm', value);
|
||||
get keyUsage() {
|
||||
return getCachedValue(this, kSlotKeyUsage, nativeKeyUsage);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get signatureAlgorithmOid() {
|
||||
let value = this[kInternalState].get('signatureAlgorithmOid');
|
||||
if (value === undefined) {
|
||||
value = this[kHandle].signatureAlgorithmOid();
|
||||
this[kInternalState].set('signatureAlgorithmOid', value);
|
||||
get serialNumber() {
|
||||
return getCachedValue(this, kSlotSerialNumber, nativeSerialNumber);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get raw() {
|
||||
let value = this[kInternalState].get('raw');
|
||||
if (value === undefined) {
|
||||
value = this[kHandle].raw();
|
||||
this[kInternalState].set('raw', value);
|
||||
get signatureAlgorithm() {
|
||||
return getCachedValue(
|
||||
this, kSlotSignatureAlgorithm, nativeSignatureAlgorithm);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
get publicKey() {
|
||||
let value = this[kInternalState].get('publicKey');
|
||||
if (value === undefined) {
|
||||
value = new PublicKeyObject(this[kHandle].publicKey());
|
||||
this[kInternalState].set('publicKey', value);
|
||||
get signatureAlgorithmOid() {
|
||||
return getCachedValue(
|
||||
this, kSlotSignatureAlgorithmOid, nativeSignatureAlgorithmOid);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
toString() {
|
||||
let value = this[kInternalState].get('pem');
|
||||
if (value === undefined) {
|
||||
value = this[kHandle].pem();
|
||||
this[kInternalState].set('pem', value);
|
||||
get raw() {
|
||||
return getCachedValue(this, kSlotRaw, nativeRaw);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// There's no standardized JSON encoding for X509 certs so we
|
||||
// fallback to providing the PEM encoding as a string.
|
||||
toJSON() { return this.toString(); }
|
||||
|
||||
get ca() {
|
||||
let value = this[kInternalState].get('ca');
|
||||
if (value === undefined) {
|
||||
value = this[kHandle].checkCA();
|
||||
this[kInternalState].set('ca', value);
|
||||
get publicKey() {
|
||||
const state = getState(this);
|
||||
const bit = 1 << kSlotPublicKey;
|
||||
if ((state[kStateCacheMask] & bit) === 0) {
|
||||
state[kSlotPublicKey] = new PublicKeyObject(
|
||||
FunctionPrototypeCall(nativePublicKey, this));
|
||||
state[kStateCacheMask] |= bit;
|
||||
}
|
||||
return state[kSlotPublicKey];
|
||||
}
|
||||
|
||||
toString() {
|
||||
return getCachedValue(this, kSlotPem, nativePem);
|
||||
}
|
||||
|
||||
// There's no standardized JSON encoding for X509 certs so we
|
||||
// fallback to providing the PEM encoding as a string.
|
||||
toJSON() { return getCachedValue(this, kSlotPem, nativePem); }
|
||||
|
||||
get ca() {
|
||||
return getCachedValue(this, kSlotCA, nativeCheckCA);
|
||||
}
|
||||
|
||||
checkHost(name, options) {
|
||||
getState(this);
|
||||
validateString(name, 'name');
|
||||
return FunctionPrototypeCall(
|
||||
nativeCheckHost, this, name, getFlags(options));
|
||||
}
|
||||
|
||||
checkEmail(email, options) {
|
||||
getState(this);
|
||||
validateString(email, 'email');
|
||||
return FunctionPrototypeCall(
|
||||
nativeCheckEmail, this, email, getFlags(options));
|
||||
}
|
||||
|
||||
checkIP(ip, options) {
|
||||
getState(this);
|
||||
validateString(ip, 'ip');
|
||||
// The options argument is currently undocumented since none of the
|
||||
// options have any effect on the behavior of this function. However, we
|
||||
// still parse the options argument in case OpenSSL adds flags in the
|
||||
// future that do affect the behavior of X509_check_ip. This ensures that
|
||||
// no invalid values are passed as the second argument in the meantime.
|
||||
return FunctionPrototypeCall(
|
||||
nativeCheckIP, this, ip, getFlags(options));
|
||||
}
|
||||
|
||||
checkIssued(otherCert) {
|
||||
getState(this);
|
||||
if (!isX509Certificate(otherCert)) {
|
||||
throw new ERR_INVALID_ARG_TYPE(
|
||||
'otherCert', 'X509Certificate', otherCert);
|
||||
}
|
||||
return FunctionPrototypeCall(nativeCheckIssued, this, otherCert);
|
||||
}
|
||||
|
||||
checkPrivateKey(pkey) {
|
||||
getState(this);
|
||||
if (!isKeyObject(pkey))
|
||||
throw new ERR_INVALID_ARG_TYPE('pkey', 'KeyObject', pkey);
|
||||
if (getKeyObjectType(pkey) !== 'private')
|
||||
throw new ERR_INVALID_ARG_VALUE('pkey', pkey);
|
||||
return FunctionPrototypeCall(
|
||||
nativeCheckPrivateKey, this, getKeyObjectHandle(pkey));
|
||||
}
|
||||
|
||||
verify(pkey) {
|
||||
getState(this);
|
||||
if (!isKeyObject(pkey))
|
||||
throw new ERR_INVALID_ARG_TYPE('pkey', 'KeyObject', pkey);
|
||||
if (getKeyObjectType(pkey) !== 'public')
|
||||
throw new ERR_INVALID_ARG_VALUE('pkey', pkey);
|
||||
return FunctionPrototypeCall(
|
||||
nativeVerify, this, getKeyObjectHandle(pkey));
|
||||
}
|
||||
|
||||
toLegacyObject() {
|
||||
getState(this);
|
||||
// TODO(tniessen): do not depend on translatePeerCertificate here, return
|
||||
// the correct legacy representation from the binding
|
||||
lazyTranslatePeerCertificate ??=
|
||||
require('internal/tls/common').translatePeerCertificate;
|
||||
return lazyTranslatePeerCertificate(
|
||||
FunctionPrototypeCall(nativeToLegacy, this));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
checkHost(name, options) {
|
||||
validateString(name, 'name');
|
||||
return this[kHandle].checkHost(name, getFlags(options));
|
||||
}
|
||||
InternalX509Certificate = class InternalX509Certificate
|
||||
extends NativeX509Certificate {
|
||||
#state = createState();
|
||||
|
||||
checkEmail(email, options) {
|
||||
validateString(email, 'email');
|
||||
return this[kHandle].checkEmail(email, getFlags(options));
|
||||
}
|
||||
static {
|
||||
isX509Certificate = (value) => {
|
||||
if (value == null || typeof value !== 'object') return false;
|
||||
return #state in value || isNativeX509Certificate(value);
|
||||
};
|
||||
|
||||
checkIP(ip, options) {
|
||||
validateString(ip, 'ip');
|
||||
// The options argument is currently undocumented since none of the options
|
||||
// have any effect on the behavior of this function. However, we still parse
|
||||
// the options argument in case OpenSSL adds flags in the future that do
|
||||
// affect the behavior of X509_check_ip. This ensures that no invalid values
|
||||
// are passed as the second argument in the meantime.
|
||||
return this[kHandle].checkIP(ip, getFlags(options));
|
||||
}
|
||||
getState = (cert) => {
|
||||
try {
|
||||
return cert.#state;
|
||||
} catch {
|
||||
// Continue with the cross-realm native brand check.
|
||||
}
|
||||
if (!isNativeX509Certificate(cert)) {
|
||||
throw new ERR_INVALID_THIS('X509Certificate');
|
||||
}
|
||||
let state = crossRealmState.get(cert);
|
||||
if (state === undefined) {
|
||||
state = createState();
|
||||
crossRealmState.set(cert, state);
|
||||
}
|
||||
return state;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
checkIssued(otherCert) {
|
||||
if (!isX509Certificate(otherCert))
|
||||
throw new ERR_INVALID_ARG_TYPE('otherCert', 'X509Certificate', otherCert);
|
||||
return this[kHandle].checkIssued(otherCert[kHandle]);
|
||||
}
|
||||
InternalX509Certificate.prototype.constructor = X509Certificate;
|
||||
ObjectSetPrototypeOf(
|
||||
InternalX509Certificate.prototype,
|
||||
X509Certificate.prototype);
|
||||
|
||||
checkPrivateKey(pkey) {
|
||||
if (!isKeyObject(pkey))
|
||||
throw new ERR_INVALID_ARG_TYPE('pkey', 'KeyObject', pkey);
|
||||
if (getKeyObjectType(pkey) !== 'private')
|
||||
throw new ERR_INVALID_ARG_VALUE('pkey', pkey);
|
||||
return this[kHandle].checkPrivateKey(getKeyObjectHandle(pkey));
|
||||
}
|
||||
return [X509Certificate, InternalX509Certificate];
|
||||
});
|
||||
|
||||
verify(pkey) {
|
||||
if (!isKeyObject(pkey))
|
||||
throw new ERR_INVALID_ARG_TYPE('pkey', 'KeyObject', pkey);
|
||||
if (getKeyObjectType(pkey) !== 'public')
|
||||
throw new ERR_INVALID_ARG_VALUE('pkey', pkey);
|
||||
return this[kHandle].verify(getKeyObjectHandle(pkey));
|
||||
}
|
||||
|
||||
toLegacyObject() {
|
||||
// TODO(tniessen): do not depend on translatePeerCertificate here, return
|
||||
// the correct legacy representation from the binding
|
||||
lazyTranslatePeerCertificate ??=
|
||||
require('internal/tls/common').translatePeerCertificate;
|
||||
return lazyTranslatePeerCertificate(this[kHandle].toLegacy());
|
||||
}
|
||||
}
|
||||
|
||||
InternalX509Certificate.prototype.constructor = X509Certificate;
|
||||
ObjectSetPrototypeOf(
|
||||
InternalX509Certificate.prototype,
|
||||
X509Certificate.prototype);
|
||||
// Keep the binding-returned constructor and the closure reference aligned.
|
||||
InternalX509Certificate = InternalX509CertificateConstructor;
|
||||
|
||||
module.exports = {
|
||||
X509Certificate,
|
||||
|
||||
@@ -50,6 +50,7 @@ following table:
|
||||
| `crypto_spkac` | Netscape SPKAC certificate utilities. |
|
||||
| `crypto_ssl` | Implementation of the `SSLWrap` object. |
|
||||
| `crypto_timing` | Implementation of the TimingSafeEqual. |
|
||||
| `crypto_x509` | X.509 certificate parsing and validation. |
|
||||
|
||||
When new crypto protocols are added, they will be added into their own
|
||||
`crypto_` `*.h` and `*.cc` files.
|
||||
@@ -189,6 +190,14 @@ primes a private JavaScript slot cache from the constructor arguments.
|
||||
Partially initialized transferred keys populate that cache from the native
|
||||
slots on first access.
|
||||
|
||||
### X.509 certificates
|
||||
|
||||
The public `X509Certificate` is backed directly by the native
|
||||
`X509Certificate` object. JavaScript caches derived certificate properties in
|
||||
a private array whose final entry is a bitmask of populated slots. Certificates
|
||||
from another realm use the same cache layout through a private `WeakMap` after
|
||||
passing the native brand check.
|
||||
|
||||
### `CryptoJob`
|
||||
|
||||
All operations that are not either Stream-based or single-use functions
|
||||
|
||||
+103
-10
@@ -807,7 +807,7 @@ Local<FunctionTemplate> X509Certificate::GetConstructorTemplate(
|
||||
Local<FunctionTemplate> tmpl = env->x509_constructor_template();
|
||||
if (tmpl.IsEmpty()) {
|
||||
Isolate* isolate = env->isolate();
|
||||
tmpl = NewFunctionTemplate(isolate, nullptr);
|
||||
tmpl = NewFunctionTemplate(isolate, NewFromHandle);
|
||||
tmpl->InstanceTemplate()->SetInternalFieldCount(
|
||||
X509Certificate::kInternalFieldCount);
|
||||
tmpl->SetClassName(
|
||||
@@ -850,8 +850,72 @@ Local<FunctionTemplate> X509Certificate::GetConstructorTemplate(
|
||||
return tmpl;
|
||||
}
|
||||
|
||||
bool X509Certificate::HasInstance(Environment* env, Local<Object> object) {
|
||||
return GetConstructorTemplate(env)->HasInstance(object);
|
||||
bool X509Certificate::HasInstance(Environment* env, Local<Value> value) {
|
||||
return GetConstructorTemplate(env)->HasInstance(value);
|
||||
}
|
||||
|
||||
void X509Certificate::NewFromHandle(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
if (args.Length() == 1 && args[0]->IsArrayBufferView()) {
|
||||
ArrayBufferViewContents<unsigned char> buf(args[0].As<ArrayBufferView>());
|
||||
auto result = X509Pointer::Parse(ncrypto::Buffer<const unsigned char>{
|
||||
.data = buf.data(),
|
||||
.len = buf.length(),
|
||||
});
|
||||
if (!result.value) [[unlikely]] {
|
||||
return ThrowCryptoError(env, result.error.value_or(0));
|
||||
}
|
||||
new X509Certificate(env,
|
||||
args.This(),
|
||||
std::make_shared<ManagedX509>(std::move(result.value)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length() != 1 || !HasInstance(env, args[0])) {
|
||||
THROW_ERR_INVALID_ARG_TYPE(
|
||||
env, "value must be an X509CertificateHandle or BufferSource");
|
||||
return;
|
||||
}
|
||||
|
||||
X509Certificate* handle = Unwrap<X509Certificate>(args[0].As<Object>());
|
||||
CHECK_NOT_NULL(handle);
|
||||
Local<Object> issuer;
|
||||
if (handle->issuer_cert_) issuer = handle->issuer_cert_->object();
|
||||
new X509Certificate(env, args.This(), handle->cert_, issuer);
|
||||
}
|
||||
|
||||
void X509Certificate::CreateX509CertificateClass(
|
||||
const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
CHECK_EQ(args.Length(), 1);
|
||||
CHECK(args[0]->IsFunction());
|
||||
|
||||
Local<Value> ctor;
|
||||
if (!GetConstructorTemplate(env)->GetFunction(env->context()).ToLocal(&ctor))
|
||||
return;
|
||||
|
||||
Local<Value> ret;
|
||||
if (!args[0]
|
||||
.As<Function>()
|
||||
->Call(env->context(), Undefined(env->isolate()), 1, &ctor)
|
||||
.ToLocal(&ret)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Local<Array> constructors = ret.As<Array>();
|
||||
Local<Value> internal_ctor;
|
||||
if (!constructors->Get(env->context(), 1).ToLocal(&internal_ctor)) return;
|
||||
CHECK(env->crypto_internal_x509_certificate_constructor().IsEmpty());
|
||||
env->set_crypto_internal_x509_certificate_constructor(
|
||||
internal_ctor.As<Function>());
|
||||
args.GetReturnValue().Set(constructors);
|
||||
}
|
||||
|
||||
void X509Certificate::IsX509Certificate(
|
||||
const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
CHECK_EQ(args.Length(), 1);
|
||||
args.GetReturnValue().Set(HasInstance(env, args[0]));
|
||||
}
|
||||
|
||||
MaybeLocal<Object> X509Certificate::New(Environment* env,
|
||||
@@ -865,13 +929,13 @@ MaybeLocal<Object> X509Certificate::New(Environment* env,
|
||||
std::shared_ptr<ManagedX509> cert,
|
||||
STACK_OF(X509) * issuer_chain) {
|
||||
EscapableHandleScope scope(env->isolate());
|
||||
Local<Function> ctor;
|
||||
if (!GetConstructorTemplate(env)->GetFunction(env->context()).ToLocal(&ctor))
|
||||
return MaybeLocal<Object>();
|
||||
|
||||
Local<Object> obj;
|
||||
if (!ctor->NewInstance(env->context()).ToLocal(&obj))
|
||||
if (!GetConstructorTemplate(env)
|
||||
->InstanceTemplate()
|
||||
->NewInstance(env->context())
|
||||
.ToLocal(&obj)) {
|
||||
return MaybeLocal<Object>();
|
||||
}
|
||||
|
||||
Local<Object> issuer_chain_obj;
|
||||
if (issuer_chain != nullptr && sk_X509_num(issuer_chain)) {
|
||||
@@ -963,11 +1027,31 @@ X509Certificate::X509CertificateTransferData::Deserialize(
|
||||
if (!X509Certificate::New(env, data_).ToLocal(&handle))
|
||||
return {};
|
||||
|
||||
return BaseObjectPtr<BaseObject>(
|
||||
Unwrap<X509Certificate>(handle.As<Object>()));
|
||||
Local<Value> module =
|
||||
FIXED_ONE_BYTE_STRING(env->isolate(), "internal/crypto/x509");
|
||||
if (env->builtin_module_require()
|
||||
->Call(context, Null(env->isolate()), 1, &module)
|
||||
.IsEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Local<Function> ctor = env->crypto_internal_x509_certificate_constructor();
|
||||
CHECK(!ctor.IsEmpty());
|
||||
Local<Value> cert;
|
||||
if (!ctor->NewInstance(context, 1, &handle).ToLocal(&cert)) return {};
|
||||
|
||||
return BaseObjectPtr<BaseObject>(Unwrap<X509Certificate>(cert.As<Object>()));
|
||||
}
|
||||
|
||||
BaseObject::TransferMode X509Certificate::GetTransferMode() const {
|
||||
Local<Value> transfer_mode =
|
||||
object()
|
||||
->GetPrivate(env()->context(), env()->transfer_mode_private_symbol())
|
||||
.ToLocalChecked();
|
||||
if (transfer_mode->IsUint32() &&
|
||||
(transfer_mode.As<Uint32>()->Value() & TransferMode::kCloneable) == 0) {
|
||||
return BaseObject::TransferMode::kDisallowCloneAndTransfer;
|
||||
}
|
||||
return BaseObject::TransferMode::kCloneable;
|
||||
}
|
||||
|
||||
@@ -977,6 +1061,12 @@ std::unique_ptr<worker::TransferData> X509Certificate::CloneForMessaging()
|
||||
}
|
||||
|
||||
void X509Certificate::Initialize(Environment* env, Local<Object> target) {
|
||||
SetMethod(env->context(),
|
||||
target,
|
||||
"createX509CertificateClass",
|
||||
CreateX509CertificateClass);
|
||||
SetMethodNoSideEffect(
|
||||
env->context(), target, "isX509Certificate", IsX509Certificate);
|
||||
SetMethod(env->context(), target, "parseX509", Parse);
|
||||
|
||||
NODE_DEFINE_CONSTANT(target, X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT);
|
||||
@@ -989,6 +1079,9 @@ void X509Certificate::Initialize(Environment* env, Local<Object> target) {
|
||||
|
||||
void X509Certificate::RegisterExternalReferences(
|
||||
ExternalReferenceRegistry* registry) {
|
||||
registry->Register(CreateX509CertificateClass);
|
||||
registry->Register(IsX509Certificate);
|
||||
registry->Register(NewFromHandle);
|
||||
registry->Register(Parse);
|
||||
registry->Register(Subject);
|
||||
registry->Register(SubjectAltName);
|
||||
|
||||
@@ -49,7 +49,13 @@ class X509Certificate final : public BaseObject {
|
||||
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
|
||||
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
|
||||
Environment* env);
|
||||
static bool HasInstance(Environment* env, v8::Local<v8::Object> object);
|
||||
static bool HasInstance(Environment* env, v8::Local<v8::Value> value);
|
||||
|
||||
static void CreateX509CertificateClass(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void IsX509Certificate(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void NewFromHandle(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
static v8::MaybeLocal<v8::Object> New(
|
||||
Environment* env,
|
||||
|
||||
@@ -507,6 +507,7 @@
|
||||
V(async_hooks_promise_resolve_function, v8::Function) \
|
||||
V(buffer_prototype_object, v8::Object) \
|
||||
V(crypto_internal_cryptokey_constructor, v8::Function) \
|
||||
V(crypto_internal_x509_certificate_constructor, v8::Function) \
|
||||
V(crypto_key_object_private_constructor, v8::Function) \
|
||||
V(crypto_key_object_public_constructor, v8::Function) \
|
||||
V(crypto_key_object_secret_constructor, v8::Function) \
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
// Flags: --expose-internals
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const assert = require('node:assert');
|
||||
const { X509Certificate } = require('node:crypto');
|
||||
const { readFileSync } = require('node:fs');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const {
|
||||
isX509Certificate,
|
||||
} = require('internal/crypto/x509');
|
||||
|
||||
const certData = readFileSync(fixtures.path('keys', 'agent1-cert.pem'));
|
||||
const cert = new X509Certificate(certData);
|
||||
const invalidThis = { code: 'ERR_INVALID_THIS', name: 'TypeError' };
|
||||
|
||||
const getterNames = [
|
||||
'subject',
|
||||
'subjectAltName',
|
||||
'issuer',
|
||||
'issuerCertificate',
|
||||
'infoAccess',
|
||||
'validFrom',
|
||||
'validTo',
|
||||
'validFromDate',
|
||||
'validToDate',
|
||||
'fingerprint',
|
||||
'fingerprint256',
|
||||
'fingerprint512',
|
||||
'keyUsage',
|
||||
'serialNumber',
|
||||
'signatureAlgorithm',
|
||||
'signatureAlgorithmOid',
|
||||
'raw',
|
||||
'publicKey',
|
||||
'ca',
|
||||
];
|
||||
|
||||
const methodNames = [
|
||||
'toString',
|
||||
'toJSON',
|
||||
'checkHost',
|
||||
'checkEmail',
|
||||
'checkIP',
|
||||
'checkIssued',
|
||||
'checkPrivateKey',
|
||||
'verify',
|
||||
'toLegacyObject',
|
||||
];
|
||||
|
||||
assert.strictEqual(isX509Certificate(cert), true);
|
||||
assert.strictEqual(cert instanceof X509Certificate, true);
|
||||
assert.strictEqual(Object.getPrototypeOf(X509Certificate.prototype),
|
||||
Object.prototype);
|
||||
for (const name of ['pem', 'checkCA', 'getIssuerCert', 'toLegacy']) {
|
||||
assert.strictEqual(name in cert, false);
|
||||
}
|
||||
|
||||
for (const value of [
|
||||
{},
|
||||
{ __proto__: null },
|
||||
1,
|
||||
null,
|
||||
undefined,
|
||||
Buffer.alloc(1),
|
||||
function() {},
|
||||
]) {
|
||||
assert.strictEqual(isX509Certificate(value), false);
|
||||
for (const name of getterNames) {
|
||||
const getter = Object.getOwnPropertyDescriptor(
|
||||
X509Certificate.prototype, name).get;
|
||||
assert.throws(() => getter.call(value), invalidThis);
|
||||
}
|
||||
for (const name of methodNames) {
|
||||
assert.throws(
|
||||
() => X509Certificate.prototype[name].call(value),
|
||||
invalidThis);
|
||||
}
|
||||
}
|
||||
|
||||
const spoofed = { __proto__: X509Certificate.prototype };
|
||||
assert.strictEqual(spoofed instanceof X509Certificate, true);
|
||||
assert.strictEqual(isX509Certificate(spoofed), false);
|
||||
assert.throws(() => spoofed.subject, invalidThis);
|
||||
assert.throws(() => spoofed.toString(), invalidThis);
|
||||
|
||||
const originalHasInstance =
|
||||
Object.getOwnPropertyDescriptor(X509Certificate, Symbol.hasInstance);
|
||||
Object.defineProperty(X509Certificate, Symbol.hasInstance, {
|
||||
configurable: true,
|
||||
value: () => true,
|
||||
});
|
||||
try {
|
||||
const buffer = Buffer.alloc(1);
|
||||
assert.strictEqual(buffer instanceof X509Certificate, true);
|
||||
assert.strictEqual(isX509Certificate(buffer), false);
|
||||
assert.throws(() => X509Certificate.prototype.toString.call(buffer),
|
||||
invalidThis);
|
||||
} finally {
|
||||
if (originalHasInstance === undefined) {
|
||||
delete X509Certificate[Symbol.hasInstance];
|
||||
} else {
|
||||
Object.defineProperty(
|
||||
X509Certificate, Symbol.hasInstance, originalHasInstance);
|
||||
}
|
||||
}
|
||||
|
||||
class DerivedX509Certificate extends X509Certificate {}
|
||||
const derived = new DerivedX509Certificate(certData);
|
||||
assert.strictEqual(derived instanceof DerivedX509Certificate, true);
|
||||
assert.strictEqual(derived instanceof X509Certificate, true);
|
||||
assert.strictEqual(isX509Certificate(derived), true);
|
||||
assert.strictEqual(derived.subject, cert.subject);
|
||||
|
||||
function PrimitivePrototype() {}
|
||||
PrimitivePrototype.prototype = 1;
|
||||
const primitivePrototype = Reflect.construct(
|
||||
X509Certificate, [certData], PrimitivePrototype);
|
||||
assert.strictEqual(Object.getPrototypeOf(primitivePrototype), Object.prototype);
|
||||
assert.strictEqual(isX509Certificate(primitivePrototype), true);
|
||||
assert.strictEqual(
|
||||
Object.getOwnPropertyDescriptor(
|
||||
X509Certificate.prototype, 'subject').get.call(primitivePrototype),
|
||||
cert.subject);
|
||||
|
||||
const firstPrototype = { __proto__: X509Certificate.prototype };
|
||||
const secondPrototype = { __proto__: X509Certificate.prototype };
|
||||
let prototypeReads = 0;
|
||||
const ProxyNewTarget = new Proxy(function() {}, {
|
||||
get(target, property, receiver) {
|
||||
if (property === 'prototype') {
|
||||
prototypeReads++;
|
||||
return prototypeReads === 1 ? firstPrototype : secondPrototype;
|
||||
}
|
||||
return Reflect.get(target, property, receiver);
|
||||
},
|
||||
});
|
||||
const proxyNewTarget = Reflect.construct(
|
||||
X509Certificate, [certData], ProxyNewTarget);
|
||||
assert.strictEqual(prototypeReads, 1);
|
||||
assert.strictEqual(Object.getPrototypeOf(proxyNewTarget), firstPrototype);
|
||||
assert.strictEqual(proxyNewTarget.subject, cert.subject);
|
||||
@@ -0,0 +1,119 @@
|
||||
// Flags: --expose-internals
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const assert = require('node:assert');
|
||||
const { once } = require('node:events');
|
||||
const {
|
||||
X509Certificate,
|
||||
createPrivateKey,
|
||||
} = require('node:crypto');
|
||||
const { readFileSync } = require('node:fs');
|
||||
const {
|
||||
markAsUncloneable,
|
||||
MessageChannel,
|
||||
Worker,
|
||||
} = require('node:worker_threads');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const {
|
||||
isX509Certificate,
|
||||
} = require('internal/crypto/x509');
|
||||
|
||||
const certData = readFileSync(fixtures.path('keys', 'agent1-cert.pem'));
|
||||
const keyData = readFileSync(fixtures.path('keys', 'agent1-key.pem'));
|
||||
const caData = readFileSync(fixtures.path('keys', 'ca1-cert.pem'));
|
||||
|
||||
const cert = new X509Certificate(certData);
|
||||
const ca = new X509Certificate(caData);
|
||||
const privateKey = createPrivateKey(keyData);
|
||||
|
||||
const dataCloneError = {
|
||||
code: 25,
|
||||
name: 'DataCloneError',
|
||||
};
|
||||
|
||||
{
|
||||
const uncloneable = new X509Certificate(certData);
|
||||
markAsUncloneable(uncloneable);
|
||||
assert.throws(() => structuredClone(uncloneable), dataCloneError);
|
||||
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
assert.throws(() => port1.postMessage(uncloneable), dataCloneError);
|
||||
port1.close();
|
||||
port2.close();
|
||||
}
|
||||
|
||||
function assertSameCertificate(original, clone) {
|
||||
assert.notStrictEqual(clone, original);
|
||||
assert.strictEqual(clone instanceof X509Certificate, true);
|
||||
assert.strictEqual(isX509Certificate(clone), true);
|
||||
assert.strictEqual(clone.subject, original.subject);
|
||||
assert.strictEqual(clone.issuer, original.issuer);
|
||||
assert.strictEqual(clone.fingerprint256, original.fingerprint256);
|
||||
assert.deepStrictEqual(clone.raw, original.raw);
|
||||
assert.deepStrictEqual(Reflect.ownKeys(clone), []);
|
||||
assert.strictEqual(clone.checkPrivateKey(privateKey), true);
|
||||
assert.strictEqual(clone.checkIssued(ca), true);
|
||||
assert.strictEqual(clone.verify(ca.publicKey), true);
|
||||
}
|
||||
|
||||
async function roundTripViaMessageChannel(value) {
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
port1.postMessage(value);
|
||||
const [received] = await once(port2, 'message');
|
||||
port1.close();
|
||||
port2.close();
|
||||
return received;
|
||||
}
|
||||
|
||||
function workerMain() {
|
||||
const {
|
||||
X509Certificate: WorkerX509Certificate,
|
||||
} = require('node:crypto');
|
||||
const { parentPort } = require('node:worker_threads');
|
||||
|
||||
parentPort.once('message', (cert) => {
|
||||
try {
|
||||
if (!(cert instanceof WorkerX509Certificate)) {
|
||||
throw new Error('X509Certificate brand was not preserved');
|
||||
}
|
||||
parentPort.postMessage({
|
||||
cert,
|
||||
ownKeyCount: Reflect.ownKeys(cert).length,
|
||||
subject: cert.subject,
|
||||
});
|
||||
} catch (error) {
|
||||
parentPort.postMessage({ error: error.stack || error.message });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function roundTripViaWorker(value) {
|
||||
const worker = new Worker(
|
||||
`'use strict';(${workerMain.toString()})()`, { eval: true });
|
||||
|
||||
worker.postMessage(value);
|
||||
const [message] = await once(worker, 'message');
|
||||
await worker.terminate();
|
||||
assert.strictEqual(message.error, undefined, message.error);
|
||||
assert.strictEqual(message.ownKeyCount, 0);
|
||||
assert.strictEqual(message.subject, value.subject);
|
||||
return message.cert;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const cloned = structuredClone(cert);
|
||||
assertSameCertificate(cert, cloned);
|
||||
|
||||
const viaPort = await roundTripViaMessageChannel(cert);
|
||||
assertSameCertificate(cert, viaPort);
|
||||
|
||||
const clonedAgain = structuredClone(viaPort);
|
||||
assertSameCertificate(cert, clonedAgain);
|
||||
|
||||
const viaWorker = await roundTripViaWorker(cert);
|
||||
assertSameCertificate(cert, viaWorker);
|
||||
})().then(common.mustCall());
|
||||
@@ -0,0 +1,117 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const assert = require('node:assert');
|
||||
const {
|
||||
X509Certificate,
|
||||
createPrivateKey,
|
||||
} = require('node:crypto');
|
||||
const { readFileSync } = require('node:fs');
|
||||
const { inspect } = require('node:util');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
const certData = readFileSync(fixtures.path('keys', 'agent1-cert.pem'));
|
||||
const keyData = readFileSync(fixtures.path('keys', 'agent1-key.pem'));
|
||||
const caData = readFileSync(fixtures.path('keys', 'ca1-cert.pem'));
|
||||
|
||||
const cert = new X509Certificate(certData);
|
||||
const ca = new X509Certificate(caData);
|
||||
const privateKey = createPrivateKey(keyData);
|
||||
const expectedPem = cert.toString();
|
||||
const expectedSubject = cert.subject;
|
||||
|
||||
function assertNoOwnKeys(value) {
|
||||
assert.deepStrictEqual(Object.getOwnPropertyNames(value), []);
|
||||
assert.deepStrictEqual(Object.getOwnPropertySymbols(value), []);
|
||||
assert.deepStrictEqual(Reflect.ownKeys(value), []);
|
||||
}
|
||||
|
||||
for (const name of [
|
||||
'subject',
|
||||
'subjectAltName',
|
||||
'issuer',
|
||||
'issuerCertificate',
|
||||
'infoAccess',
|
||||
'validFrom',
|
||||
'validTo',
|
||||
'validFromDate',
|
||||
'validToDate',
|
||||
'fingerprint',
|
||||
'fingerprint256',
|
||||
'fingerprint512',
|
||||
'keyUsage',
|
||||
'serialNumber',
|
||||
'signatureAlgorithm',
|
||||
'signatureAlgorithmOid',
|
||||
'raw',
|
||||
'publicKey',
|
||||
'ca',
|
||||
]) {
|
||||
// Populate every private lazy-cache entry before checking reflection.
|
||||
Reflect.get(cert, name);
|
||||
}
|
||||
cert.toString();
|
||||
cert.toJSON();
|
||||
assertNoOwnKeys(cert);
|
||||
|
||||
const getterNames = [
|
||||
'subject',
|
||||
'subjectAltName',
|
||||
'issuer',
|
||||
'infoAccess',
|
||||
'validFrom',
|
||||
'validTo',
|
||||
'validFromDate',
|
||||
'validToDate',
|
||||
'fingerprint',
|
||||
'fingerprint256',
|
||||
'fingerprint512',
|
||||
'keyUsage',
|
||||
'serialNumber',
|
||||
'signatureAlgorithm',
|
||||
'signatureAlgorithmOid',
|
||||
];
|
||||
const originals = new Map();
|
||||
for (const name of getterNames) {
|
||||
originals.set(name,
|
||||
Object.getOwnPropertyDescriptor(
|
||||
X509Certificate.prototype, name));
|
||||
Object.defineProperty(X509Certificate.prototype, name, {
|
||||
configurable: true,
|
||||
get() { return `FORGED-${name}`; },
|
||||
});
|
||||
}
|
||||
const originalToString =
|
||||
Object.getOwnPropertyDescriptor(X509Certificate.prototype, 'toString');
|
||||
Object.defineProperty(X509Certificate.prototype, 'toString', {
|
||||
configurable: true,
|
||||
value() { return 'FORGED-PEM'; },
|
||||
});
|
||||
|
||||
try {
|
||||
assert.strictEqual(cert.subject, 'FORGED-subject');
|
||||
assert.strictEqual(cert.toString(), 'FORGED-PEM');
|
||||
|
||||
const rendered = inspect(cert, { depth: 4 });
|
||||
assert.match(rendered, /CN=agent1/);
|
||||
assert.doesNotMatch(rendered, /FORGED/);
|
||||
assert.strictEqual(cert.toJSON(), expectedPem);
|
||||
assert.strictEqual(JSON.parse(JSON.stringify(cert)), expectedPem);
|
||||
|
||||
assert.strictEqual(cert.checkIssued(ca), true);
|
||||
assert.strictEqual(cert.checkPrivateKey(privateKey), true);
|
||||
assert.strictEqual(cert.verify(ca.publicKey), true);
|
||||
assertNoOwnKeys(cert);
|
||||
} finally {
|
||||
for (const [name, descriptor] of originals) {
|
||||
Object.defineProperty(X509Certificate.prototype, name, descriptor);
|
||||
}
|
||||
Object.defineProperty(
|
||||
X509Certificate.prototype, 'toString', originalToString);
|
||||
}
|
||||
|
||||
assert.strictEqual(cert.subject, expectedSubject);
|
||||
assert.strictEqual(cert.toString(), expectedPem);
|
||||
@@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
common.skipIfEslintMissing();
|
||||
|
||||
const RuleTester = require('../../tools/eslint/node_modules/eslint').RuleTester;
|
||||
const rule = require('../../tools/eslint-rules/no-crypto-class-instanceof');
|
||||
|
||||
new RuleTester().run('no-crypto-class-instanceof', rule, {
|
||||
valid: [
|
||||
'value instanceof Buffer;',
|
||||
'value instanceof KeyObject;',
|
||||
`
|
||||
const { isKeyObject } = require('internal/crypto/keys');
|
||||
isKeyObject(key);
|
||||
`,
|
||||
`
|
||||
function isCryptoKey(value, CryptoKey) {
|
||||
return value instanceof CryptoKey;
|
||||
}
|
||||
`,
|
||||
`
|
||||
function isCryptoKey(value, globalThis) {
|
||||
return value instanceof globalThis.CryptoKey;
|
||||
}
|
||||
`,
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: `
|
||||
const { KeyObject: KO } = require('internal/crypto/keys');
|
||||
key instanceof KO;
|
||||
`,
|
||||
errors: [{ messageId: 'noKeyObjectInstanceof' }],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
const webcrypto = require('internal/crypto/webcrypto');
|
||||
key instanceof webcrypto.CryptoKey;
|
||||
`,
|
||||
errors: [{ messageId: 'noCryptoKeyInstanceof' }],
|
||||
},
|
||||
{
|
||||
code: 'key instanceof globalThis.CryptoKey;',
|
||||
errors: [{ messageId: 'noCryptoKeyInstanceof' }],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
const { X509Certificate } = require('internal/crypto/x509');
|
||||
cert instanceof X509Certificate;
|
||||
`,
|
||||
errors: [{ messageId: 'noX509CertificateInstanceof' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -1,78 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
common.skipIfEslintMissing();
|
||||
|
||||
const RuleTester = require('../../tools/eslint/node_modules/eslint').RuleTester;
|
||||
const rule = require('../../tools/eslint-rules/no-keyobject-cryptokey-instanceof');
|
||||
|
||||
new RuleTester().run('no-keyobject-cryptokey-instanceof', rule, {
|
||||
valid: [
|
||||
'key instanceof Buffer;',
|
||||
'key instanceof KeyObject;',
|
||||
`
|
||||
const { isKeyObject } = require('internal/crypto/keys');
|
||||
isKeyObject(key);
|
||||
`,
|
||||
`
|
||||
const { isCryptoKey } = require('internal/crypto/keys');
|
||||
isCryptoKey(key);
|
||||
`,
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: `
|
||||
const { KeyObject } = require('internal/crypto/keys');
|
||||
key instanceof KeyObject;
|
||||
`,
|
||||
errors: [{ messageId: 'noKeyObjectInstanceof' }],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
const { KeyObject: KO } = require('internal/crypto/keys');
|
||||
key instanceof KO;
|
||||
`,
|
||||
errors: [{ messageId: 'noKeyObjectInstanceof' }],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
const keys = require('internal/crypto/keys');
|
||||
key instanceof keys.KeyObject;
|
||||
`,
|
||||
errors: [{ messageId: 'noKeyObjectInstanceof' }],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
key instanceof CryptoKey;
|
||||
`,
|
||||
errors: [{ messageId: 'noCryptoKeyInstanceof' }],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
const { CryptoKey } = require('internal/crypto/keys');
|
||||
key instanceof CryptoKey;
|
||||
`,
|
||||
errors: [{ messageId: 'noCryptoKeyInstanceof' }],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
const { CryptoKey: CK } = require('internal/crypto/webcrypto');
|
||||
key instanceof CK;
|
||||
`,
|
||||
errors: [{ messageId: 'noCryptoKeyInstanceof' }],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
const webcrypto = require('internal/crypto/webcrypto');
|
||||
key instanceof webcrypto.CryptoKey;
|
||||
`,
|
||||
errors: [{ messageId: 'noCryptoKeyInstanceof' }],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
key instanceof globalThis.CryptoKey;
|
||||
`,
|
||||
errors: [{ messageId: 'noCryptoKeyInstanceof' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* @file Prevent internal code from brand-checking crypto classes with
|
||||
* instanceof.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { isRequireCall, isString } = require('./rules-utils.js');
|
||||
|
||||
const moduleClasses = new Map([
|
||||
['internal/crypto/keys', new Set([
|
||||
'KeyObject',
|
||||
'SecretKeyObject',
|
||||
'AsymmetricKeyObject',
|
||||
'PublicKeyObject',
|
||||
'PrivateKeyObject',
|
||||
'CryptoKey',
|
||||
'InternalCryptoKey',
|
||||
])],
|
||||
['internal/crypto/webcrypto', new Set(['CryptoKey'])],
|
||||
['internal/crypto/x509', new Set([
|
||||
'X509Certificate',
|
||||
'InternalX509Certificate',
|
||||
])],
|
||||
]);
|
||||
|
||||
const knownClassNames = new Set();
|
||||
for (const classes of moduleClasses.values()) {
|
||||
for (const name of classes) knownClassNames.add(name);
|
||||
}
|
||||
|
||||
function getRequiredModule(node) {
|
||||
if (node?.type !== 'CallExpression' ||
|
||||
!isRequireCall(node) ||
|
||||
!isString(node.arguments[0])) {
|
||||
return undefined;
|
||||
}
|
||||
return node.arguments[0].value;
|
||||
}
|
||||
|
||||
function getPropertyName(node) {
|
||||
if (!node) return undefined;
|
||||
if (node.computed) {
|
||||
return node.property.type === 'Literal' ? node.property.value : undefined;
|
||||
}
|
||||
return node.property.name;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
messages: {
|
||||
noCryptoClassInstanceof:
|
||||
'Do not use `instanceof` to brand-check {{name}}; use its native or private brand.',
|
||||
noCryptoKeyInstanceof:
|
||||
'Use `isCryptoKey(value)` instead of `value instanceof CryptoKey`.',
|
||||
noKeyObjectInstanceof:
|
||||
'Use `isKeyObject(value)` instead of `value instanceof KeyObject`.',
|
||||
noX509CertificateInstanceof:
|
||||
'Use `isX509Certificate(value)` instead of `value instanceof X509Certificate`.',
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const sourceCode = context.sourceCode;
|
||||
const constructorNames = new Map();
|
||||
const namespaceClasses = new Map();
|
||||
|
||||
function registerVariable(node, name, map, value) {
|
||||
const variables = sourceCode.scopeManager.getDeclaredVariables(node);
|
||||
for (const variable of variables) {
|
||||
if (variable.name === name) map.set(variable, value);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveVariable(node) {
|
||||
let scope = sourceCode.getScope(node);
|
||||
while (scope !== null) {
|
||||
const variable = scope.set.get(node.name);
|
||||
if (variable !== undefined) return variable;
|
||||
scope = scope.upper;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isGlobalReference(node) {
|
||||
const variable = resolveVariable(node);
|
||||
return variable === undefined || variable.defs.length === 0;
|
||||
}
|
||||
|
||||
function registerRequire(node) {
|
||||
const module = getRequiredModule(node.init);
|
||||
const classes = moduleClasses.get(module);
|
||||
if (classes === undefined) return;
|
||||
|
||||
if (node.id.type === 'Identifier') {
|
||||
registerVariable(node, node.id.name, namespaceClasses, classes);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.id.type !== 'ObjectPattern') return;
|
||||
for (const property of node.id.properties) {
|
||||
if (property.type !== 'Property' ||
|
||||
property.value.type !== 'Identifier') {
|
||||
continue;
|
||||
}
|
||||
const importedName = property.key.name ?? property.key.value;
|
||||
if (classes.has(importedName)) {
|
||||
registerVariable(
|
||||
node, property.value.name, constructorNames, importedName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function declarationName(variable) {
|
||||
if (!knownClassNames.has(variable.name)) return undefined;
|
||||
for (const definition of variable.defs) {
|
||||
if (definition.type === 'ClassName' ||
|
||||
definition.type === 'FunctionName') {
|
||||
return variable.name;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function constructorName(node) {
|
||||
if (node.type === 'Identifier') {
|
||||
const variable = resolveVariable(node);
|
||||
if (variable !== undefined) {
|
||||
const name = constructorNames.get(variable) ??
|
||||
declarationName(variable);
|
||||
if (name !== undefined) return name;
|
||||
}
|
||||
if (node.name === 'CryptoKey' && isGlobalReference(node)) {
|
||||
return 'CryptoKey';
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
if (node.type !== 'MemberExpression') return undefined;
|
||||
|
||||
const property = getPropertyName(node);
|
||||
if (node.object.type !== 'Identifier') return undefined;
|
||||
if (node.object.name === 'globalThis' &&
|
||||
property === 'CryptoKey' &&
|
||||
isGlobalReference(node.object)) {
|
||||
return 'CryptoKey';
|
||||
}
|
||||
|
||||
const variable = resolveVariable(node.object);
|
||||
const classes = namespaceClasses.get(variable);
|
||||
return classes?.has(property) ? property : undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
VariableDeclarator: registerRequire,
|
||||
|
||||
BinaryExpression(node) {
|
||||
if (node.operator !== 'instanceof') return;
|
||||
|
||||
const name = constructorName(node.right);
|
||||
if (name === undefined) return;
|
||||
|
||||
let messageId = 'noCryptoClassInstanceof';
|
||||
if (name === 'CryptoKey' || name === 'InternalCryptoKey') {
|
||||
messageId = 'noCryptoKeyInstanceof';
|
||||
} else if (name.endsWith('KeyObject')) {
|
||||
messageId = 'noKeyObjectInstanceof';
|
||||
} else if (name.endsWith('X509Certificate')) {
|
||||
messageId = 'noX509CertificateInstanceof';
|
||||
}
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId,
|
||||
data: { name },
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,122 +0,0 @@
|
||||
/**
|
||||
* @file Prevent internal code from brand-checking keys with instanceof.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { isRequireCall, isString } = require('./rules-utils.js');
|
||||
|
||||
const CRYPTO_KEYS_MODULE = 'internal/crypto/keys';
|
||||
const WEBCRYPTO_MODULE = 'internal/crypto/webcrypto';
|
||||
|
||||
const keyObjectClassNames = new Set([
|
||||
'KeyObject',
|
||||
'SecretKeyObject',
|
||||
'AsymmetricKeyObject',
|
||||
'PublicKeyObject',
|
||||
'PrivateKeyObject',
|
||||
]);
|
||||
|
||||
const cryptoKeyClassNames = new Set([
|
||||
'CryptoKey',
|
||||
'InternalCryptoKey',
|
||||
]);
|
||||
|
||||
function isKeyModuleRequire(node) {
|
||||
return node?.type === 'CallExpression' &&
|
||||
isRequireCall(node) &&
|
||||
isString(node.arguments[0]) &&
|
||||
(node.arguments[0].value === CRYPTO_KEYS_MODULE ||
|
||||
node.arguments[0].value === WEBCRYPTO_MODULE);
|
||||
}
|
||||
|
||||
function getPropertyName(node) {
|
||||
if (!node) return undefined;
|
||||
if (node.computed) {
|
||||
return node.property.type === 'Literal' ? node.property.value : undefined;
|
||||
}
|
||||
return node.property.name;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
messages: {
|
||||
noKeyObjectInstanceof: 'Use `isKeyObject(value)` instead of `value instanceof KeyObject`.',
|
||||
noCryptoKeyInstanceof: 'Use `isCryptoKey(value)` instead of `value instanceof CryptoKey`.',
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const namespaceNames = new Set();
|
||||
const keyObjectConstructorNames = new Set();
|
||||
const cryptoKeyConstructorNames = new Set(['CryptoKey']);
|
||||
|
||||
function registerRequire(node) {
|
||||
if (!isKeyModuleRequire(node.init)) return;
|
||||
|
||||
if (node.id.type === 'Identifier') {
|
||||
namespaceNames.add(node.id.name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.id.type !== 'ObjectPattern') return;
|
||||
|
||||
for (const property of node.id.properties) {
|
||||
if (property.type !== 'Property') continue;
|
||||
const keyName = property.key.name ?? property.key.value;
|
||||
if (property.value.type !== 'Identifier') continue;
|
||||
const localName = property.value.name;
|
||||
if (keyObjectClassNames.has(keyName)) {
|
||||
keyObjectConstructorNames.add(localName);
|
||||
} else if (cryptoKeyClassNames.has(keyName)) {
|
||||
cryptoKeyConstructorNames.add(localName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function constructorKind(node) {
|
||||
if (node.type === 'Identifier') {
|
||||
if (keyObjectConstructorNames.has(node.name)) return 'KeyObject';
|
||||
if (cryptoKeyConstructorNames.has(node.name)) return 'CryptoKey';
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (node.type !== 'MemberExpression') return undefined;
|
||||
|
||||
const property = getPropertyName(node);
|
||||
if (node.object.type === 'Identifier') {
|
||||
if (namespaceNames.has(node.object.name)) {
|
||||
if (keyObjectClassNames.has(property)) return 'KeyObject';
|
||||
if (cryptoKeyClassNames.has(property)) return 'CryptoKey';
|
||||
}
|
||||
if (node.object.name === 'globalThis' &&
|
||||
cryptoKeyClassNames.has(property)) {
|
||||
return 'CryptoKey';
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
VariableDeclarator: registerRequire,
|
||||
|
||||
BinaryExpression(node) {
|
||||
if (node.operator !== 'instanceof') return;
|
||||
|
||||
const kind = constructorKind(node.right);
|
||||
if (kind === 'KeyObject') {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'noKeyObjectInstanceof',
|
||||
});
|
||||
} else if (kind === 'CryptoKey') {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'noCryptoKeyInstanceof',
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
Vendored
+28
@@ -755,6 +755,27 @@ declare namespace InternalCryptoBinding {
|
||||
toLegacy(): object;
|
||||
}
|
||||
|
||||
interface NativeX509CertificateConstructor {
|
||||
readonly prototype: X509CertificateHandle;
|
||||
new(value: X509CertificateHandle | ArrayBufferView): X509CertificateHandle;
|
||||
}
|
||||
|
||||
interface X509CertificateConstructor {
|
||||
readonly prototype: object;
|
||||
new(buffer: string | ArrayBufferView): object;
|
||||
}
|
||||
|
||||
interface InternalX509CertificateConstructor {
|
||||
readonly prototype: object;
|
||||
new(value: X509CertificateHandle | ArrayBufferView): object;
|
||||
}
|
||||
|
||||
type CreateX509CertificateClassCallback =
|
||||
(NativeX509Certificate: NativeX509CertificateConstructor) => [
|
||||
X509Certificate: X509CertificateConstructor,
|
||||
InternalX509Certificate: InternalX509CertificateConstructor,
|
||||
];
|
||||
|
||||
interface CipherInfo {
|
||||
name: string;
|
||||
nid: number;
|
||||
@@ -934,6 +955,12 @@ export interface CryptoBinding {
|
||||
PublicKeyObject: InternalCryptoBinding.KeyObjectSubtypeConstructor,
|
||||
PrivateKeyObject: InternalCryptoBinding.KeyObjectSubtypeConstructor,
|
||||
];
|
||||
createX509CertificateClass(
|
||||
callback: InternalCryptoBinding.CreateX509CertificateClassCallback,
|
||||
): [
|
||||
X509Certificate: InternalCryptoBinding.X509CertificateConstructor,
|
||||
InternalX509Certificate: InternalCryptoBinding.InternalX509CertificateConstructor,
|
||||
];
|
||||
getBundledRootCertificates(): string[];
|
||||
getCachedAliases(): Record<string, number>;
|
||||
getCertificateCompressionAlgorithms(): string[];
|
||||
@@ -951,6 +978,7 @@ export interface CryptoBinding {
|
||||
getHashes(): string[];
|
||||
isCryptoKey(key: unknown): boolean;
|
||||
isKeyObject(key: unknown): boolean;
|
||||
isX509Certificate(value: unknown): boolean;
|
||||
getKeyObjectSlots(key: object): InternalCryptoBinding.KeyObjectSlots;
|
||||
getOpenSSLSecLevelCrypto(): number | undefined;
|
||||
getSSLCiphers(): string[];
|
||||
|
||||
Reference in new issue
Block a user