blob: cc8c3f1167762cc30a7391caf0c1b42e4ed8918d (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
import { X509 } from 'jsrsasign';
import fetch from 'node-fetch';
import { AsnParser } from '@peculiar/asn1-schema';
import { CertificateList } from '@peculiar/asn1-x509';
import { convertCertBufferToPEM } from './convertCertBufferToPEM';
/**
* A cache of revoked cert serial numbers by Authority Key ID
*/
type CAAuthorityInfo = {
// A list of certificates serial numbers in hex format
revokedCerts: string[];
// An optional date by which an update should be published
nextUpdate?: Date;
};
const cacheRevokedCerts: { [certAuthorityKeyID: string]: CAAuthorityInfo } = {};
/**
* A method to pull a CRL from a certificate and compare its serial number to the list of revoked
* certificate serial numbers within the CRL.
*
* CRL certificate structure referenced from https://tools.ietf.org/html/rfc5280#page-117
*/
export async function isCertRevoked(cert: X509): Promise<boolean> {
const certSerialHex = cert.getSerialNumberHex();
// Check to see if we've got cached info for the cert's CA
let certAuthKeyID: { kid: { hex: string } } | null = null;
try {
certAuthKeyID = cert.getExtAuthorityKeyIdentifier() as { kid: { hex: string } } | null;
} catch (err) {
return false;
}
if (certAuthKeyID) {
const cached = cacheRevokedCerts[certAuthKeyID.kid.hex];
if (cached) {
const now = new Date();
// If there's a nextUpdate then make sure we're before it
if (!cached.nextUpdate || cached.nextUpdate > now) {
return cached.revokedCerts.indexOf(certSerialHex) >= 0;
}
}
}
let crlURL = undefined;
try {
crlURL = cert.getExtCRLDistributionPointsURI();
} catch (err) {
// Cert probably didn't include any CDP URIs
return false;
}
// If no URL is provided then we have nothing to check
if (!crlURL) {
return false;
}
// Download and read the CRL
const crlCert = new X509();
try {
const respCRL = await fetch(crlURL[0]);
const dataCRL = await respCRL.buffer();
const dataPEM = convertCertBufferToPEM(dataCRL);
crlCert.readCertPEM(dataPEM);
} catch (err) {
return false;
}
const data = AsnParser.parse(Buffer.from(crlCert.hex, 'hex'), CertificateList);
const newCached: CAAuthorityInfo = {
revokedCerts: [],
nextUpdate: undefined,
};
// nextUpdate
if (data.tbsCertList.nextUpdate) {
newCached.nextUpdate = data.tbsCertList.nextUpdate.getTime();
}
// revokedCertificates
const revokedCerts = data.tbsCertList.revokedCertificates;
if (revokedCerts) {
for (const cert of revokedCerts) {
const revokedHex = Buffer.from(cert.userCertificate).toString('hex');
newCached.revokedCerts.push(revokedHex);
}
// Cache the results
if (certAuthKeyID) {
cacheRevokedCerts[certAuthKeyID.kid.hex] = newCached;
}
return newCached.revokedCerts.indexOf(certSerialHex) >= 0;
}
return false;
}
|