This commit is contained in:
Glenn Harper 2023-04-10 17:03:55 -04:00 коммит произвёл GitHub
Родитель f37a6d245e
Коммит a79b92f5bd
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
16 изменённых файлов: 125 добавлений и 1849 удалений

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

@ -1,47 +0,0 @@
/** Declaration file generated by dts-gen */
import * as http from "http";
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;
}

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

@ -1,11 +0,0 @@
'use strict';
exports.Cache = require('./ocsp/cache');
exports.Agent = require('./ocsp/agent');
exports.getOCSPURI = require('./ocsp/api').getOCSPURI;
exports.request = require('./ocsp/request');
exports.check = require('./ocsp/check');
exports.verify = require('./ocsp/verify');
exports.utils = require('./ocsp/utils');

160
external/ocsp/ocsp/agent.js поставляемый
Просмотреть файл

@ -1,160 +0,0 @@
'use strict';
var ocsp = require('../ocsp');
var util = require('util');
var http = require('http');
var https = require('https');
var rfc5280 = require('asn1.js-rfc5280');
var SimpleCache = require('simple-lru-cache');
function Agent(options) {
if (!options)
options = {};
https.Agent.call(this, options);
this.caCache = new SimpleCache({ maxSize: options.CACacheSize || 1024 });
}
module.exports = Agent;
util.inherits(Agent, https.Agent);
Agent.prototype.createConnection = function createConnection(port,
host,
options) {
if (port !== null && typeof port === 'object') {
options = port;
port = null;
} else if (host !== null && typeof host === 'object') {
options = host;
host = null;
} else if (options === null || typeof options !== 'object')
options = {};
if (typeof port === 'number')
options.port = port;
if (typeof host === 'string')
options.host = host;
var ocspOptions = util._extend({ requestOCSP: true }, options);
var socket = https.Agent.prototype.createConnection.call(
this, port, host, ocspOptions);
var self = this;
var stapling = null;
socket.on('OCSPResponse', function(data) {
stapling = data;
});
socket.on('secure', function() {
return self.handleOCSPResponse(socket, stapling, function(err) {
if (err)
return socket.destroy(err);
// Time to allow all writes!
socket.uncork();
});
});
// Do not let any writes come through until we will verify OCSP
socket.cork();
return socket;
};
Agent.prototype.handleOCSPResponse = function handleOCSPResponse(socket,
stapling,
cb) {
var cert = socket.ssl.getPeerCertificate(true);
var issuer = cert.issuerCertificate;
cert = cert.raw;
try {
cert = rfc5280.Certificate.decode(cert, 'der');
if (issuer) {
issuer = issuer.raw;
issuer = rfc5280.Certificate.decode(issuer, 'der');
}
} catch (e) {
return cb(e);
}
function onIssuer(err, x509) {
if (err)
return cb(err);
issuer = x509;
if (stapling) {
var req = ocsp.request.generate(cert, issuer);
ocsp.verify({
request: req,
response: stapling
}, cb);
} else {
return ocsp.check({ cert: cert, issuer: issuer }, cb);
}
}
if (issuer)
return onIssuer(null, issuer);
else
return this.fetchIssuer(cert, stapling, onIssuer);
};
Agent.prototype.fetchIssuer = function fetchIssuer(cert, stapling, cb) {
var issuers = ocsp.utils['id-ad-caIssuers'].join('.');
var self = this;
// TODO(indutny): use info from stapling response
ocsp.utils.getAuthorityInfo(cert, issuers, function(err, uri) {
if (err)
return cb(err);
var ca = self.caCache.get(uri);
if (ca)
return cb(null, ca);
var once = false;
function done(err, data) {
if (once)
return;
once = true;
cb(err, data);
}
function onResponse(res) {
if (res.statusCode < 200 || res.statusCode >= 400)
return done(new Error('Failed to fetch CA: ' + res.statusCode));
var chunks = [];
res.on('readable', function() {
var chunk = res.read();
if (!chunk)
return;
chunks.push(chunk);
});
res.on('end', function() {
var cert = Buffer.concat(chunks);
try {
cert = rfc5280.Certificate.decode(cert, 'der');
} catch (e) {
return done(e);
}
self.caCache.set(uri, cert);
done(null, cert);
});
}
http.get(uri)
.on('error', done)
.on('response', onResponse);
});
};

