'use strict';
const {
FunctionPrototypeCall,
ObjectGetPrototypeOf,
ObjectSetPrototypeOf,
SafeWeakMap,
} = primordials;
const {
createX509CertificateClass,
getX509CertificateSnapshot: getNativeX509CertificateSnapshot,
isX509Certificate: isNativeX509Certificate,
parseX509Snapshot,
X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT,
X509_CHECK_FLAG_NEVER_CHECK_SUBJECT,
X509_CHECK_FLAG_NO_WILDCARDS,
X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS,
X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS,
X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS,
} = internalBinding('crypto');
const {
PublicKeyObject,
getKeyObjectHandle,
getKeyObjectType,
isKeyObject,
} = require('internal/crypto/keys');
const {
customInspectSymbol: kInspect,
kEmptyObject,
} = require('internal/util');
const {
validateBoolean,
validateObject,
validateString,
} = require('internal/validators');
const { inspect } = require('internal/util/inspect');
const { Buffer } = require('buffer');
const {
isArrayBufferView,
} = require('internal/util/types');
const {
codes: {
ERR_CRYPTO_INVALID_X509_CERTIFICATE,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_THIS,
},
} = require('internal/errors');
let lazyTranslatePeerCertificate;
let isX509Certificate;
function getFlags(options = kEmptyObject) {
validateObject(options, 'options');
const {
subject = 'default', // Can be 'default', 'always', or 'never'
wildcards = true,
partialWildcards = true,
multiLabelWildcards = false,
singleLabelSubdomains = false,
} = { ...options };
let flags = 0;
validateString(subject, 'options.subject');
validateBoolean(wildcards, 'options.wildcards');
validateBoolean(partialWildcards, 'options.partialWildcards');
validateBoolean(multiLabelWildcards, 'options.multiLabelWildcards');
validateBoolean(singleLabelSubdomains, 'options.singleLabelSubdomains');
switch (subject) {
case 'default': /* Matches OpenSSL's default, no flags. */ break;
case 'always': flags |= X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT; break;
case 'never': flags |= X509_CHECK_FLAG_NEVER_CHECK_SUBJECT; break;
default:
throw new ERR_INVALID_ARG_VALUE('options.subject', subject);
}
if (!wildcards) flags |= X509_CHECK_FLAG_NO_WILDCARDS;
if (!partialWildcards) flags |= X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS;
if (multiLabelWildcards) flags |= X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS;
if (singleLabelSubdomains) flags |= X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS;
return flags;
}
const crossRealmState = new SafeWeakMap();
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;
}
let InternalX509Certificate;
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,
issuerName: nativeIssuerName,
issuer: nativeIssuer,
extensions: nativeExtensions,
keyUsage: nativeKeyUsage,
pem: nativePem,
publicKey: nativePublicKey,
raw: nativeRaw,
serialNumber: nativeSerialNumber,
signature: nativeSignature,
signatureAlgorithm: nativeSignatureAlgorithm,
signatureAlgorithmOid: nativeSignatureAlgorithmOid,
subject: nativeSubject,
subjectName: nativeSubjectName,
subjectAltName: nativeSubjectAltName,
toLegacy: nativeToLegacy,
validFrom: nativeValidFrom,
validFromDate: nativeValidFromDate,
validTo: nativeValidTo,
validToDate: nativeValidToDate,
version: nativeVersion,
verify: nativeVerify,
tbs: nativeTbs,
} = 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;
}
return state[index];
}
function getInspectionValue(certificate, getter) {
getState(certificate);
const value = FunctionPrototypeCall(getter, certificate);
if (value === undefined) {
throw new ERR_CRYPTO_INVALID_X509_CERTIFICATE(
'the certificate cannot be inspected');
}
return 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;
}
[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)}`;
}
get subject() {
return getCachedValue(this, kSlotSubject, nativeSubject);
}
get subjectAltName() {
return getCachedValue(
this, kSlotSubjectAltName, nativeSubjectAltName);
}
get issuer() {
return getCachedValue(this, kSlotIssuer, nativeIssuer);
}
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];
}
get infoAccess() {
return getCachedValue(this, kSlotInfoAccess, nativeInfoAccess);
}
get validFrom() {
return getCachedValue(this, kSlotValidFrom, nativeValidFrom);
}
get validTo() {
return getCachedValue(this, kSlotValidTo, nativeValidTo);
}
get validFromDate() {
return getCachedValue(this, kSlotValidFromDate, nativeValidFromDate);
}
get validToDate() {
return getCachedValue(this, kSlotValidToDate, nativeValidToDate);
}
get fingerprint() {
return getCachedValue(this, kSlotFingerprint, nativeFingerprint);
}
get fingerprint256() {
return getCachedValue(this, kSlotFingerprint256, nativeFingerprint256);
}
get fingerprint512() {
return getCachedValue(this, kSlotFingerprint512, nativeFingerprint512);
}
get keyUsage() {
return getCachedValue(this, kSlotKeyUsage, nativeKeyUsage);
}
get serialNumber() {
return getCachedValue(this, kSlotSerialNumber, nativeSerialNumber);
}
get signatureAlgorithm() {
return getCachedValue(
this, kSlotSignatureAlgorithm, nativeSignatureAlgorithm);
}
get signatureAlgorithmOid() {
return getCachedValue(
this, kSlotSignatureAlgorithmOid, nativeSignatureAlgorithmOid);
}
get version() {
return getInspectionValue(this, nativeVersion);
}
get subjectName() {
return getInspectionValue(this, nativeSubjectName);
}
get issuerName() {
return getInspectionValue(this, nativeIssuerName);
}
get extensions() {
return getInspectionValue(this, nativeExtensions);
}
get tbs() {
return getInspectionValue(this, nativeTbs);
}
get signature() {
return getInspectionValue(this, nativeSignature);
}
get raw() {
return getCachedValue(this, kSlotRaw, nativeRaw);
}
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));
}
}
InternalX509Certificate = class InternalX509Certificate
extends NativeX509Certificate {
#state = createState();
static {
isX509Certificate = (value) => {
if (value == null || typeof value !== 'object') return false;
return #state in value || isNativeX509Certificate(value);
};
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;
};
}
};
InternalX509Certificate.prototype.constructor = X509Certificate;
ObjectSetPrototypeOf(
InternalX509Certificate.prototype,
X509Certificate.prototype);
return [X509Certificate, InternalX509Certificate];
});
// Keep the binding-returned constructor and the closure reference aligned.
InternalX509Certificate = InternalX509CertificateConstructor;
function getX509CertificateSnapshot(certificate) {
if (!isX509Certificate(certificate)) {
throw new ERR_INVALID_ARG_TYPE(
'certificate', 'X509Certificate', certificate);
}
return getNativeX509CertificateSnapshot(certificate);
}
function createX509CertificateFromSnapshot(snapshot) {
return new InternalX509Certificate(parseX509Snapshot(snapshot));
}
module.exports = {
X509Certificate,
InternalX509Certificate,
createX509CertificateFromSnapshot,
getX509CertificateSnapshot,
isX509Certificate,
};