UNPKG

snowflake-sdk

Version:
124 lines 5.95 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CertificateRevokedError = exports.CRL_VALIDATOR_INTERNAL = void 0; exports.isCrlValidationEnabled = isCrlValidationEnabled; exports.corkSocketAndValidateCrl = corkSocketAndValidateCrl; exports.validateCrl = validateCrl; const asn1_js_rfc5280_1 = __importDefault(require("asn1.js-rfc5280")); const crypto_1 = __importDefault(require("crypto")); const logger_1 = __importDefault(require("../../logger")); const certificate_utils_1 = require("./certificate_utils"); const crl_fetcher_1 = require("./crl_fetcher"); const crl_signature_verifier_1 = require("./crl_signature_verifier"); const errors_1 = require("../../errors"); // Allows to mock/spy internal calls in tests exports.CRL_VALIDATOR_INTERNAL = { validateCrl: (...args) => validateCrl(...args), }; class CertificateRevokedError extends Error { constructor(message) { super(message); this.name = 'CertificateRevokedError'; } } exports.CertificateRevokedError = CertificateRevokedError; function isCrlValidationEnabled(config) { return config.checkMode !== 'DISABLED'; } function corkSocketAndValidateCrl(socket, config) { socket.once('secureConnect', async () => { const certChain = socket.getPeerCertificate(true); try { await exports.CRL_VALIDATOR_INTERNAL.validateCrl(certChain, config); } catch (error) { if (!(error instanceof CertificateRevokedError) && config.checkMode === 'ADVISORY') { (0, logger_1.default)().warn('Failed to check CRL revocation, but checkMode=ADVISORY. Allowing connection. Error: %j', error); } else { // NOTE: Wrap error into CrlError to prevent retries socket.destroy((0, errors_1.createCrlError)(error)); } } socket.uncork(); }); socket.cork(); } function* iterateCertChain(cert) { let current = cert; while (current) { if (current === current.issuerCertificate) { break; // Root is self-signed, ignoring } yield current; current = current.issuerCertificate; } } // NOTE: // Sticking with asn1.js-rfc5280 + custom signature validation, because popular libraries have issues: // - jsrsasign: has outdated crypto library with CEV issues // - pkijs: takes 4 seconds to parse 9Mb CRL // - @peculiar/x509: takes 2.5 seconds to parse 9Mb CRL async function validateCrl(certChain, config) { for (const certificate of iterateCertChain(certChain)) { const decodedCertificate = asn1_js_rfc5280_1.default.Certificate.decode(certificate.raw, 'der'); const name = (0, certificate_utils_1.getCertificateDebugName)(certificate); const logDebug = (msg) => (0, logger_1.default)().debug(`validateCrl[${name}]: ${msg}`); logDebug('starting validation'); if ((0, certificate_utils_1.isShortLivedCertificate)(decodedCertificate)) { logDebug('certificate is short-lived, skipping'); continue; } logDebug('getting CRL distribution points'); const crlUrls = (0, certificate_utils_1.getCertificateCrlUrls)(name, decodedCertificate); if (!crlUrls) { if (config.allowCertificatesWithoutCrlURL) { logDebug('certificate has no CRL distribution points, skipping'); continue; } throw new Error(`Certificate ${name} does not have CRL http URL. This could be disabled with allowCertificatesWithoutCrlURL`); } const decodedIssuerCertificate = asn1_js_rfc5280_1.default.Certificate.decode(certificate.issuerCertificate.raw, 'der'); const issuerSubject = JSON.stringify(decodedIssuerCertificate.tbsCertificate.subject); const issuerPublicKey = crypto_1.default .createPublicKey({ key: certificate.issuerCertificate.pubkey, format: 'der', type: 'spki', }) .export({ format: 'pem', type: 'spki' }); for (const crlUrl of crlUrls) { logDebug(`fetching ${crlUrl}`); const crl = await (0, crl_fetcher_1.getCrl)(crlUrl, { inMemoryCache: config.inMemoryCache, onDiskCache: config.onDiskCache, }); logDebug(`validating ${crlUrl} signature`); if (!(0, crl_signature_verifier_1.isCrlSignatureValid)(crl, issuerPublicKey)) { throw new Error(`CRL ${crlUrl} signature is invalid. Expected signature by ${(0, certificate_utils_1.getCertificateDebugName)(certificate.issuerCertificate)}`); } logDebug(`validating ${crlUrl} issuingDistributionPoint extension`); if (!(0, certificate_utils_1.isIssuingDistributionPointExtensionValid)(crl, crlUrl)) { throw new Error(`CRL ${crlUrl} issuingDistributionPoint extension is invalid`); } logDebug(`validating ${crlUrl} issuer`); const crlIssuer = JSON.stringify(crl.tbsCertList.issuer); if (issuerSubject !== crlIssuer) { throw new Error(`CRL ${crlUrl} issuer is invalid. Expected ${issuerSubject} but got ${crlIssuer}`); } logDebug(`validating ${crlUrl} nextUpdate`); if (crl.tbsCertList.nextUpdate.value < Date.now()) { throw new Error(`CRL ${crlUrl} nextUpdate is expired`); } logDebug(`checking if certificate is revoked in ${crlUrl}`); if ((0, certificate_utils_1.isCertificateRevoked)(decodedCertificate, crl)) { throw new CertificateRevokedError(`Certificate ${name} is revoked in ${crlUrl}`); } } } return true; } //# sourceMappingURL=index.js.map