14
external/ocsp/ocsp/api.js поставляемый
Просмотреть файл

@ -1,14 +0,0 @@
'use strict';
var ocsp = require('../ocsp');
var rfc2560 = require('asn1.js-rfc2560');
var rfc5280 = require('asn1.js-rfc5280');
exports.getOCSPURI = function getOCSPURI(rawCert, cb) {
var ocspMethod = rfc2560['id-pkix-ocsp'].join('.');
var cert = ocsp.utils.toDER(rawCert, 'CERTIFICATE');
cert = rfc5280.Certificate.decode(cert, 'der');
ocsp.utils.getAuthorityInfo(cert, ocspMethod, cb);
};

117
external/ocsp/ocsp/cache.js поставляемый
Просмотреть файл

@ -1,117 +0,0 @@
'use strict';
var ocsp = require('../ocsp');
function Cache(options) {
this.options = options || {};
this.cache = {};
// Override methods
if (this.options.probe)
this.probe = this.options.probe;
if (this.options.store)
this.store = this.options.store;
if (this.options.filter)
this.filter = this.options.filter;
}
module.exports = Cache;
Cache.prototype.filter = function filter(url, callback) {
callback(null);
};
Cache.prototype.probe = function probe(id, callback) {
if (this.cache.hasOwnProperty(id))
callback(null, this.cache[id]);
else
callback(null, false);
};
Cache.prototype.store = function store(id, response, maxTime, callback) {
if (this.cache.hasOwnProperty(id))
clearTimeout(this.cache[id].timer);
var self = this;
this.cache[id] = {
response: response,
timer: setTimeout(function() {
delete self.cache[id];
}, maxTime)
};
callback(null, null);
};
Cache.prototype.request = function request(id, data, callback) {
var self = this;
function done(err, response) {
if (callback)
callback(err, response);
callback = null;
}
function onResponse(err, ocsp) {
if (err)
return done(err);
// Respond early
done(null, ocsp);
// Try parsing and caching response
self.getMaxStoreTime(ocsp, function(err, maxTime) {
if (err)
return;
self.store(id, ocsp, maxTime, function() {
// No-op
});
});
}
// Check that url isn't blacklisted
this.filter(data.url, function(err) {
if (err)
return done(err, null);
ocsp.utils.getResponse(data.url, data.ocsp, onResponse);
});
};
Cache.prototype.getMaxStoreTime = function getMaxStoreTime(response, callback) {
var basic;
try {
basic = ocsp.utils.parseResponse(response).value;
} catch (e) {
return callback(e);
}
// Not enough responses
if (basic.tbsResponseData.responses.length === 0)
return callback(new Error('No OCSP responses'));
var responses = basic.tbsResponseData.responses;
// Every response should be positive
var good = responses.every(function(response) {
return response.certStatus.type === 'good';
});
// No good - no cache
if (!good)
return callback(new Error('Some OCSP responses are not good'));
// Find minimum nextUpdate time
var nextUpdate = 0;
for (var i = 0; i < responses.length; i++) {
var response = responses[i];
var responseNext = response.nextUpdate;
if (!responseNext)
continue;
if (nextUpdate === 0 || nextUpdate > responseNext)
nextUpdate = responseNext;
}
return callback(null, Math.max(0, nextUpdate - new Date()));
};

56
external/ocsp/ocsp/check.js поставляемый
Просмотреть файл

@ -1,56 +0,0 @@
'use strict';
var ocsp = require('../ocsp');
var util = require('util');
var url = require('url');
var rfc2560 = require('asn1.js-rfc2560');
module.exports = function check(options, cb) {
var sync = true;
var req;
function done(err, data) {
if (sync) {
sync = false;
process.nextTick(function() {
cb(err, data);
});
return;
}
cb(err, data);
}
try {
req = ocsp.request.generate(options.cert, options.issuer);
} catch (e) {
return done(e);
}
var ocspMethod = rfc2560['id-pkix-ocsp'].join('.');
ocsp.utils.getAuthorityInfo(req.cert, ocspMethod, function(err, uri) {
if (err)
return done(err);
var httpOptions;
if (options.httpOptions !== undefined) {
httpOptions = util._extend(options.httpOptions, url.parse(uri));
} else {
httpOptions = uri;
}
ocsp.utils.getResponse(httpOptions, req.data, function(err, raw) {
if (err)
return done(err);
ocsp.verify({
request: req,
response: raw
}, done);
});
});
sync = false;
};

