UNPKG

ac-microsoft-cognitiveservices-speech-sdk

Version:
342 lines (340 loc) 15 kB
"use strict"; /* eslint-disable import/order */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CertCheckAgent = void 0; const tls = __importStar(require("tls")); const ocsp = __importStar(require("../../external/ocsp/ocsp")); const Exports_1 = require("../common/Exports"); const agent_base_1 = __importDefault(require("agent-base")); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const async_disk_cache_1 = __importDefault(require("async-disk-cache")); const https_proxy_agent_1 = __importDefault(require("https-proxy-agent")); const net = __importStar(require("net")); const OCSPEvents_1 = require("../common/OCSPEvents"); class CertCheckAgent { constructor(proxyInfo) { if (!!proxyInfo) { this.privProxyInfo = proxyInfo; } // Initialize this here to allow tests to set the env variable before the cache is constructed. if (!CertCheckAgent.privDiskCache) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call CertCheckAgent.privDiskCache = new async_disk_cache_1.default("microsoft-cognitiveservices-speech-sdk-cache", { supportBuffer: true, location: (typeof process !== "undefined" && !!process.env.SPEECH_OCSP_CACHE_ROOT) ? process.env.SPEECH_OCSP_CACHE_ROOT : undefined }); } } // Test hook to force the disk cache to be recreated. static forceReinitDiskCache() { CertCheckAgent.privDiskCache = undefined; CertCheckAgent.privMemCache = {}; } // eslint-disable-next-line @typescript-eslint/no-unused-vars GetAgent(disableStapling) { // eslint-disable-next-line @typescript-eslint/unbound-method const agent = new agent_base_1.default.Agent(this.CreateConnection); if (this.privProxyInfo !== undefined && this.privProxyInfo.HostName !== undefined && this.privProxyInfo.Port > 0) { const proxyName = "privProxyInfo"; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access agent[proxyName] = this.privProxyInfo; } return agent; } static GetProxyAgent(proxyInfo) { const httpProxyOptions = { host: proxyInfo.HostName, port: proxyInfo.Port, }; if (!!proxyInfo.UserName) { httpProxyOptions.headers = { "Proxy-Authentication": "Basic " + new Buffer(`${proxyInfo.UserName}:${(proxyInfo.Password === undefined) ? "" : proxyInfo.Password}`).toString("base64"), }; } else { httpProxyOptions.headers = {}; } httpProxyOptions.headers.requestOCSP = "true"; const httpProxyAgent = new https_proxy_agent_1.default(httpProxyOptions); return httpProxyAgent; } static async OCSPCheck(socketPromise, proxyInfo) { let ocspRequest; let stapling; let resolved = false; const socket = await socketPromise; socket.cork(); const tlsSocket = socket; return new Promise((resolve, reject) => { socket.on("OCSPResponse", (data) => { if (!!data) { this.onEvent(new Exports_1.OCSPStapleReceivedEvent()); stapling = data; } }); socket.on("error", (error) => { if (!resolved) { resolved = true; socket.destroy(); reject(error); } }); // eslint-disable-next-line @typescript-eslint/no-misused-promises, @typescript-eslint/explicit-function-return-type tlsSocket.on("secure", async () => { const peer = tlsSocket.getPeerCertificate(true); try { const issuer = await this.GetIssuer(peer); // We always need a request to verify the response. ocspRequest = ocsp.request.generate(peer.raw, issuer.raw); // Do we have a result for this certificate in our memory cache? const sig = ocspRequest.id.toString("hex"); // Stapled response trumps cached response. if (!stapling) { const cacheEntry = await CertCheckAgent.GetResponseFromCache(sig, ocspRequest, proxyInfo); stapling = cacheEntry; } await this.VerifyOCSPResponse(stapling, ocspRequest, proxyInfo); socket.uncork(); resolved = true; resolve(socket); } catch (e) { socket.destroy(); resolved = true; reject(e); } }); }); } static GetIssuer(peer) { if (peer.issuerCertificate) { return Promise.resolve(peer.issuerCertificate); } return new Promise((resolve, reject) => { const ocspAgent = new ocsp.Agent({}); ocspAgent.fetchIssuer(peer, null, (error, value) => { if (!!error) { reject(error); return; } resolve(value); }); }); } static async GetResponseFromCache(signature, ocspRequest, proxyInfo) { let cachedResponse = CertCheckAgent.privMemCache[signature]; if (!!cachedResponse) { this.onEvent(new Exports_1.OCSPMemoryCacheHitEvent(signature)); } // Do we have a result for this certificate on disk in %TMP%? if (!cachedResponse) { try { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call const diskCacheResponse = await CertCheckAgent.privDiskCache.get(signature); if (!!diskCacheResponse.isCached) { CertCheckAgent.onEvent(new Exports_1.OCSPDiskCacheHitEvent(signature)); CertCheckAgent.StoreMemoryCacheEntry(signature, diskCacheResponse.value); cachedResponse = diskCacheResponse.value; } } catch (error) { cachedResponse = null; } } if (!cachedResponse) { return cachedResponse; } try { const cachedOcspResponse = ocsp.utils.parseResponse(cachedResponse); const responseValue = cachedOcspResponse.value; const tbsData = responseValue.tbsResponseData; if (tbsData.responses.length < 1) { this.onEvent(new Exports_1.OCSPCacheFetchErrorEvent(signature, "Not enough data in cached response")); return; } const cachedStartTime = tbsData.responses[0].thisUpdate; const cachedNextTime = tbsData.responses[0].nextUpdate; if (cachedNextTime < (Date.now() + this.testTimeOffset - 60000)) { // Cached entry has expired. this.onEvent(new Exports_1.OCSPCacheEntryExpiredEvent(signature, cachedNextTime)); cachedResponse = null; } else { // If we're within one day of the next update, or 50% of the way through the validity period, // background an update to the cache. const minUpdate = Math.min(24 * 60 * 60 * 1000, (cachedNextTime - cachedStartTime) / 2); if ((cachedNextTime - (Date.now() + this.testTimeOffset)) < minUpdate) { this.onEvent(new Exports_1.OCSPCacheEntryNeedsRefreshEvent(signature, cachedStartTime, cachedNextTime)); this.UpdateCache(ocspRequest, proxyInfo).catch((error) => { // Well, not much we can do here. this.onEvent(new OCSPEvents_1.OCSPCacheUpdateErrorEvent(signature, error.toString())); }); } else { this.onEvent(new Exports_1.OCSPCacheHitEvent(signature, cachedStartTime, cachedNextTime)); } } } catch (error) { this.onEvent(new Exports_1.OCSPCacheFetchErrorEvent(signature, error)); cachedResponse = null; } if (!cachedResponse) { this.onEvent(new Exports_1.OCSPCacheMissEvent(signature)); } return cachedResponse; } static async VerifyOCSPResponse(cacheValue, ocspRequest, proxyInfo) { let ocspResponse = cacheValue; // Do we have a valid response? if (!ocspResponse) { ocspResponse = await CertCheckAgent.GetOCSPResponse(ocspRequest, proxyInfo); } return new Promise((resolve, reject) => { ocsp.verify({ request: ocspRequest, response: ocspResponse }, (error) => { if (!!error) { CertCheckAgent.onEvent(new Exports_1.OCSPVerificationFailedEvent(ocspRequest.id.toString("hex"), error)); // Bad Cached Value? One more try without the cache. if (!!cacheValue) { this.VerifyOCSPResponse(null, ocspRequest, proxyInfo).then(() => { resolve(); }, (error) => { reject(error); }); } else { reject(error); } } else { if (!cacheValue) { CertCheckAgent.StoreCacheEntry(ocspRequest.id.toString("hex"), ocspResponse); } resolve(); } }); }); } static async UpdateCache(req, proxyInfo) { const signature = req.id.toString("hex"); this.onEvent(new Exports_1.OCSPCacheUpdateNeededEvent(signature)); const rawResponse = await this.GetOCSPResponse(req, proxyInfo); this.StoreCacheEntry(signature, rawResponse); this.onEvent(new Exports_1.OCSPCacheUpdateCompleteEvent(req.id.toString("hex"))); } static StoreCacheEntry(sig, rawResponse) { this.StoreMemoryCacheEntry(sig, rawResponse); this.StoreDiskCacheEntry(sig, rawResponse); } static StoreMemoryCacheEntry(sig, rawResponse) { this.privMemCache[sig] = rawResponse; this.onEvent(new Exports_1.OCSPMemoryCacheStoreEvent(sig)); } static StoreDiskCacheEntry(sig, rawResponse) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call this.privDiskCache.set(sig, rawResponse).then(() => { this.onEvent(new Exports_1.OCSPDiskCacheStoreEvent(sig)); }); } static GetOCSPResponse(req, proxyInfo) { const ocspMethod = "1.3.6.1.5.5.7.48.1"; let options = {}; if (!!proxyInfo) { const agent = CertCheckAgent.GetProxyAgent(proxyInfo); options.agent = agent; } return new Promise((resolve, reject) => { ocsp.utils.getAuthorityInfo(req.cert, ocspMethod, (error, uri) => { if (error) { reject(error); return; } const url = new URL(uri); options = { ...options, host: url.host, protocol: url.protocol, port: url.port, path: url.pathname, hostname: url.host }; ocsp.utils.getResponse(options, req.data, (error, raw) => { if (error) { reject(error); return; } const certID = req.certID; this.onEvent(new Exports_1.OCSPResponseRetrievedEvent(certID.toString("hex"))); resolve(raw); }); }); }); } static onEvent(event) { Exports_1.Events.instance.onEvent(event); } CreateConnection(request, options) { const enableOCSP = (typeof process !== "undefined" && process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "0" && process.env.SPEECH_CONDUCT_OCSP_CHECK !== "0") && options.secureEndpoint; let socketPromise; options = { ...options, ...{ requestOCSP: !CertCheckAgent.forceDisableOCSPStapling, servername: options.host } }; if (!!this.privProxyInfo) { const httpProxyAgent = CertCheckAgent.GetProxyAgent(this.privProxyInfo); const baseAgent = httpProxyAgent; socketPromise = new Promise((resolve, reject) => { baseAgent.callback(request, options, (error, socket) => { if (!!error) { reject(error); } else { resolve(socket); } }); }); } else { if (!!options.secureEndpoint) { socketPromise = Promise.resolve(tls.connect(options)); } else { socketPromise = Promise.resolve(net.connect(options)); } } if (!!enableOCSP) { return CertCheckAgent.OCSPCheck(socketPromise, this.privProxyInfo); } else { return socketPromise; } } } exports.CertCheckAgent = CertCheckAgent; // Test hook to enable forcing expiration / refresh to happen. CertCheckAgent.testTimeOffset = 0; // Test hook to disable stapling for cache testing. CertCheckAgent.forceDisableOCSPStapling = false; // An in memory cache for recived responses. CertCheckAgent.privMemCache = {}; //# sourceMappingURL=CertChecks.js.map