UNPKG

@voice-ping/cognitive-services-speech

Version:

VoicePing Cognitive Services Speech SDK for JavaScript forked from Microsoft

1 lines 16 kB
{"version":3,"sources":["src/common.browser/CertChecks.ts"],"names":[],"mappings":";AAGA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAwB7B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAUxC,qBAAa,cAAc;IAGvB,OAAc,cAAc,EAAE,MAAM,CAAK;IAGzC,OAAc,wBAAwB,EAAE,OAAO,CAAS;IAGxD,OAAO,CAAC,MAAM,CAAC,YAAY,CAAiC;IAG5D,OAAO,CAAC,MAAM,CAAC,aAAa,CAAQ;IAEpC,OAAO,CAAC,aAAa,CAAY;gBAErB,SAAS,CAAC,EAAE,SAAS;WAYnB,oBAAoB,IAAI,IAAI;IAKnC,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC,KAAK;IAatD,OAAO,CAAC,MAAM,CAAC,aAAa;mBAoBP,SAAS;IAyD9B,OAAO,CAAC,MAAM,CAAC,SAAS;mBAkBH,oBAAoB;mBAkEpB,kBAAkB;mBAkClB,WAAW;IAUhC,OAAO,CAAC,MAAM,CAAC,eAAe;IAK9B,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAKpC,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAMlC,OAAO,CAAC,MAAM,CAAC,eAAe;IAiC9B,OAAO,CAAC,MAAM,CAAC,OAAO,CAErB;IAED,OAAO,CAAC,gBAAgB;CAmC3B","file":"CertChecks.d.ts","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT license.\n\nimport * as http from \"http\";\nimport * as tls from \"tls\";\nimport * as url from \"url\";\nimport * as ocsp from \"../../external/ocsp/ocsp\";\nimport {\n Events,\n OCSPCacheEntryExpiredEvent,\n OCSPCacheEntryNeedsRefreshEvent,\n OCSPCacheFetchErrorEvent,\n OCSPCacheHitEvent,\n OCSPCacheMissEvent,\n OCSPCacheUpdatehCompleteEvent,\n OCSPCacheUpdateNeededEvent,\n OCSPDiskCacheHitEvent,\n OCSPDiskCacheStoreEvent,\n OCSPEvent,\n OCSPMemoryCacheHitEvent,\n OCSPMemoryCacheStoreEvent,\n OCSPResponseRetrievedEvent,\n OCSPStapleReceivedEvent,\n OCSPVerificationFailedEvent,\n OCSPWSUpgradeStartedEvent\n} from \"../common/Exports\";\nimport { IStringDictionary } from \"../common/IDictionary\";\nimport { ProxyInfo } from \"./ProxyInfo\";\n\nimport Agent from \"agent-base\";\n\n// @ts-ignore\nimport Cache from \"async-disk-cache\";\nimport HttpsProxyAgent from \"https-proxy-agent\";\nimport * as net from \"net\";\nimport { OCSPCacheUpdateErrorEvent } from \"../common/OCSPEvents\";\n\nexport class CertCheckAgent {\n\n // Test hook to enable forcing expiration / refresh to happen.\n public static testTimeOffset: number = 0;\n\n // Test hook to disable stapling for cache testing.\n public static forceDisableOCSPStapling: boolean = false;\n\n // An in memory cache for recived responses.\n private static privMemCache: IStringDictionary<Buffer> = {};\n\n // The on disk cache.\n private static privDiskCache: Cache;\n\n private privProxyInfo: ProxyInfo;\n\n constructor(proxyInfo?: ProxyInfo) {\n if (!!proxyInfo) {\n this.privProxyInfo = proxyInfo;\n }\n\n // Initialize this here to allow tests to set the env variable before the cache is constructed.\n if (!CertCheckAgent.privDiskCache) {\n CertCheckAgent.privDiskCache = new Cache(\"microsoft-cognitiveservices-speech-sdk-cache\", { supportBuffer: true, location: (typeof process !== \"undefined\" && !!process.env.SPEECH_OCSP_CACHE_ROOT) ? process.env.SPEECH_OCSP_CACHE_ROOT : undefined });\n }\n }\n\n // Test hook to force the disk cache to be recreated.\n public static forceReinitDiskCache(): void {\n CertCheckAgent.privDiskCache = undefined;\n CertCheckAgent.privMemCache = {};\n }\n\n public GetAgent(disableStapling?: boolean): http.Agent {\n const agent: any = new Agent.Agent(this.CreateConnection);\n\n if (this.privProxyInfo !== undefined &&\n this.privProxyInfo.HostName !== undefined &&\n this.privProxyInfo.Port > 0) {\n const proxyName: string = \"privProxyInfo\";\n agent[proxyName] = this.privProxyInfo;\n }\n\n return agent;\n }\n\n private static GetProxyAgent(proxyInfo: ProxyInfo): HttpsProxyAgent {\n const httpProxyOptions: HttpsProxyAgent.HttpsProxyAgentOptions = {\n host: proxyInfo.HostName,\n port: proxyInfo.Port,\n };\n\n if (!!proxyInfo.UserName) {\n httpProxyOptions.headers = {\n \"Proxy-Authentication\": \"Basic \" + new Buffer(proxyInfo.UserName + \":\" + (proxyInfo.Password === undefined) ? \"\" : proxyInfo.Password).toString(\"base64\"),\n };\n } else {\n httpProxyOptions.headers = {};\n }\n\n httpProxyOptions.headers.requestOCSP = \"true\";\n\n const httpProxyAgent: HttpsProxyAgent = new HttpsProxyAgent(httpProxyOptions);\n return httpProxyAgent;\n }\n\n private static async OCSPCheck(socketPromise: Promise<net.Socket>, proxyInfo: ProxyInfo): Promise<net.Socket> {\n let ocspRequest: ocsp.Request;\n let stapling: Buffer;\n let resolved: boolean = false;\n\n const socket: net.Socket = await socketPromise;\n socket.cork();\n\n const tlsSocket: tls.TLSSocket = socket as tls.TLSSocket;\n\n return new Promise<net.Socket>((resolve: (value: net.Socket) => void, reject: (error: string | Error) => void) => {\n socket.on(\"OCSPResponse\", (data: Buffer): void => {\n if (!!data) {\n this.onEvent(new OCSPStapleReceivedEvent());\n stapling = data;\n }\n });\n\n socket.on(\"error\", (error: Error) => {\n if (!resolved) {\n resolved = true;\n socket.destroy();\n reject(error);\n }\n });\n\n tlsSocket.on(\"secure\", async () => {\n const peer: tls.DetailedPeerCertificate = tlsSocket.getPeerCertificate(true);\n try {\n const issuer: tls.DetailedPeerCertificate = await this.GetIssuer(peer);\n\n // We always need a request to verify the response.\n ocspRequest = ocsp.request.generate(peer.raw, issuer.raw);\n\n // Do we have a result for this certificate in our memory cache?\n const sig: string = ocspRequest.id.toString(\"hex\");\n\n // Stapled response trumps cached response.\n if (!stapling) {\n const cacheEntry: Buffer = await CertCheckAgent.GetResponseFromCache(sig, ocspRequest, proxyInfo);\n stapling = cacheEntry;\n }\n\n await this.VerifyOCSPResponse(stapling, ocspRequest, proxyInfo);\n\n socket.uncork();\n resolved = true;\n resolve(socket);\n } catch (e) {\n socket.destroy();\n resolved = true;\n reject(e);\n }\n });\n });\n }\n\n private static GetIssuer(peer: tls.DetailedPeerCertificate): Promise<tls.DetailedPeerCertificate> {\n if (peer.issuerCertificate) {\n return Promise.resolve(peer.issuerCertificate);\n }\n\n return new Promise<tls.DetailedPeerCertificate>((resolve: (value: tls.DetailedPeerCertificate) => void, reject: (reason: string) => void) => {\n const ocspAgent: ocsp.Agent = new ocsp.Agent({});\n ocspAgent.fetchIssuer(peer, null, (error: string, value: tls.DetailedPeerCertificate): void => {\n if (!!error) {\n reject(error);\n return;\n }\n\n resolve(value);\n });\n });\n }\n\n private static async GetResponseFromCache(signature: string, ocspRequest: ocsp.Request, proxyInfo: ProxyInfo): Promise<Buffer> {\n let cachedResponse: Buffer = CertCheckAgent.privMemCache[signature];\n\n if (!!cachedResponse) {\n this.onEvent(new OCSPMemoryCacheHitEvent(signature));\n }\n\n // Do we have a result for this certificate on disk in %TMP%?\n if (!cachedResponse) {\n try {\n const diskCacheResponse: any = await CertCheckAgent.privDiskCache.get(signature);\n if (!!diskCacheResponse.isCached) {\n CertCheckAgent.onEvent(new OCSPDiskCacheHitEvent(signature));\n CertCheckAgent.StoreMemoryCacheEntry(signature, diskCacheResponse.value);\n cachedResponse = diskCacheResponse.value;\n }\n } catch (error) {\n cachedResponse = null;\n }\n }\n\n if (!cachedResponse) {\n return cachedResponse;\n }\n\n try {\n const cachedOcspResponse: ocsp.Response = ocsp.utils.parseResponse(cachedResponse);\n const tbsData = cachedOcspResponse.value.tbsResponseData;\n if (tbsData.responses.length < 1) {\n this.onEvent(new OCSPCacheFetchErrorEvent(signature, \"Not enough data in cached response\"));\n return;\n }\n\n const cachedStartTime: number = tbsData.responses[0].thisUpdate;\n const cachedNextTime: number = tbsData.responses[0].nextUpdate;\n\n if (cachedNextTime < (Date.now() + this.testTimeOffset - 60000)) {\n // Cached entry has expired.\n this.onEvent(new OCSPCacheEntryExpiredEvent(signature, cachedNextTime));\n cachedResponse = null;\n } else {\n // If we're within one day of the next update, or 50% of the way through the validity period,\n // background an update to the cache.\n\n const minUpdate: number = Math.min(24 * 60 * 60 * 1000, (cachedNextTime - cachedStartTime) / 2);\n\n if ((cachedNextTime - (Date.now() + this.testTimeOffset)) < minUpdate) {\n this.onEvent(new OCSPCacheEntryNeedsRefreshEvent(signature, cachedStartTime, cachedNextTime));\n this.UpdateCache(ocspRequest, proxyInfo).catch((error: string) => {\n // Well, not much we can do here.\n this.onEvent(new OCSPCacheUpdateErrorEvent(signature, error.toString()));\n });\n } else {\n this.onEvent(new OCSPCacheHitEvent(signature, cachedStartTime, cachedNextTime));\n }\n }\n } catch (error) {\n this.onEvent(new OCSPCacheFetchErrorEvent(signature, error));\n cachedResponse = null;\n }\n if (!cachedResponse) {\n this.onEvent(new OCSPCacheMissEvent(signature));\n }\n return cachedResponse;\n }\n\n private static async VerifyOCSPResponse(cacheValue: Buffer, ocspRequest: ocsp.Request, proxyInfo: ProxyInfo): Promise<void> {\n let ocspResponse: Buffer = cacheValue;\n const sig: string = ocspRequest.certID.toString(\"hex\");\n\n // Do we have a valid response?\n if (!ocspResponse) {\n ocspResponse = await CertCheckAgent.GetOCSPResponse(ocspRequest, proxyInfo);\n }\n\n return new Promise<void>((resolve: () => void, reject: (error: string | Error) => void) => {\n ocsp.verify({ request: ocspRequest, response: ocspResponse }, (error: string, result: any): void => {\n if (!!error) {\n CertCheckAgent.onEvent(new OCSPVerificationFailedEvent(ocspRequest.id.toString(\"hex\"), error));\n\n // Bad Cached Value? One more try without the cache.\n if (!!cacheValue) {\n this.VerifyOCSPResponse(null, ocspRequest, proxyInfo).then(() => {\n resolve();\n }, (error: Error) => {\n reject(error);\n });\n } else {\n reject(error);\n }\n } else {\n if (!cacheValue) {\n CertCheckAgent.StoreCacheEntry(ocspRequest.id.toString(\"hex\"), ocspResponse);\n }\n resolve();\n }\n });\n });\n }\n\n private static async UpdateCache(req: ocsp.Request, proxyInfo: ProxyInfo): Promise<void> {\n const signature: string = req.id.toString(\"hex\");\n this.onEvent(new OCSPCacheUpdateNeededEvent(signature));\n\n const rawResponse: Buffer = await this.GetOCSPResponse(req, proxyInfo);\n this.StoreCacheEntry(signature, rawResponse);\n this.onEvent(new OCSPCacheUpdatehCompleteEvent(req.id.toString(\"hex\")));\n\n }\n\n private static StoreCacheEntry(sig: string, rawResponse: Buffer): void {\n this.StoreMemoryCacheEntry(sig, rawResponse);\n this.StoreDiskCacheEntry(sig, rawResponse);\n }\n\n private static StoreMemoryCacheEntry(sig: string, rawResponse: Buffer): void {\n this.privMemCache[sig] = rawResponse;\n this.onEvent(new OCSPMemoryCacheStoreEvent(sig));\n }\n\n private static StoreDiskCacheEntry(sig: string, rawResponse: Buffer): void {\n this.privDiskCache.set(sig, rawResponse).then(() => {\n this.onEvent(new OCSPDiskCacheStoreEvent(sig));\n });\n }\n\n private static GetOCSPResponse(req: ocsp.Request, proxyInfo: ProxyInfo): Promise<Buffer> {\n\n const ocspMethod: string = \"1.3.6.1.5.5.7.48.1\";\n let options: http.RequestOptions = {};\n\n if (!!proxyInfo) {\n const agent: HttpsProxyAgent = CertCheckAgent.GetProxyAgent(proxyInfo);\n options.agent = agent;\n }\n\n return new Promise<Buffer>((resolve: (value: Buffer) => void, reject: (error: string | Error) => void) => {\n ocsp.utils.getAuthorityInfo(req.cert, ocspMethod, (error: string, uri: string): void => {\n if (error) {\n reject(error);\n return;\n }\n\n const parsedUri = url.parse(uri);\n options = { ...options, ...parsedUri };\n\n ocsp.utils.getResponse(options, req.data, (error: string, raw: Buffer): void => {\n if (error) {\n reject(error);\n return;\n }\n\n this.onEvent(new OCSPResponseRetrievedEvent(req.certID.toString(\"hex\")));\n resolve(raw);\n });\n });\n });\n }\n\n private static onEvent = (event: OCSPEvent): void => {\n Events.instance.onEvent(event);\n }\n\n private CreateConnection(request: Agent.ClientRequest, options: Agent.RequestOptions): Promise<net.Socket> {\n const enableOCSP: boolean = (typeof process !== \"undefined\" && process.env.NODE_TLS_REJECT_UNAUTHORIZED !== \"0\" && process.env.SPEECH_CONDUCT_OCSP_CHECK !== \"0\") && options.secureEndpoint;\n let socketPromise: Promise<net.Socket>;\n\n options = {\n ...options,\n ...{\n requestOCSP: !CertCheckAgent.forceDisableOCSPStapling,\n servername: options.host\n }\n };\n\n if (!!this.privProxyInfo) {\n const httpProxyAgent: HttpsProxyAgent = CertCheckAgent.GetProxyAgent(this.privProxyInfo);\n const baseAgent: Agent.Agent = httpProxyAgent as unknown as Agent.Agent;\n\n socketPromise = new Promise<net.Socket>((resolve: (value: net.Socket) => void, reject: (error: string | Error) => void) => {\n baseAgent.callback(request, options, (error: Error, socket: net.Socket) => {\n if (!!error) {\n reject(error);\n } else {\n resolve(socket);\n }\n });\n });\n } else {\n socketPromise = Promise.resolve(tls.connect(options));\n }\n\n if (!!enableOCSP) {\n return CertCheckAgent.OCSPCheck(socketPromise, this.privProxyInfo);\n } else {\n return socketPromise;\n }\n }\n}\n"]}