69
external/ocsp/ocsp/request.js поставляемый
Просмотреть файл

@ -1,69 +0,0 @@
'use strict';
var ocsp = require('../ocsp');
var crypto = require('crypto');
var rfc2560 = require('asn1.js-rfc2560');
var rfc5280 = require('asn1.js-rfc5280');
function sha1(data) {
return crypto.createHash('sha1').update(data).digest();
}
exports.generate = function generate(rawCert, rawIssuer) {
var cert;
if (rawCert.tbsCertificate) {
cert = rawCert;
} else {
cert = rfc5280.Certificate.decode(
ocsp.utils.toDER(rawCert, 'CERTIFICATE'),
'der');
}
var issuer;
if (rawIssuer.tbsCertificate) {
issuer = rawIssuer;
} else {
issuer = rfc5280.Certificate.decode(
ocsp.utils.toDER(rawIssuer, 'CERTIFICATE'),
'der');
}
var tbsCert = cert.tbsCertificate;
var tbsIssuer = issuer.tbsCertificate;
var certID = {
hashAlgorithm: {
// algorithm: [ 2, 16, 840, 1, 101, 3, 4, 2, 1 ] // sha256
algorithm: [ 1, 3, 14, 3, 2, 26 ] // sha1
},
issuerNameHash: sha1(rfc5280.Name.encode(tbsCert.issuer, 'der')),
issuerKeyHash: sha1(
tbsIssuer.subjectPublicKeyInfo.subjectPublicKey.data),
serialNumber: tbsCert.serialNumber
};
var tbs = {
version: 'v1',
requestList: [ {
reqCert: certID
} ],
requestExtensions: [ {
extnID: rfc2560['id-pkix-ocsp-nonce'],
critical: false,
extnValue: rfc2560.Nonce.encode(crypto.randomBytes(16), 'der')
} ]
};
var req = {
tbsRequest: tbs
};
return {
id: sha1(rfc2560.CertID.encode(certID, 'der')),
certID: certID,
data: rfc2560.OCSPRequest.encode(req, 'der'),
// Just to avoid re-parsing DER
cert: cert,
issuer: issuer
};
};

210
external/ocsp/ocsp/utils.js поставляемый
Просмотреть файл

