Add a cache for OCSP responses. (#196)
* Extend OCSP certificate verification to include a memory and disk based cache. The disk cache will by default be located in TMPDIR/<username>/ but can be overridden. When the cached OCSP response is within 1 day of expiring, or 1/2 way through it's vaility period (whichever is shorter) a background task is queued to refresh it while the cached value is used. * More fixes * Fix error case * Fix typos
This commit is contained in:
Родитель
e8d024cfde
Коммит
fc9f3f6d2a
|
@ -1,12 +1,47 @@
|
|||
|
||||
/** Declaration file generated by dts-gen */
|
||||
|
||||
import { Certificate, Hash } from "crypto";
|
||||
import * as http from "http";
|
||||
import { DetailedPeerCertificate } from "tls";
|
||||
import { DetailedPeerCertificate, Certificate, TlsOptions } from "tls";
|
||||
import { HttpsProxyAgentOptions } from "https-proxy-agent";
|
||||
|
||||
export class Agent extends http.Agent {
|
||||
public constructor(options: any);
|
||||
public fetchIssuer(peerCert: DetailedPeerCertificate, stapling: Certificate, cb: (error: string, result: DetailedPeerCertificate) => void): void;
|
||||
}
|
||||
|
||||
export function check(options: any, cb: (error: Error, res: any) => void): any;
|
||||
export function verify(options: VerifyOptions, cb: (error: string, res: any) => void): void;
|
||||
|
||||
export interface Response {
|
||||
start: any;
|
||||
end: any;
|
||||
value: any;
|
||||
certs: any;
|
||||
certsTbs: any;
|
||||
}
|
||||
|
||||
export interface Request {
|
||||
id: Buffer;
|
||||
certID: any;
|
||||
data: any;
|
||||
|
||||
// Just to avoid re-parsing DER
|
||||
cert: any;
|
||||
issuer: any;
|
||||
}
|
||||
|
||||
export interface VerifyOptions {
|
||||
request: Request;
|
||||
response: Buffer;
|
||||
}
|
||||
|
||||
export class utils {
|
||||
public static parseResponse(response: any): Response;
|
||||
public static getAuthorityInfo(cert: DetailedPeerCertificate, ocspMethod: string, cb: (err: string, uri: string) => void): void;
|
||||
public static getResponse(httpOptions: http.RequestOptions | string, data: any, cb: (err: string, raw: Buffer) => void): void;
|
||||
}
|
||||
|
||||
export class request {
|
||||
public static generate(rawCert: Buffer, rawIssuer: Buffer): Request;
|
||||
}
|
|
@ -1799,6 +1799,16 @@
|
|||
"form-data": "^2.5.0"
|
||||
}
|
||||
},
|
||||
"@types/rimraf": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.0.tgz",
|
||||
"integrity": "sha512-7WhJ0MdpFgYQPXlF4Dx+DhgvlPCfz/x5mHaeDQAKhcenvQP1KCpLQ18JklAqeGMYSAT2PxLpzd0g2/HE7fj7hQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/glob": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/stack-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
|
||||
|
@ -2059,11 +2069,21 @@
|
|||
"dev": true
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
|
||||
"integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz",
|
||||
"integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==",
|
||||
"requires": {
|
||||
"es6-promisify": "^5.0.0"
|
||||
"debug": "4"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ajv": {
|
||||
|
@ -2377,6 +2397,51 @@
|
|||
"lodash": "^4.17.14"
|
||||
}
|
||||
},
|
||||
"async-disk-cache": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/async-disk-cache/-/async-disk-cache-2.1.0.tgz",
|
||||
"integrity": "sha512-iH+boep2xivfD9wMaZWkywYIURSmsL96d6MoqrC94BnGSvXE4Quf8hnJiHGFYhw/nLeIa1XyRaf4vvcvkwAefg==",
|
||||
"requires": {
|
||||
"debug": "^4.1.1",
|
||||
"heimdalljs": "^0.2.3",
|
||||
"istextorbinary": "^2.5.1",
|
||||
"mkdirp": "^0.5.0",
|
||||
"rimraf": "^3.0.0",
|
||||
"rsvp": "^4.8.5",
|
||||
"username-sync": "^1.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"async-done": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz",
|
||||
|
@ -2600,8 +2665,7 @@
|
|||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||
},
|
||||
"base": {
|
||||
"version": "0.11.2",
|
||||
|
@ -2685,6 +2749,11 @@
|
|||
"integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
|
||||
"dev": true
|
||||
},
|
||||
"binaryextensions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz",
|
||||
"integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg=="
|
||||
},
|
||||
"bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
|
@ -2710,7 +2779,6 @@
|
|||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
@ -2944,6 +3012,15 @@
|
|||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
||||
|
@ -3225,8 +3302,7 @@
|
|||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||
"dev": true
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"concat-stream": {
|
||||
"version": "1.6.2",
|
||||
|
@ -3273,6 +3349,31 @@
|
|||
"mkdirp": "^0.5.1",
|
||||
"rimraf": "^2.5.4",
|
||||
"run-queue": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"glob": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"copy-descriptor": {
|
||||
|
@ -3724,6 +3825,15 @@
|
|||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"editions": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/editions/-/editions-2.3.1.tgz",
|
||||
"integrity": "sha512-ptGvkwTvGdGfC0hfhKg0MT+TRLRKGtUiWGBInxOm5pz7ssADezahjCUaYuZ8Dr+C05FW0AECIIPt4WBxVINEhA==",
|
||||
"requires": {
|
||||
"errlop": "^2.0.0",
|
||||
"semver": "^6.3.0"
|
||||
}
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
|
||||
|
@ -3783,6 +3893,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"errlop": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/errlop/-/errlop-2.2.0.tgz",
|
||||
"integrity": "sha512-e64Qj9+4aZzjzzFpZC7p5kmm/ccCrbLhAJplhsDXQFs87XTsXwOpH4s1Io2s90Tau/8r2j9f4l/thhDevRjzxw=="
|
||||
},
|
||||
"errno": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
|
||||
|
@ -4456,8 +4571,7 @@
|
|||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"dev": true
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "1.2.13",
|
||||
|
@ -4920,6 +5034,21 @@
|
|||
"minimalistic-assert": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"heimdalljs": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/heimdalljs/-/heimdalljs-0.2.6.tgz",
|
||||
"integrity": "sha512-o9bd30+5vLBvBtzCPwwGqpry2+n0Hi6H1+qwt6y+0kwRHGGF8TFIhJPmnuM0xO97zaKrDZMwO/V56fAnn8m/tA==",
|
||||
"requires": {
|
||||
"rsvp": "~3.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"rsvp": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.2.1.tgz",
|
||||
"integrity": "sha1-B8tKXfJa3Z6Cbrxn3Mn9idsn2Eo="
|
||||
}
|
||||
}
|
||||
},
|
||||
"hmac-drbg": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
||||
|
@ -4985,6 +5114,16 @@
|
|||
"requires": {
|
||||
"agent-base": "^4.3.0",
|
||||
"debug": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"agent-base": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
|
||||
"integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
|
||||
"requires": {
|
||||
"es6-promisify": "^5.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"human-signals": {
|
||||
|
@ -5085,7 +5224,6 @@
|
|||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
|
@ -5480,6 +5618,16 @@
|
|||
"istanbul-lib-report": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"istextorbinary": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-2.6.0.tgz",
|
||||
"integrity": "sha512-+XRlFseT8B3L9KyjxxLjfXSLMuErKDsd8DBNrsaxoViABMEZlOSCstwmw0qpoFX3+U6yWU1yhLudAe6/lETGGA==",
|
||||
"requires": {
|
||||
"binaryextensions": "^2.1.2",
|
||||
"editions": "^2.2.0",
|
||||
"textextensions": "^2.5.0"
|
||||
}
|
||||
},
|
||||
"jest": {
|
||||
"version": "26.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jest/-/jest-26.0.1.tgz",
|
||||
|
@ -9082,7 +9230,6 @@
|
|||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
|
@ -9148,7 +9295,6 @@
|
|||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
},
|
||||
|
@ -9156,8 +9302,7 @@
|
|||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -9173,6 +9318,31 @@
|
|||
"mkdirp": "^0.5.1",
|
||||
"rimraf": "^2.5.4",
|
||||
"run-queue": "^1.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"glob": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
|
@ -9508,11 +9678,60 @@
|
|||
"make-iterator": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"ocsp": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ocsp/-/ocsp-1.2.0.tgz",
|
||||
"integrity": "sha1-RpoXdrRX3uZ+sCAUCMGUa6xAdsw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asn1.js": "^4.8.0",
|
||||
"asn1.js-rfc2560": "^4.0.0",
|
||||
"asn1.js-rfc5280": "^2.0.0",
|
||||
"async": "^1.5.2",
|
||||
"simple-lru-cache": "0.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"asn1.js": {
|
||||
"version": "4.10.1",
|
||||
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
|
||||
"integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bn.js": "^4.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"asn1.js-rfc2560": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asn1.js-rfc2560/-/asn1.js-rfc2560-4.0.6.tgz",
|
||||
"integrity": "sha512-ysf48ni+f/efNPilq4+ApbifUPcSW/xbDeQAh055I+grr2gXgNRQqHew7kkO70WSMQ2tEOURVwsK+dJqUNjIIg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asn1.js-rfc5280": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"asn1.js-rfc5280": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/asn1.js-rfc5280/-/asn1.js-rfc5280-2.0.1.tgz",
|
||||
"integrity": "sha512-1e2ypnvTbYD/GdxWK77tdLBahvo1fZUHlQJqAVUuZWdYj0rdjGcf2CWYUtbsyRYpYUMwMWLZFUtLxog8ZXTrcg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asn1.js": "^4.5.0"
|
||||
}
|
||||
},
|
||||
"async": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
|
@ -9712,8 +9931,7 @@
|
|||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"dev": true
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||
},
|
||||
"path-key": {
|
||||
"version": "2.0.1",
|
||||
|
@ -10273,9 +10491,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
|
@ -10310,8 +10528,7 @@
|
|||
"rsvp": {
|
||||
"version": "4.8.5",
|
||||
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
|
||||
"integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA=="
|
||||
},
|
||||
"run-queue": {
|
||||
"version": "1.0.3",
|
||||
|
@ -10391,8 +10608,7 @@
|
|||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
},
|
||||
"semver-greatest-satisfied-range": {
|
||||
"version": "1.1.0",
|
||||
|
@ -11084,6 +11300,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"textextensions": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.6.0.tgz",
|
||||
"integrity": "sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ=="
|
||||
},
|
||||
"throat": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz",
|
||||
|
@ -11599,6 +11820,11 @@
|
|||
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
|
||||
"dev": true
|
||||
},
|
||||
"username-sync": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/username-sync/-/username-sync-1.0.2.tgz",
|
||||
"integrity": "sha512-ayNkOJdoNSGNDBE46Nkc+l6IXmeugbzahZLSMkwvgRWv5y5ZqNY2IrzcgmkR4z32sj1W3tM3TuTUMqkqBzO+RA=="
|
||||
},
|
||||
"util": {
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
|
||||
|
@ -11947,8 +12173,7 @@
|
|||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"write-file-atomic": {
|
||||
"version": "3.0.3",
|
||||
|
|
14
package.json
14
package.json
|
@ -29,13 +29,16 @@
|
|||
"browser": {
|
||||
"asn1.js-rfc2560": false,
|
||||
"asn1.js-rfc5280": false,
|
||||
"distrib/es2015/external/ocsp/ocsp": false,
|
||||
"distrib/lib/external/ocsp/ocsp": false,
|
||||
"https-proxy-agent": false,
|
||||
"simple-lru-cache": false,
|
||||
"ws": false,
|
||||
"fs": false,
|
||||
"xmlhttprequest-ts": false
|
||||
"xmlhttprequest-ts": false,
|
||||
"async-disk-cache": false,
|
||||
"distrib/es2015/external/ocsp/ocsp": false,
|
||||
"distrib/lib/external/ocsp/ocsp": false,
|
||||
"agent-base": false,
|
||||
"tls": false
|
||||
},
|
||||
"main": "distrib/lib/microsoft.cognitiveservices.speech.sdk.js",
|
||||
"module": "distrib/es2015/microsoft.cognitiveservices.speech.sdk.js",
|
||||
|
@ -51,6 +54,7 @@
|
|||
"@types/jest": "^25.2.2",
|
||||
"@types/node": "^12.12.30",
|
||||
"@types/request": "^2.48.3",
|
||||
"@types/rimraf": "^3.0.0",
|
||||
"@types/ws": "^6.0.4",
|
||||
"asn1.js": "^5.2.0",
|
||||
"dts-bundle-webpack": "^1.0.2",
|
||||
|
@ -62,7 +66,9 @@
|
|||
"gulp-typescript": "^5.0.1",
|
||||
"jest": "^26.0.1",
|
||||
"jest-junit": "^10.0.0",
|
||||
"ocsp": "^1.2.0",
|
||||
"request": "^2.88.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^6.3.0",
|
||||
"source-map-loader": "^0.2.4",
|
||||
"ts-jest": "^25.5.1",
|
||||
|
@ -90,8 +96,10 @@
|
|||
"usePathForSuiteName": "true"
|
||||
},
|
||||
"dependencies": {
|
||||
"agent-base": "^6.0.0",
|
||||
"asn1.js-rfc2560": "^5.0.0",
|
||||
"asn1.js-rfc5280": "^3.0.0",
|
||||
"async-disk-cache": "^2.1.0",
|
||||
"https-proxy-agent": "^3.0.1",
|
||||
"simple-lru-cache": "0.0.2",
|
||||
"ws": "^7.2.0",
|
||||
|
|
|
@ -0,0 +1,370 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import * as http from "http";
|
||||
import * as tls from "tls";
|
||||
import * as url from "url";
|
||||
import * as ocsp from "../../external/ocsp/ocsp";
|
||||
import {
|
||||
Events,
|
||||
OCSPCacheEntryExpiredEvent,
|
||||
OCSPCacheEntryNeedsRefreshEvent,
|
||||
OCSPCacheFetchErrorEvent,
|
||||
OCSPCacheMissEvent,
|
||||
OCSPCacheUpdatehCompleteEvent,
|
||||
OCSPCacheUpdateNeededEvent,
|
||||
OCSPDiskCacheHitEvent,
|
||||
OCSPDiskCacheStoreEvent,
|
||||
OCSPEvent,
|
||||
OCSPMemoryCacheHitEvent,
|
||||
OCSPMemoryCacheStoreEvent,
|
||||
OCSPResponseRetrievedEvent,
|
||||
OCSPStapleReceivedEvent,
|
||||
OCSPVerificationFailedEvent,
|
||||
OCSPWSUpgradeStartedEvent
|
||||
} from "../common/Exports";
|
||||
import { IStringDictionary } from "../common/IDictionary";
|
||||
import { ProxyInfo } from "./ProxyInfo";
|
||||
|
||||
import Agent from "agent-base";
|
||||
|
||||
// @ts-ignore
|
||||
import Cache from "async-disk-cache";
|
||||
import HttpsProxyAgent from "https-proxy-agent";
|
||||
import * as net from "net";
|
||||
|
||||
export class CertCheckAgent {
|
||||
|
||||
// Test hook to enable forcing expiration / refresh to happen.
|
||||
public static testTimeOffset: number;
|
||||
|
||||
// Test hook to disable stapling for cache testing.
|
||||
public static forceDisableOCSPStapling: boolean = false;
|
||||
|
||||
// An in memory cache for recived responses.
|
||||
private static privMemCache: IStringDictionary<Buffer> = {};
|
||||
|
||||
// The on disk cache.
|
||||
private static privDiskCache: Cache;
|
||||
|
||||
private privProxyInfo: ProxyInfo;
|
||||
|
||||
constructor(proxyInfo?: 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) {
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
// Test hook to force the disk cache to be recreated.
|
||||
public static forceReinitDiskCache(): void {
|
||||
CertCheckAgent.privDiskCache = undefined;
|
||||
CertCheckAgent.privMemCache = {};
|
||||
}
|
||||
|
||||
public GetAgent(disableStapling?: boolean): http.Agent {
|
||||
const agent: any = new Agent.Agent(this.CreateConnection);
|
||||
|
||||
if (this.privProxyInfo !== undefined &&
|
||||
this.privProxyInfo.HostName !== undefined &&
|
||||
this.privProxyInfo.Port > 0) {
|
||||
const proxyName: string = "privProxyInfo";
|
||||
agent[proxyName] = this.privProxyInfo;
|
||||
}
|
||||
|
||||
return agent;
|
||||
}
|
||||
|
||||
private static GetProxyAgent(proxyInfo: ProxyInfo): HttpsProxyAgent {
|
||||
const httpProxyOptions: HttpsProxyAgent.HttpsProxyAgentOptions = {
|
||||
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: HttpsProxyAgent = new HttpsProxyAgent(httpProxyOptions);
|
||||
return httpProxyAgent;
|
||||
}
|
||||
|
||||
private static async OCSPCheck(socketPromise: Promise<net.Socket>, proxyInfo: ProxyInfo): Promise<net.Socket> {
|
||||
let ocspRequest: ocsp.Request;
|
||||
let stapling: Buffer;
|
||||
let resolved: boolean = false;
|
||||
|
||||
const socket: net.Socket = await socketPromise;
|
||||
socket.cork();
|
||||
|
||||
const tlsSocket: tls.TLSSocket = socket as tls.TLSSocket;
|
||||
|
||||
return new Promise<net.Socket>((resolve: (value: net.Socket) => void, reject: (error: string | Error) => void) => {
|
||||
socket.on("OCSPResponse", (data: Buffer): void => {
|
||||
if (!!data) {
|
||||
this.onEvent(new OCSPStapleReceivedEvent());
|
||||
stapling = data;
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("error", (error: Error) => {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
socket.destroy();
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
tlsSocket.on("secure", async () => {
|
||||
const peer: tls.DetailedPeerCertificate = tlsSocket.getPeerCertificate(true);
|
||||
try {
|
||||
const issuer: tls.DetailedPeerCertificate = 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: string = ocspRequest.id.toString("hex");
|
||||
|
||||
// Stapled response trumps cached response.
|
||||
if (!stapling) {
|
||||
const cacheEntry: Buffer = 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static GetIssuer(peer: tls.DetailedPeerCertificate): Promise<tls.DetailedPeerCertificate> {
|
||||
if (peer.issuerCertificate) {
|
||||
return Promise.resolve(peer.issuerCertificate);
|
||||
}
|
||||
|
||||
return new Promise<tls.DetailedPeerCertificate>((resolve: (value: tls.DetailedPeerCertificate) => void, reject: (reason: string) => void) => {
|
||||
const ocspAgent: ocsp.Agent = new ocsp.Agent({});
|
||||
ocspAgent.fetchIssuer(peer, null, (error: string, value: tls.DetailedPeerCertificate): void => {
|
||||
if (!!error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static async GetResponseFromCache(signature: string, ocspRequest: ocsp.Request, proxyInfo: ProxyInfo): Promise<Buffer> {
|
||||
let cachedResponse: Buffer = CertCheckAgent.privMemCache[signature];
|
||||
|
||||
if (!!cachedResponse) {
|
||||
this.onEvent(new OCSPMemoryCacheHitEvent(signature));
|
||||
}
|
||||
|
||||
// Do we have a result for this certificate on disk in %TMP%?
|
||||
if (!cachedResponse) {
|
||||
try {
|
||||
const diskCacheResponse: any = await CertCheckAgent.privDiskCache.get(signature);
|
||||
if (!!diskCacheResponse.isCached) {
|
||||
CertCheckAgent.onEvent(new OCSPDiskCacheHitEvent(signature));
|
||||
CertCheckAgent.StoreMemoryCacheEntry(signature, diskCacheResponse.value);
|
||||
cachedResponse = diskCacheResponse.value;
|
||||
}
|
||||
} catch (error) {
|
||||
cachedResponse = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cachedResponse) {
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
try {
|
||||
const cachedOcspResponse: ocsp.Response = ocsp.utils.parseResponse(cachedResponse);
|
||||
const tbsData = cachedOcspResponse.value.tbsResponseData;
|
||||
if (tbsData.responses.length < 1) {
|
||||
this.onEvent(new OCSPCacheFetchErrorEvent(signature, "Not enough data in cached response"));
|
||||
return;
|
||||
}
|
||||
|
||||
const cachedStartTime: number = tbsData.responses[0].thisUpdate;
|
||||
const cachedNextTime: number = tbsData.responses[0].nextUpdate;
|
||||
|
||||
if (cachedNextTime < (Date.now() + this.testTimeOffset - 60000)) {
|
||||
// Cached entry has expired.
|
||||
this.onEvent(new 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: number = Math.min(24 * 60 * 60 * 1000, (cachedNextTime - cachedStartTime) / 2);
|
||||
|
||||
if ((cachedNextTime - (Date.now() + this.testTimeOffset)) < minUpdate) {
|
||||
this.onEvent(new OCSPCacheEntryNeedsRefreshEvent(signature, cachedStartTime, cachedNextTime));
|
||||
this.UpdateCache(ocspRequest, proxyInfo).catch();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.onEvent(new OCSPCacheFetchErrorEvent(signature, error));
|
||||
cachedResponse = null;
|
||||
}
|
||||
if (!cachedResponse) {
|
||||
this.onEvent(new OCSPCacheMissEvent(signature));
|
||||
}
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
private static async VerifyOCSPResponse(cacheValue: Buffer, ocspRequest: ocsp.Request, proxyInfo: ProxyInfo): Promise<void> {
|
||||
let ocspResponse: Buffer = cacheValue;
|
||||
const sig: string = ocspRequest.certID.toString("hex");
|
||||
|
||||
// Do we have a valid response?
|
||||
if (!ocspResponse) {
|
||||
ocspResponse = await CertCheckAgent.GetOCSPResponse(ocspRequest, proxyInfo);
|
||||
}
|
||||
|
||||
return new Promise<void>((resolve: () => void, reject: (error: string | Error) => void) => {
|
||||
ocsp.verify({ request: ocspRequest, response: ocspResponse }, (error: string, result: any): void => {
|
||||
if (!!error) {
|
||||
CertCheckAgent.onEvent(new 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: Error) => {
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
reject(error);
|
||||
} else {
|
||||
if (!cacheValue) {
|
||||
CertCheckAgent.StoreCacheEntry(ocspRequest.id.toString("hex"), ocspResponse);
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static async UpdateCache(req: ocsp.Request, proxyInfo: ProxyInfo): Promise<void> {
|
||||
const signature: string = req.id.toString("hex");
|
||||
this.onEvent(new OCSPCacheUpdateNeededEvent(signature));
|
||||
|
||||
const rawResponse: Buffer = await this.GetOCSPResponse(req, proxyInfo);
|
||||
this.StoreCacheEntry(signature, rawResponse);
|
||||
this.onEvent(new OCSPCacheUpdatehCompleteEvent(req.id.toString("hex")));
|
||||
|
||||
}
|
||||
|
||||
private static StoreCacheEntry(sig: string, rawResponse: Buffer): void {
|
||||
this.StoreMemoryCacheEntry(sig, rawResponse);
|
||||
this.StoreDiskCacheEntry(sig, rawResponse);
|
||||
}
|
||||
|
||||
private static StoreMemoryCacheEntry(sig: string, rawResponse: Buffer): void {
|
||||
this.privMemCache[sig] = rawResponse;
|
||||
this.onEvent(new OCSPMemoryCacheStoreEvent(sig));
|
||||
}
|
||||
|
||||
private static StoreDiskCacheEntry(sig: string, rawResponse: Buffer): void {
|
||||
this.privDiskCache.set(sig, rawResponse).then(() => {
|
||||
this.onEvent(new OCSPDiskCacheStoreEvent(sig));
|
||||
});
|
||||
}
|
||||
|
||||
private static GetOCSPResponse(req: ocsp.Request, proxyInfo: ProxyInfo): Promise<Buffer> {
|
||||
|
||||
const ocspMethod: string = "1.3.6.1.5.5.7.48.1";
|
||||
let options: http.RequestOptions = {};
|
||||
|
||||
if (!!proxyInfo) {
|
||||
const agent: HttpsProxyAgent = CertCheckAgent.GetProxyAgent(proxyInfo);
|
||||
options.agent = agent;
|
||||
}
|
||||
|
||||
return new Promise<Buffer>((resolve: (value: Buffer) => void, reject: (error: string | Error) => void) => {
|
||||
ocsp.utils.getAuthorityInfo(req.cert, ocspMethod, (error: string, uri: string): void => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedUri = url.parse(uri);
|
||||
options = { ...options, ...parsedUri };
|
||||
|
||||
ocsp.utils.getResponse(options, req.data, (error: string, raw: Buffer): void => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
this.onEvent(new OCSPResponseRetrievedEvent(req.certID.toString("hex")));
|
||||
resolve(raw);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static onEvent = (event: OCSPEvent): void => {
|
||||
Events.instance.onEvent(event);
|
||||
}
|
||||
|
||||
private CreateConnection(request: Agent.ClientRequest, options: Agent.RequestOptions): Promise<net.Socket> {
|
||||
const enableOCSP: boolean = (typeof process !== "undefined" && process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "0" && process.env.SPEECH_CONDUCT_OCSP_CHECK !== "0") && options.secureEndpoint;
|
||||
let socketPromise: Promise<net.Socket>;
|
||||
|
||||
options = {
|
||||
...options,
|
||||
...{
|
||||
requestOCSP: !CertCheckAgent.forceDisableOCSPStapling,
|
||||
servername: options.host
|
||||
}
|
||||
};
|
||||
|
||||
if (!!this.privProxyInfo) {
|
||||
const httpProxyAgent: HttpsProxyAgent = CertCheckAgent.GetProxyAgent(this.privProxyInfo);
|
||||
const baseAgent: Agent.Agent = httpProxyAgent as unknown as Agent.Agent;
|
||||
|
||||
socketPromise = new Promise<net.Socket>((resolve: (value: net.Socket) => void, reject: (error: string | Error) => void) => {
|
||||
baseAgent.callback(request, options, (error: Error, socket: net.Socket) => {
|
||||
if (!!error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(socket);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
socketPromise = Promise.resolve(tls.connect(options));
|
||||
}
|
||||
|
||||
if (!!enableOCSP) {
|
||||
return CertCheckAgent.OCSPCheck(socketPromise, this.privProxyInfo);
|
||||
} else {
|
||||
return socketPromise;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,11 +26,8 @@ import {
|
|||
import { ProxyInfo } from "./ProxyInfo";
|
||||
|
||||
// Node.JS specific web socket / browser support.
|
||||
import * as http from "http";
|
||||
import * as HttpsProxyAgent from "https-proxy-agent";
|
||||
import * as tls from "tls";
|
||||
import * as ws from "ws";
|
||||
import * as ocsp from "../../external/ocsp/ocsp";
|
||||
import ws from "ws";
|
||||
import { CertCheckAgent } from "./CertChecks";
|
||||
|
||||
interface ISendItem {
|
||||
Message: ConnectionMessage;
|
||||
|
@ -101,7 +98,6 @@ export class WebsocketMessageAdapter {
|
|||
this.privConnectionState = ConnectionState.Connecting;
|
||||
|
||||
try {
|
||||
const enableOCSP: boolean = (typeof process !== "undefined" && process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "0" && process.env.SPEECH_CONDUCT_OCSP_CHECK !== "0") && !this.privUri.startsWith("ws:");
|
||||
|
||||
if (typeof WebSocket !== "undefined" && !WebsocketMessageAdapter.forceNpmWebSocket) {
|
||||
// Browser handles cert checks.
|
||||
|
@ -109,65 +105,13 @@ export class WebsocketMessageAdapter {
|
|||
|
||||
this.privWebsocketClient = new WebSocket(this.privUri);
|
||||
} else {
|
||||
if (this.proxyInfo !== undefined &&
|
||||
this.proxyInfo.HostName !== undefined &&
|
||||
this.proxyInfo.Port > 0) {
|
||||
const httpProxyOptions: HttpsProxyAgent.HttpsProxyAgentOptions = {
|
||||
host: this.proxyInfo.HostName,
|
||||
port: this.proxyInfo.Port,
|
||||
};
|
||||
const options: ws.ClientOptions = { headers: this.privHeaders };
|
||||
// The ocsp library will handle validation for us and fail the connection if needed.
|
||||
this.privCertificateValidatedDeferral.resolve(true);
|
||||
const checkAgent: CertCheckAgent = new CertCheckAgent(this.proxyInfo);
|
||||
|
||||
if (undefined !== this.proxyInfo.UserName) {
|
||||
httpProxyOptions.headers = {
|
||||
"Proxy-Authentication": "Basic " + new Buffer(this.proxyInfo.UserName + ":" + (this.proxyInfo.Password === undefined) ? "" : this.proxyInfo.Password).toString("base64"),
|
||||
};
|
||||
if (enableOCSP) {
|
||||
httpProxyOptions.headers.requestOCSP = "true";
|
||||
}
|
||||
}
|
||||
|
||||
const httpProxyAgent: HttpsProxyAgent = new HttpsProxyAgent(httpProxyOptions);
|
||||
const httpsOptions: http.RequestOptions = { agent: httpProxyAgent, headers: this.privHeaders };
|
||||
|
||||
this.privWebsocketClient = new ws(this.privUri, httpsOptions as ws.ClientOptions);
|
||||
|
||||
// Register to be notified when WebSocket upgrade happens so we can check the validity of the
|
||||
// Certificate.
|
||||
if (enableOCSP) {
|
||||
this.privWebsocketClient.addListener("upgrade", (e: http.IncomingMessage): void => {
|
||||
const tlsSocket: tls.TLSSocket = e.socket as tls.TLSSocket;
|
||||
const peer: tls.DetailedPeerCertificate = tlsSocket.getPeerCertificate(true);
|
||||
|
||||
// Cork the socket until we know if the cert is good.
|
||||
tlsSocket.cork();
|
||||
|
||||
ocsp.check({
|
||||
cert: peer.raw,
|
||||
httpOptions: httpsOptions,
|
||||
issuer: peer.issuerCertificate.raw,
|
||||
}, (error: Error, res: any): void => {
|
||||
if (error) {
|
||||
this.privCertificateValidatedDeferral.reject(error.message);
|
||||
tlsSocket.destroy(error);
|
||||
} else {
|
||||
this.privCertificateValidatedDeferral.resolve(true);
|
||||
tlsSocket.uncork();
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.privCertificateValidatedDeferral.resolve(true);
|
||||
}
|
||||
|
||||
} else {
|
||||
const options: ws.ClientOptions = { headers: this.privHeaders };
|
||||
// The ocsp library will handle validation for us and fail the connection if needed.
|
||||
this.privCertificateValidatedDeferral.resolve(true);
|
||||
if (enableOCSP) {
|
||||
options.agent = new ocsp.Agent({});
|
||||
}
|
||||
this.privWebsocketClient = new ws(this.privUri, options);
|
||||
}
|
||||
options.agent = checkAgent.GetAgent();
|
||||
this.privWebsocketClient = new ws(this.privUri, options);
|
||||
}
|
||||
|
||||
this.privWebsocketClient.binaryType = "arraybuffer";
|
||||
|
|
|
@ -28,3 +28,4 @@ export * from "./Stream";
|
|||
export { TranslationStatus } from "../common.speech/TranslationStatus";
|
||||
export * from "./ChunkedArrayBufferStream";
|
||||
export * from "./IAudioDestination";
|
||||
export * from "./OCSPEvents";
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { EventType, PlatformEvent } from "./PlatformEvent";
|
||||
|
||||
export class OCSPEvent extends PlatformEvent {
|
||||
private privSignature: string;
|
||||
|
||||
constructor(eventName: string, eventType: EventType, signature: string) {
|
||||
super(eventName, eventType);
|
||||
|
||||
this.privSignature = signature;
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:max-classes-per-file
|
||||
export class OCSPMemoryCacheHitEvent extends OCSPEvent {
|
||||
constructor(signature: string) {
|
||||
super("OCSPMemoryCacheHitEvent", EventType.Debug, signature);
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:max-classes-per-file
|
||||
export class OCSPCacheMissEvent extends OCSPEvent {
|
||||
constructor(signature: string) {
|
||||
super("OCSPCacheMissEvent", EventType.Debug, signature);
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:max-classes-per-file
|
||||
export class OCSPDiskCacheHitEvent extends OCSPEvent {
|
||||
constructor(signature: string) {
|
||||
super("OCSPDiskCacheHitEvent", EventType.Debug, signature);
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:max-classes-per-file
|
||||
export class OCSPCacheUpdateNeededEvent extends OCSPEvent {
|
||||
constructor(signature: string) {
|
||||
super("OCSPCacheUpdateNeededEvent", EventType.Debug, signature);
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:max-classes-per-file
|
||||
export class OCSPMemoryCacheStoreEvent extends OCSPEvent {
|
||||
constructor(signature: string) {
|
||||
super("OCSPMemoryCacheStoreEvent", EventType.Debug, signature);
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:max-classes-per-file
|
||||
export class OCSPDiskCacheStoreEvent extends OCSPEvent {
|
||||
constructor(signature: string) {
|
||||
super("OCSPDiskCacheStoreEvent", EventType.Debug, signature);
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:max-classes-per-file
|
||||
export class OCSPCacheUpdatehCompleteEvent extends OCSPEvent {
|
||||
constructor(signature: string) {
|
||||
super("OCSPCacheUpdatehCompleteEvent", EventType.Debug, signature);
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:max-classes-per-file
|
||||
export class OCSPStapleReceivedEvent extends OCSPEvent {
|
||||
constructor() {
|
||||
super("OCSPStapleReceivedEvent", EventType.Debug, "");
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:max-classes-per-file
|
||||
export class OCSPWSUpgradeStartedEvent extends OCSPEvent {
|
||||
constructor(serialNumber: string) {
|
||||
super("OCSPWSUpgradeStartedEvent", EventType.Debug, serialNumber);
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:max-classes-per-file
|
||||
export class OCSPCacheEntryExpiredEvent extends OCSPEvent {
|
||||
private privExpireTime: number;
|
||||
|
||||
constructor(serialNumber: string, expireTime: number) {
|
||||
super("OCSPCacheEntryExpiredEvent", EventType.Debug, serialNumber);
|
||||
this.privExpireTime = expireTime;
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:max-classes-per-file
|
||||
export class OCSPCacheEntryNeedsRefreshEvent extends OCSPEvent {
|
||||
private privExpireTime: number;
|
||||
private privStartTime: number;
|
||||
|
||||
constructor(serialNumber: string, startTime: number, expireTime: number) {
|
||||
super("OCSPCacheEntryNeedsRefreshEvent", EventType.Debug, serialNumber);
|
||||
this.privExpireTime = expireTime;
|
||||
this.privStartTime = startTime;
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:max-classes-per-file
|
||||
export class OCSPVerificationFailedEvent extends OCSPEvent {
|
||||
private privError: string;
|
||||
|
||||
constructor(serialNumber: string, error: string) {
|
||||
super("OCSPVerificationFailedEvent", EventType.Debug, serialNumber);
|
||||
this.privError = error;
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:max-classes-per-file
|
||||
export class OCSPCacheFetchErrorEvent extends OCSPEvent {
|
||||
private privError: string;
|
||||
|
||||
constructor(serialNumber: string, error: string) {
|
||||
super("OCSPCacheFetchErrorEvent", EventType.Debug, serialNumber);
|
||||
this.privError = error;
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:max-classes-per-file
|
||||
export class OCSPResponseRetrievedEvent extends OCSPEvent {
|
||||
constructor(serialNumber: string) {
|
||||
super("OCSPResponseRetrievedEvent", EventType.Debug, serialNumber);
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ import { WaveFileAudioInput } from "./WaveFileAudioInputStream";
|
|||
import * as fs from "fs";
|
||||
|
||||
import { setTimeout } from "timers";
|
||||
import WaitForCondition from "./Utilities";
|
||||
import { WaitForCondition } from "./Utilities";
|
||||
|
||||
let objsToClose: any[];
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
import {
|
||||
Settings
|
||||
} from "./Settings";
|
||||
import WaitForCondition from "./Utilities";
|
||||
import { WaitForCondition } from "./Utilities";
|
||||
import {
|
||||
WaveFileAudioInput
|
||||
} from "./WaveFileAudioInputStream";
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
EventType,
|
||||
} from "../src/common/Exports";
|
||||
import { Settings } from "./Settings";
|
||||
import WaitForCondition from "./Utilities";
|
||||
import { WaitForCondition } from "./Utilities";
|
||||
import { WaveFileAudioInput } from "./WaveFileAudioInputStream";
|
||||
|
||||
// tslint:disable-next-line:no-console
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from "../src/common/Exports";
|
||||
import { PropertyId, PullAudioOutputStream } from "../src/sdk/Exports";
|
||||
import { Settings } from "./Settings";
|
||||
import WaitForCondition from "./Utilities";
|
||||
import { WaitForCondition } from "./Utilities";
|
||||
import { WaveFileAudioInput } from "./WaveFileAudioInputStream";
|
||||
|
||||
// tslint:disable-next-line:no-console
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Events, EventType } from "../src/common/Exports";
|
|||
|
||||
import { ByteBufferAudioFile } from "./ByteBufferAudioFile";
|
||||
import { Settings } from "./Settings";
|
||||
import { default as WaitForCondition } from "./Utilities";
|
||||
import { WaitForCondition } from "./Utilities";
|
||||
import { WaveFileAudioInput } from "./WaveFileAudioInputStream";
|
||||
|
||||
import { AudioStreamFormatImpl } from "../src/sdk/Audio/AudioStreamFormat";
|
||||
|
|
|
@ -9,7 +9,7 @@ import { WaveFileAudioInput } from "../WaveFileAudioInputStream";
|
|||
|
||||
import * as request from "request";
|
||||
|
||||
import WaitForCondition from "../Utilities";
|
||||
import { WaitForCondition } from "../Utilities";
|
||||
|
||||
let objsToClose: any[];
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { WaveFileAudioInput } from "../WaveFileAudioInputStream";
|
|||
|
||||
import * as request from "request";
|
||||
|
||||
import WaitForCondition from "../Utilities";
|
||||
import { WaitForCondition } from "../Utilities";
|
||||
|
||||
let objsToClose: any[];
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import { Events, EventType, PlatformEvent } from "../../src/common/Exports";
|
|||
import { Settings } from "../Settings";
|
||||
import { WaveFileAudioInput } from "../WaveFileAudioInputStream";
|
||||
|
||||
import WaitForCondition from "../Utilities";
|
||||
import { WaitForCondition } from "../Utilities";
|
||||
|
||||
let objsToClose: any[];
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { WaveFileAudioInput } from "../WaveFileAudioInputStream";
|
|||
|
||||
import * as request from "request";
|
||||
|
||||
import WaitForCondition from "../Utilities";
|
||||
import { WaitForCondition } from "../Utilities";
|
||||
|
||||
let objsToClose: any[];
|
||||
|
||||
|
|
|
@ -0,0 +1,251 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { CertCheckAgent } from "../src/common.browser/CertChecks";
|
||||
import {
|
||||
ConsoleLoggingListener
|
||||
} from "../src/common.browser/Exports";
|
||||
import {
|
||||
Events,
|
||||
EventType,
|
||||
IDetachable,
|
||||
OCSPEvent,
|
||||
PlatformEvent
|
||||
} from "../src/common/Exports";
|
||||
import { Settings } from "./Settings";
|
||||
import { WaitForPromise } from "./Utilities";
|
||||
|
||||
import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import path from "path";
|
||||
import request from "request";
|
||||
import rimraf from "rimraf";
|
||||
|
||||
const origCacehDir: string = process.env.SPEECH_OSCP_CACHE_ROOT;
|
||||
let cacheDir: string;
|
||||
let events: OCSPEvent[];
|
||||
let currentListener: IDetachable;
|
||||
|
||||
beforeAll(() => {
|
||||
// override inputs, if necessary
|
||||
Settings.LoadSettings();
|
||||
Events.instance.attachListener(new ConsoleLoggingListener(EventType.Debug));
|
||||
});
|
||||
|
||||
// Test cases are run linerally, the only other mechanism to demark them in the output is to put a console line in each case and
|
||||
// report the name.
|
||||
beforeEach(() => {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.info("---------------------------------------Starting test case-----------------------------------");
|
||||
// tslint:disable-next-line:no-console
|
||||
console.info("Start Time: " + new Date(Date.now()).toLocaleString());
|
||||
cacheDir = path.join(os.tmpdir(), Math.random().toString(36).substr(2, 15));
|
||||
process.env.SPEECH_OCSP_CACHE_ROOT = cacheDir;
|
||||
fs.mkdirSync(cacheDir);
|
||||
events = [];
|
||||
currentListener = Events.instance.attach((event: PlatformEvent): void => {
|
||||
if (event.name.startsWith("OCSP")) {
|
||||
events.push(event as OCSPEvent);
|
||||
}
|
||||
});
|
||||
CertCheckAgent.forceReinitDiskCache();
|
||||
CertCheckAgent.testTimeOffset = 0;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.info("End Time: " + new Date(Date.now()).toLocaleString());
|
||||
rimraf(cacheDir, (error: Error): void => {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.info("Error " + Error.toString() + " cleaning up.");
|
||||
});
|
||||
currentListener.detach();
|
||||
currentListener = null;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.env.SPEECH_OSCP_CACHE_ROOT = origCacehDir;
|
||||
});
|
||||
|
||||
function findEvent(eventName: string): number {
|
||||
let found: number = 0;
|
||||
events.forEach((event: OCSPEvent, index: number, array: OCSPEvent[]): void => {
|
||||
if (event.name === eventName) {
|
||||
found++;
|
||||
}
|
||||
});
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
function waitForEvents(eventName: string, eventCount: number, rejectMessage?: string, timeoutMS: number = 5000): Promise<void> {
|
||||
return WaitForPromise((): boolean => {
|
||||
return findEvent(eventName) === eventCount;
|
||||
}, rejectMessage === undefined ? eventName : rejectMessage, timeoutMS);
|
||||
}
|
||||
|
||||
function makeRequest(disableOCSPStapling: boolean = true): Promise<void> {
|
||||
return new Promise((resolve: (value: void) => void, reject: (reason: string) => void): void => {
|
||||
const testUrl: string = "https://www.microsoft.com/";
|
||||
|
||||
const agent: CertCheckAgent = new CertCheckAgent();
|
||||
|
||||
const testRequest: request.Request = request({
|
||||
followRedirect: false,
|
||||
url: testUrl
|
||||
}, (error: any, response: request.Response, body: any): void => {
|
||||
if (error !== null) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
|
||||
});
|
||||
CertCheckAgent.forceDisableOCSPStapling = disableOCSPStapling;
|
||||
testRequest.agent = agent.GetAgent();
|
||||
testRequest.end();
|
||||
});
|
||||
}
|
||||
|
||||
test("Test OCSP Revoked", (done: jest.DoneCallback) => {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.info("Name: Test OCSP Revoked");
|
||||
|
||||
const testUrl: string = "https://revoked.badssl.com/";
|
||||
|
||||
const agent: CertCheckAgent = new CertCheckAgent();
|
||||
|
||||
const testRequest: request.Request = request({
|
||||
followRedirect: false,
|
||||
url: testUrl
|
||||
}, (error: any, response: request.Response, body: any): void => {
|
||||
try {
|
||||
expect(error).not.toBeUndefined();
|
||||
expect(error).not.toBeNull();
|
||||
expect(error.toString()).toContain("revoked");
|
||||
done();
|
||||
} catch (ex) {
|
||||
done.fail(ex);
|
||||
}
|
||||
});
|
||||
testRequest.agent = agent.GetAgent();
|
||||
|
||||
testRequest.end();
|
||||
});
|
||||
|
||||
test("Test OCSP Staple", async (done: jest.DoneCallback) => {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.info("Name: Test OCSP Staple");
|
||||
|
||||
await makeRequest(false);
|
||||
await waitForEvents("OCSPStapleReceivedEvent", 1);
|
||||
await waitForEvents("OCSPResponseRetrievedEvent", 0);
|
||||
done();
|
||||
});
|
||||
|
||||
test("Test OCSP Basic", async (done: jest.DoneCallback) => {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.info("Name: Test OCSP Basic");
|
||||
|
||||
await makeRequest();
|
||||
await waitForEvents("OCSPResponseRetrievedEvent", 1);
|
||||
await waitForEvents("OCSPMemoryCacheStoreEvent", 1);
|
||||
await waitForEvents("OCSPDiskCacheStoreEvent", 1);
|
||||
done();
|
||||
});
|
||||
|
||||
test("Test OCSP 2nd request mem cache hit.", async (done: jest.DoneCallback) => {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.info("Name: Test OCSP 2nd request mem cache hit.");
|
||||
|
||||
await makeRequest();
|
||||
await waitForEvents("OCSPResponseRetrievedEvent", 1);
|
||||
await waitForEvents("OCSPMemoryCacheStoreEvent", 1);
|
||||
await waitForEvents("OCSPDiskCacheStoreEvent", 1);
|
||||
|
||||
events = [];
|
||||
|
||||
await makeRequest();
|
||||
await waitForEvents("OCSPResponseRetrievedEvent", 0);
|
||||
await waitForEvents("OCSPMemoryCacheStoreEvent", 0);
|
||||
await waitForEvents("OCSPDiskCacheStoreEvent", 0);
|
||||
await waitForEvents("OCSPDiskCacheHitEvent", 0);
|
||||
await waitForEvents("OCSPMemoryCacheHitEvent", 1);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
test("Test OCSP expirey refreshes.", async (done: jest.DoneCallback) => {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.info("Name: Test OCSP expirey refreshes.");
|
||||
|
||||
await makeRequest();
|
||||
await waitForEvents("OCSPResponseRetrievedEvent", 1);
|
||||
await waitForEvents("OCSPMemoryCacheStoreEvent", 1);
|
||||
await waitForEvents("OCSPDiskCacheStoreEvent", 1);
|
||||
|
||||
events = [];
|
||||
CertCheckAgent.testTimeOffset = 1000 * 60 * 60 * 24 * 7.5;
|
||||
|
||||
await makeRequest();
|
||||
await waitForEvents("OCSPResponseRetrievedEvent", 1);
|
||||
await waitForEvents("OCSPMemoryCacheStoreEvent", 1);
|
||||
await waitForEvents("OCSPDiskCacheStoreEvent", 1);
|
||||
await waitForEvents("OCSPDiskCacheHitEvent", 0);
|
||||
await waitForEvents("OCSPCacheEntryExpiredEvent", 1);
|
||||
done();
|
||||
});
|
||||
|
||||
test("Test OCSP expirey approaching refreshes.", async (done: jest.DoneCallback) => {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.info("Name: Test OCSP expirey approaching refreshes.");
|
||||
|
||||
await makeRequest();
|
||||
await waitForEvents("OCSPResponseRetrievedEvent", 1);
|
||||
await waitForEvents("OCSPMemoryCacheStoreEvent", 1);
|
||||
await waitForEvents("OCSPDiskCacheStoreEvent", 1);
|
||||
|
||||
events = [];
|
||||
CertCheckAgent.testTimeOffset = 1000 * 60 * 60 * 24 * 3.5;
|
||||
|
||||
await makeRequest();
|
||||
await waitForEvents("OCSPResponseRetrievedEvent", 1);
|
||||
await waitForEvents("OCSPMemoryCacheStoreEvent", 1);
|
||||
await waitForEvents("OCSPDiskCacheStoreEvent", 1);
|
||||
await waitForEvents("OCSPCacheUpdateNeededEvent", 1);
|
||||
await waitForEvents("OCSPCacheUpdatehCompleteEvent", 1);
|
||||
done();
|
||||
});
|
||||
|
||||
test("Test OCSP invalid cert refreshes.", async (done: jest.DoneCallback) => {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.info("Name: invalid cert refreshes.");
|
||||
|
||||
await makeRequest();
|
||||
await waitForEvents("OCSPResponseRetrievedEvent", 1);
|
||||
await waitForEvents("OCSPMemoryCacheStoreEvent", 1);
|
||||
await waitForEvents("OCSPDiskCacheStoreEvent", 1);
|
||||
|
||||
events = [];
|
||||
CertCheckAgent.forceReinitDiskCache();
|
||||
|
||||
const dir: string = path.join(cacheDir, "if-you-need-to-delete-this-open-an-issue-async-disk-cache", "microsoft-cognitiveservices-speech-sdk-cache");
|
||||
fs.readdir(dir, (error: NodeJS.ErrnoException, files: string[]): void => {
|
||||
files.forEach((value: string, index: number, array: string[]): void => {
|
||||
const file: string = path.join(dir, value);
|
||||
const content: Buffer = fs.readFileSync(file);
|
||||
content.set([2], 7);
|
||||
fs.writeFileSync(file, content);
|
||||
});
|
||||
});
|
||||
|
||||
await makeRequest();
|
||||
await waitForEvents("OCSPResponseRetrievedEvent", 1);
|
||||
await waitForEvents("OCSPMemoryCacheStoreEvent", 2);
|
||||
await waitForEvents("OCSPDiskCacheStoreEvent", 1);
|
||||
await waitForEvents("OCSPCacheFetchErrorEvent", 1);
|
||||
await waitForEvents("OCSPCacheMissEvent", 1);
|
||||
|
||||
done();
|
||||
|
||||
});
|
|
@ -17,7 +17,7 @@ import * as request from "request";
|
|||
|
||||
import { setTimeout } from "timers";
|
||||
import { ByteBufferAudioFile } from "./ByteBufferAudioFile";
|
||||
import WaitForCondition from "./Utilities";
|
||||
import { WaitForCondition } from "./Utilities";
|
||||
|
||||
import { AudioStreamFormatImpl } from "../src/sdk/Audio/AudioStreamFormat";
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
InvalidOperationError
|
||||
} from "../src/common/Exports";
|
||||
import { Settings } from "./Settings";
|
||||
import WaitForCondition from "./Utilities";
|
||||
import { WaitForCondition } from "./Utilities";
|
||||
|
||||
let objsToClose: any[];
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
import { ByteBufferAudioFile } from "./ByteBufferAudioFile";
|
||||
import { Settings } from "./Settings";
|
||||
import { validateTelemetry } from "./TelemetryUtil";
|
||||
import { default as WaitForCondition } from "./Utilities";
|
||||
import { WaitForCondition } from "./Utilities";
|
||||
import { WaveFileAudioInput } from "./WaveFileAudioInputStream";
|
||||
|
||||
import { AudioStreamFormatImpl } from "../src/sdk/Audio/AudioStreamFormat";
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
|
||||
import { ByteBufferAudioFile } from "./ByteBufferAudioFile";
|
||||
import { Settings } from "./Settings";
|
||||
import { default as WaitForCondition } from "./Utilities";
|
||||
import { WaitForCondition } from "./Utilities";
|
||||
import { WaveFileAudioInput } from "./WaveFileAudioInputStream";
|
||||
|
||||
let objsToClose: any[];
|
||||
|
|
|
@ -1,11 +1,31 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
const WaitForCondition = (condition: () => boolean, after: () => void): void => {
|
||||
|
||||
export function WaitForCondition(condition: () => boolean, after: () => void): void {
|
||||
if (condition() === true) {
|
||||
after();
|
||||
} else {
|
||||
setTimeout(() => WaitForCondition(condition, after), 500);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default WaitForCondition;
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve: (_: void) => void) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export const WaitForPromise = (condition: () => boolean, rejectMessage: string, timeout: number = 60 * 1000): Promise<void> => {
|
||||
return new Promise(async (resolve: (value: void) => void, reject: (reason: string) => void): Promise<void> => {
|
||||
const endTime: number = Date.now() + timeout;
|
||||
|
||||
while (!condition() && Date.now() < endTime) {
|
||||
await sleep(500);
|
||||
}
|
||||
|
||||
if (Date.now() <= endTime) {
|
||||
resolve();
|
||||
} else {
|
||||
reject("Condition timeout: " + rejectMessage);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"noImplicitAny": true,
|
||||
"removeComments": false
|
||||
"removeComments": false,
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче