diff options
Diffstat (limited to 'packages/server')
-rw-r--r-- | packages/server/src/helpers/isCertRevoked.ts | 118 | ||||
-rw-r--r-- | packages/server/src/helpers/validateCertificatePath.ts | 110 |
2 files changed, 119 insertions, 109 deletions
diff --git a/packages/server/src/helpers/isCertRevoked.ts b/packages/server/src/helpers/isCertRevoked.ts new file mode 100644 index 0000000..1885408 --- /dev/null +++ b/packages/server/src/helpers/isCertRevoked.ts @@ -0,0 +1,118 @@ +import { X509 } from 'jsrsasign'; +import fetch from 'node-fetch'; + +import { leafCertToASN1Object, asn1ObjectToJSON, JASN1 } from './asn1Utils'; + +/** + * 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 default async function isCertRevoked(cert: X509): Promise<boolean> { + 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; + } + + const certSerialHex = cert.getSerialNumberHex(); + + // Check to see if we've got cached info for the cert's CA + const certAuthKeyID = cert.getExtAuthorityKeyIdentifier(); + if (certAuthKeyID) { + const cached = cacheRevokedCerts[certAuthKeyID.kid]; + if (cached) { + console.log(`Found cached info for CA ID ${certAuthKeyID.kid}`, 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; + } + } + } + + // Download and read the CRL + const crlCert = new X509(); + try { + const respCRL = await fetch(crlURL[0]); + const dataCRL = await respCRL.text(); + crlCert.readCertPEM(dataCRL); + } catch (err) { + return false; + } + + // Start diving into the CRL's ASN.1 data structure + const crlASN1 = leafCertToASN1Object(Buffer.from(crlCert.hex, 'hex')); + const crlJSON = asn1ObjectToJSON(crlASN1); + + const root0 = (crlJSON.data as JASN1[])[0]; + + if ((root0.data as JASN1[])?.length < 7) { + // CRL is empty + return false; + } + + const newCached: CAAuthorityInfo = { + revokedCerts: [], + nextUpdate: undefined, + }; + + // nextUpdate + const root04 = (root0.data as JASN1[])[4]; + if (root04) { + console.log('nextUpdate:', root04.data); + newCached.nextUpdate = new Date(root04.data as string); + } + + // revokedCertificates + const root05 = (root0.data as JASN1[])[5]; + const revokedCerts = root05.data; + + if (revokedCerts) { + for (const cert of revokedCerts) { + const certSerialData = (cert as JASN1).data; + if (certSerialData) { + const certSerialSequence = (certSerialData[0] as JASN1).data; + if (typeof certSerialSequence === 'string') { + // Grab the value after "\n" in "(115 bit)\n23373519225161898650309958210680307" + const revokedHex = parseInt(certSerialSequence.split('\n')[1], 10).toString(16); + // Push the revoked cert serial hex into the cache + newCached.revokedCerts.push(revokedHex); + + // Check to see if this cert is one of the revoked certificates + console.log(`Checking if cert ${certSerialHex} matches revoked ${revokedHex}`); + if (certSerialHex === revokedHex) { + return true; + } + } + } + } + + // Cache the results + if (certAuthKeyID) { + console.log(`Adding cached info for CA ID ${certAuthKeyID.kid}:`, newCached); + cacheRevokedCerts[certAuthKeyID.kid] = newCached; + } + } + + return false; +} diff --git a/packages/server/src/helpers/validateCertificatePath.ts b/packages/server/src/helpers/validateCertificatePath.ts index 1023ff7..796f8cd 100644 --- a/packages/server/src/helpers/validateCertificatePath.ts +++ b/packages/server/src/helpers/validateCertificatePath.ts @@ -2,9 +2,8 @@ // ASN1HEX exists in the lib, but not typings, I swear // @ts-ignore 2305 import { KJUR, X509, ASN1HEX, zulutodate } from 'jsrsasign'; -import fetch from 'node-fetch'; -import { leafCertToASN1Object, asn1ObjectToJSON, JASN1 } from './asn1Utils'; +import isCertRevoked from './isCertRevoked'; const { crypto } = KJUR; @@ -66,110 +65,3 @@ export default async function validateCertificatePath(certificates: string[]): P return true; } - -/** - * 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 - */ -async function isCertRevoked(cert: X509): Promise<boolean> { - const crlURL = cert.getExtCRLDistributionPointsURI(); - - // If no URL is provided then we have nothing to check - if (!crlURL) { - return false; - } - - const certSerialHex = cert.getSerialNumberHex(); - - // Check to see if we've got cached info for the cert's CA - const certAuthKeyID = cert.getExtAuthorityKeyIdentifier(); - if (certAuthKeyID) { - const cached = cacheRevokedCerts[certAuthKeyID.kid]; - if (cached) { - console.log(`Found cached info for CA ID ${certAuthKeyID}`, 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; - } - } - } - - // Download and read the CRL - const crlCert = new X509(); - try { - const respCRL = await fetch(crlURL[0]); - const dataCRL = await respCRL.text(); - crlCert.readCertPEM(dataCRL); - } catch (err) { - return false; - } - - // Start diving into the CRL's ASN.1 data structure - const crlASN1 = leafCertToASN1Object(Buffer.from(crlCert.hex, 'hex')); - const crlJSON = asn1ObjectToJSON(crlASN1); - - const root0 = (crlJSON.data as JASN1[])[0]; - - if ((root0.data as JASN1[])?.length < 7) { - // CRL is empty - return false; - } - - const newCached: CAAuthorityInfo = { - revokedCerts: [], - nextUpdate: undefined, - }; - - // nextUpdate - const root04 = (root0.data as JASN1[])[4]; - if (root04) { - console.log('nextUpdate:', root04.data); - newCached.nextUpdate = new Date(root04.data as string); - } - - // revokedCertificates - const root05 = (root0.data as JASN1[])[5]; - const revokedCerts = root05.data; - - if (revokedCerts) { - for (const cert of revokedCerts) { - const certSerialData = (cert as JASN1).data; - if (certSerialData) { - const certSerialSequence = (certSerialData[0] as JASN1).data; - if (typeof certSerialSequence === 'string') { - // Grab the value after "\n" in "(115 bit)\n23373519225161898650309958210680307" - const revokedHex = parseInt(certSerialSequence.split('\n')[1], 10).toString(16); - // Push the revoked cert serial hex into the cache - newCached.revokedCerts.push(revokedHex); - - // Check to see if this cert is one of the revoked certificates - console.log(`Checking if cert ${certSerialHex} matches revoked ${revokedHex}`); - if (certSerialHex === revokedHex) { - return true; - } - } - } - } - - // Cache the results - if (certAuthKeyID) { - cacheRevokedCerts[certAuthKeyID.kid] = newCached; - } - } - - return false; -} |