@ -1,210 +0,0 @@
'use strict';
var http = require('http');
var util = require('util');
var url = require('url');
var asn1 = require('asn1.js');
var rfc2560 = require('asn1.js-rfc2560');
exports['id-ad-caIssuers'] = [ 1, 3, 6, 1, 5, 5, 7, 48, 2 ];
exports['id-kp-OCSPSigning'] = [ 1, 3, 6, 1, 5, 5, 7, 3, 9 ];
exports.getResponse = function getResponse(uri, req, cb) {
if (typeof uri !== 'object') {
uri = url.parse(uri);
}
var options = util._extend({
method: 'POST',
headers: {
'Content-Type': 'application/ocsp-request',
'Content-Length': req.length
}
}, uri);
function done(err, response) {
if (cb)
cb(err, response);
cb = null;
}
function onResponse(response) {
if (response.statusCode < 200 || response.statusCode >= 400) {
return done(
new Error('Failed to obtain OCSP response: ' + response.statusCode));
}
var chunks = [];
response.on('readable', function() {
var chunk = response.read();
if (!chunk)
return;
chunks.push(chunk);
});
response.on('end', function() {
var ocsp = Buffer.concat(chunks);
done(null, ocsp);
});
}
http.request(options, onResponse)
.on('error', done)
.end(req);
};
exports.parseResponse = function parseResponse(raw) {
var body = { start: 0, end: raw.length };
var response = rfc2560.OCSPResponse.decode(raw, 'der', {
track: function(key, start, end, type) {
if (type !== 'content' || key !== 'responseBytes/response')
return;
body.start = start;
body.end = end;
}
});
var status = response.responseStatus;
if (status !== 'successful')
throw new Error('Bad OCSP response status: ' + status);
// Unknown response type
var responseType = response.responseBytes.responseType;
if (responseType !== 'id-pkix-ocsp-basic')
throw new Error('Unknown OCSP response type: ' + responseType);
var bytes = response.responseBytes.response;
var tbs = { start: body.start, end: body.end };
var certsTbs = [];
var basic = rfc2560.BasicOCSPResponse.decode(bytes, 'der', {
track: function(key, start, end, type) {
if (type !== 'tagged')
return;
if (key === 'tbsResponseData') {
tbs.start = body.start + start;
tbs.end = body.start + end;
} else if (key === 'certs/tbsCertificate') {
certsTbs.push({ start: body.start + start, end: body.start + end });
}
}
});
var OCSPSigning = exports['id-kp-OCSPSigning'].join('.');
var certs = (basic.certs || []).filter(function(cert) {
return cert.tbsCertificate.extensions.some(function(ext) {
if (ext.extnID !== 'extendedKeyUsage')
return false;
return ext.extnValue.some(function(value) {
return value.join('.') === OCSPSigning;
});
});
});
return {
start: tbs.start,
end: tbs.end,
value: basic,
certs: certs,
certsTbs: certsTbs
};
};
exports.digest = {
'1.3.14.3.2.26': 'sha1',
'2.16.840.1.101.3.4.2.1': 'sha256'
};
exports.digestRev = {
sha1: '1.3.14.3.2.26',
sha256: '2.16.840.1.101.3.4.2.1'
};
exports.sign = {
'1.2.840.113549.1.1.5': 'sha1WithRSAEncryption',
'1.2.840.113549.1.1.11': 'sha256WithRSAEncryption',
'1.2.840.113549.1.1.12': 'sha384WithRSAEncryption',
'1.2.840.113549.1.1.13': 'sha512WithRSAEncryption'
};
exports.signRev = {
sha1WithRSAEncryption: [ 1, 2, 840, 113549, 1, 1, 5 ],
sha256WithRSAEncryption: [ 1, 2, 840, 113549, 1, 1, 11 ],
sha384WithRSAEncryption: [ 1, 2, 840, 113549, 1, 1, 12 ],
sha512WithRSAEncryption: [ 1, 2, 840, 113549, 1, 1, 13 ]
};
exports.toPEM = function toPEM(buf, label) {
var p = buf.toString('base64');
var out = [ '-----BEGIN ' + label + '-----' ];
for (var i = 0; i < p.length; i += 64)
out.push(p.slice(i, i + 64));
out.push('-----END ' + label + '-----');
return out.join('\n');
};
exports.toDER = function toDER(raw, what) {
var der = raw.toString().match(new RegExp(
'-----BEGIN ' + what + '-----([^-]*)-----END ' + what + '-----'));
if (der)
der = new Buffer(der[1].replace(/[\r\n]/g, ''), 'base64');
else if (typeof raw === 'string')
der = new Buffer(raw);
else
der = raw;
return der;
};
exports.getAuthorityInfo = function getAuthorityInfo(cert, key, done) {
var exts = cert.tbsCertificate.extensions;
if (!exts)
exts = [];
var infoAccess = exts.filter(function(ext) {
return ext.extnID === 'authorityInformationAccess';
});
if (infoAccess.length === 0)
return done(new Error('AuthorityInfoAccess not found in extensions'));
var res = null;
var found = infoAccess.some(function(info) {
var ext = info.extnValue;
return ext.some(function(ad) {
if (ad.accessMethod.join('.') !== key)
return false;
var loc = ad.accessLocation;
if (loc.type !== 'uniformResourceIdentifier')
return false;
res = loc.value + '';
return true;
});
});
if (!found)
return done(new Error(key + ' not found in AuthorityInfoAccess'));
return done(null, res);
};
var RSAPrivateKey = asn1.define('RSAPrivateKey', function() {
this.seq().obj(
this.key('version').int(),
this.key('modulus').int(),
this.key('publicExponent').int(),
this.key('privateExponent').int(),
this.key('prime1').int(),
this.key('prime2').int(),
this.key('exponent1').int(),
this.key('exponent2').int(),
this.key('coefficient').int()
);
});
exports.RSAPrivateKey = RSAPrivateKey;

