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:
Ryan Hurey 2020-07-02 09:59:35 -07:00 коммит произвёл GitHub
Родитель e8d024cfde
Коммит fc9f3f6d2a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
23 изменённых файлов: 1095 добавлений и 114 удалений

39
external/ocsp/ocsp.d.ts поставляемый
Просмотреть файл

@ -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;
}

281
package-lock.json сгенерированный
Просмотреть файл

@ -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",

Просмотреть файл

@ -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";

126
src/common/OCSPEvents.ts Normal file
Просмотреть файл

@ -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[];

251
tests/OCSPCacheTests.ts Normal file
Просмотреть файл

@ -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
}
}