118
external/ocsp/ocsp/verify.js поставляемый
Просмотреть файл

@ -1,118 +0,0 @@
'use strict';
var ocsp = require('../ocsp');
var rfc5280 = require('asn1.js-rfc5280');
var crypto = require('crypto');
// TODO(indutny): verify issuer, etc...
function findResponder(issuer, certs, raws) {
var issuerKey = issuer.tbsCertificate.subjectPublicKeyInfo;
issuerKey = ocsp.utils.toPEM(
rfc5280.SubjectPublicKeyInfo.encode(issuerKey, 'der'), 'PUBLIC KEY');
for (var i = 0; i < certs.length; i++) {
var cert = certs[i];
var signAlg = ocsp.utils.sign[cert.signatureAlgorithm.algorithm.join('.')];
if (!signAlg) {
throw new Error('Unknown signature algorithm ' +
cert.signatureAlgorithm.algorithm);
}
var verify = crypto.createVerify(signAlg);
verify.update(raws[i]);
if (!verify.verify(issuerKey, cert.signature.data))
throw new Error('Invalid signature');
var certKey = cert.tbsCertificate.subjectPublicKeyInfo;
certKey = ocsp.utils.toPEM(
rfc5280.SubjectPublicKeyInfo.encode(certKey, 'der'), 'PUBLIC KEY');
return certKey;
}
return issuerKey;
}
module.exports = function verify(options, cb) {
var req = options.request;
var issuer;
var res;
function done(err) {
process.nextTick(function() {
cb(err, res && res.certStatus);
});
}
try {
issuer = req.issuer ||
rfc5280.Certificate.decode(
ocsp.utils.toDER(options.issuer, 'CERTIFICATE'), 'der');
res = ocsp.utils.parseResponse(options.response);
} catch (e) {
return done(e);
}
var rawTBS = options.response.slice(res.start, res.end);
var certs = res.certs;
var raws = res.certsTbs.map(function(tbs) {
return options.response.slice(tbs.start, tbs.end);
});
res = res.value;
// Verify signature using CAs Public Key
var signAlg = ocsp.utils.sign[res.signatureAlgorithm.algorithm.join('.')];
if (!signAlg) {
done(new Error('Unknown signature algorithm ' +
res.signatureAlgorithm.algorithm));
return;
}
var responderKey = findResponder(issuer, certs, raws);
var verify = crypto.createVerify(signAlg);
var tbs = res.tbsResponseData;
var signature = res.signature.data;
verify.update(rawTBS);
if (!verify.verify(responderKey, signature))
return done(new Error('Invalid signature'));
if (tbs.responses.length < 1)
return done(new Error('Expected at least one response'));
var res = tbs.responses[0];
// Verify CertID
// XXX(indutny): verify parameters
if (res.certId.hashAlgorithm.algorithm.join('.') !==
req.certID.hashAlgorithm.algorithm.join('.')) {
return done(new Error('Hash algorithm mismatch'));
}
if (res.certId.issuerNameHash.toString('hex') !==
req.certID.issuerNameHash.toString('hex')) {
return done(new Error('Issuer name hash mismatch'));
}
if (res.certId.issuerKeyHash.toString('hex') !==
req.certID.issuerKeyHash.toString('hex')) {
return done(new Error('Issuer key hash mismatch'));
}
if (res.certId.serialNumber.cmp(req.certID.serialNumber) !== 0)
return done(new Error('Serial number mismatch'));
if (res.certStatus.type !== 'good') {
return done(new Error('OCSP Status: ' + res.certStatus.type));
}
var now = +new Date();
var nudge = options.nudge || 60000;
if (res.thisUpdate - nudge > now || res.nextUpdate + nudge < now)
return done(new Error('OCSP Response expired'));
return done(null);
};

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

@ -30,9 +30,6 @@
.pipe(tsProject())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('distrib/lib'));
}, function () {
return gulp.src('./external/**/*')
.pipe(gulp.dest('./distrib/lib/external/'));
}));
gulp.task('build2015', gulp.series(function build() {
@ -50,9 +47,6 @@
.pipe(tsProject2015())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('distrib/es2015'));
}, function () {
return gulp.src('./external/**/*')
.pipe(gulp.dest('./distrib/es2015/external/'));
}));
gulp.task('bundle', gulp.series('build', function bundle() {

596
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -33,9 +33,6 @@
"simple-lru-cache": false,
"ws": false,
"fs": false,
"async-disk-cache": false,
"distrib/es2015/external/ocsp/ocsp": false,
"distrib/lib/external/ocsp/ocsp": false,
"agent-base": false,
"tls": false,
"net": false
@ -106,12 +103,8 @@
},
"dependencies": {
"agent-base": "^6.0.1",
"asn1.js-rfc2560": "^5.0.1",
"asn1.js-rfc5280": "^3.0.0",
"async-disk-cache": "^2.1.0",
"bent": "^7.3.12",
"https-proxy-agent": "^4.0.0",
"simple-lru-cache": "0.0.2",
"uuid": "^9.0.0",
"ws": "^7.5.6"
},

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

@ -4,48 +4,14 @@
import * as http from "http";
import * as tls from "tls";
import * as ocsp from "../../external/ocsp/ocsp";
import {
Events,
OCSPCacheEntryExpiredEvent,
OCSPCacheEntryNeedsRefreshEvent,
OCSPCacheFetchErrorEvent,
OCSPCacheHitEvent,
OCSPCacheMissEvent,
OCSPCacheUpdateCompleteEvent,
OCSPCacheUpdateNeededEvent,
OCSPDiskCacheHitEvent,
OCSPDiskCacheStoreEvent,
OCSPEvent,
OCSPMemoryCacheHitEvent,
OCSPMemoryCacheStoreEvent,
OCSPResponseRetrievedEvent,
OCSPStapleReceivedEvent,
OCSPVerificationFailedEvent,
} from "../common/Exports";
import { IStringDictionary } from "../common/IDictionary";
import { ProxyInfo } from "./ProxyInfo";
import Agent from "agent-base";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import Cache from "async-disk-cache";
import HttpsProxyAgent from "https-proxy-agent";
import * as net from "net";
import { OCSPCacheUpdateErrorEvent } from "../common/OCSPEvents";
interface tbsUpdateResponse {
thisUpdate: number;
nextUpdate: number;
}
interface tbsResponse {
tbsResponseData: {
responses: tbsUpdateResponse[];
};
}
export class CertCheckAgent {
@ -55,34 +21,16 @@ export class CertCheckAgent {
// 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;
public 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) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
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 = {};
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public GetAgent(disableStapling?: boolean): http.Agent {
public GetAgent(): http.Agent {
// eslint-disable-next-line @typescript-eslint/unbound-method
const agent: any = new Agent.Agent(this.CreateConnection);
@ -117,250 +65,7 @@ export class CertCheckAgent {
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): void => {
socket.on("OCSPResponse", (data: Buffer): void => {
if (!!data) {
this.onEvent(new OCSPStapleReceivedEvent());
stapling = data;
}
});
socket.on("error", (error: Error): void => {
if (!resolved) {
resolved = true;
socket.destroy();
reject(error);
}
});
// eslint-disable-next-line @typescript-eslint/no-misused-promises, @typescript-eslint/explicit-function-return-type
tlsSocket.on("secure", async () => {
const peer: 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 as string);
}
});
});
}
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): 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 {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
const diskCacheResponse: { value: Buffer; isCached?: any } = await CertCheckAgent.privDiskCache.get(signature) as { value: Buffer; isCached?: any };
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 responseValue: tbsResponse = cachedOcspResponse.value as tbsResponse;
const tbsData: { responses: tbsUpdateResponse[] } = responseValue.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((error: string): void => {
// Well, not much we can do here.
this.onEvent(new OCSPCacheUpdateErrorEvent(signature, error.toString()));
});
} else {
this.onEvent(new OCSPCacheHitEvent(signature, cachedStartTime, cachedNextTime));
}
}
} catch (error) {
this.onEvent(new OCSPCacheFetchErrorEvent(signature, error as string));
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;
// 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): void => {
ocsp.verify({ request: ocspRequest, response: ocspResponse }, (error: string): 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((): void => {
resolve();
}, (error: Error): void => {
reject(error);
});
} else {
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 OCSPCacheUpdateCompleteEvent(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 {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
this.privDiskCache.set(sig, rawResponse).then((): void => {
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): void => {
ocsp.utils.getAuthorityInfo(req.cert as tls.DetailedPeerCertificate, ocspMethod, (error: string, uri: string): void => {
if (error) {
reject(error);
return;
}
const url = new URL(uri);
options = { ...options, host: url.host, protocol: url.protocol, port: url.port, path: url.pathname, hostname: url.host };
ocsp.utils.getResponse(options, req.data, (error: string, raw: Buffer): void => {
if (error) {
reject(error);
return;
}
const certID: Buffer = req.certID as Buffer;
this.onEvent(new OCSPResponseRetrievedEvent(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 = {
@ -392,10 +97,6 @@ export class CertCheckAgent {
}
}
if (!!enableOCSP) {
return CertCheckAgent.OCSPCheck(socketPromise, this.privProxyInfo);
} else {
return socketPromise;
}
return socketPromise;
}
}

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

@ -118,7 +118,6 @@ export class WebsocketMessageAdapter {
const checkAgent: CertCheckAgent = new CertCheckAgent(this.proxyInfo);
options.agent = checkAgent.GetAgent();
// Workaround for https://github.com/microsoft/cognitive-services-speech-sdk-js/issues/465
// Which is root caused by https://github.com/TooTallNate/node-agent-base/issues/61
const uri = new URL(this.privUri);

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

@ -1,258 +0,0 @@
// 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 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));
});
beforeEach(() => {
// tslint:disable-next-line:no-console
console.info("------------------Starting test case: " + expect.getState().currentTestName + "-------------------------");
// 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;
});
jest.retryTimes(Settings.RetryCount);
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().catch();
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);
}
const makeRequest = (disableOCSPStapling: boolean = true): Promise<void> => {
return new Promise(async (resolve: (value: void) => void, reject: (reason: string) => void): Promise<void> => {
const testUrl: string = "https://www.microsoft.com/";
const agent: CertCheckAgent = new CertCheckAgent();
CertCheckAgent.forceDisableOCSPStapling = disableOCSPStapling;
// TODO: if we re-enable this test, make it work without got (find another http request lib
// that replaces the user agent)
const { statusCode } = await got(testUrl, {
agent: { http: agent.GetAgent() },
followRedirect: false,
});
try {
if (statusCode !== 200) {
reject(`error: statusCode ${statusCode} received`);
} else {
resolve();
}
} catch (error) {
reject(error);
}
});
}
// https://github.com/chromium/badssl.com/issues/477
test.skip("Test OCSP Revoked", async (done: jest.DoneCallback): Promise<void> => {
// tslint:disable-next-line:no-console
console.info("Name: Test OCSP Revoked");
const testUrl: string = "https://revoked.badssl.com/";
const agent: CertCheckAgent = new CertCheckAgent();
try {
// TODO: if we re-enable this test, make it work without got (find another http request lib
// that replaces the user agent)
await got(testUrl, {
agent: { http: agent.GetAgent() },
followRedirect: false,
});
} catch (error) {
try {
expect(error).not.toBeUndefined();
expect(error).not.toBeNull();
expect(error.toString()).toContain("revoked");
done();
} catch (ex) {
done(ex);
}
}
});
*/
test.skip("Test OCSP Staple", async (done: jest.DoneCallback) => {
// eslint-disable-next-line no-console
console.info("Name: Test OCSP Staple");
// await makeRequest(false);
// await waitForEvents("OCSPStapleReceivedEvent", 1);
// await waitForEvents("OCSPResponseRetrievedEvent", 0);
done();
});
/*
test.skip("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.skip("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.skip("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.skip("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("OCSPCacheUpdateCompleteEvent", 1);
done();
});
test.skip("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();
});
*/

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

@ -50,7 +50,6 @@ import { closeAsyncObjects, RepeatingPullStream, WaitForCondition } from "./Util
import { AudioStreamFormatImpl } from "../src/sdk/Audio/AudioStreamFormat";
import { Console } from "console";
import { utils } from "../external/ocsp/ocsp";
import { PullAudioInputStream } from "../microsoft.cognitiveservices.speech.sdk";
const FIRST_EVENT_ID: number = 1;