diff options
author | Matthew Miller <matthew@millerti.me> | 2020-06-24 22:23:03 -0700 |
---|---|---|
committer | Matthew Miller <matthew@millerti.me> | 2020-06-24 22:23:03 -0700 |
commit | 653aad63e8c1e4ee8ee7577e00b9636a4ac3a0dd (patch) | |
tree | 8e91081c2296499061be06f456a22a5715612b62 /packages/server/src | |
parent | 4496371054edbc2709dd0848fcce7aa1e6313242 (diff) |
Add validateCertificatePath helper
Diffstat (limited to 'packages/server/src')
-rw-r--r-- | packages/server/src/helpers/validateCertificatePath.ts | 55 |
1 files changed, 55 insertions, 0 deletions
diff --git a/packages/server/src/helpers/validateCertificatePath.ts b/packages/server/src/helpers/validateCertificatePath.ts new file mode 100644 index 0000000..b1df42e --- /dev/null +++ b/packages/server/src/helpers/validateCertificatePath.ts @@ -0,0 +1,55 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +// ASN1HEX exists in the lib, but not typings, I swear +// @ts-ignore 2305 +import { KJUR, X509, ASN1HEX } from 'jsrsasign'; + +const { crypto } = KJUR; + +/** + * Traverse an array of PEM certificates and ensure they form a proper chain + * @param certificates Typically the result of `x5c.map(convertASN1toPEM)` + */ +export default function validateCertificatePath(certificates: string[]): boolean { + if (new Set(certificates).size !== certificates.length) { + throw new Error('Invalid certificate path: found duplicate certificates'); + } + + // From leaf to root, make sure each cert is issued by the next certificate in the chain + for (let i = 0; i < certificates.length; i += 1) { + const subjectPem = certificates[i]; + + const subjectCert = new X509(); + subjectCert.readCertPEM(subjectPem); + + let issuerPem = ''; + if (i + 1 >= certificates.length) { + issuerPem = subjectPem; + } else { + issuerPem = certificates[i + 1]; + } + + const issuerCert = new X509(); + issuerCert.readCertPEM(issuerPem); + + console.log('x issuer:', subjectCert.getIssuerString()); + console.log('x+1 subject:', issuerCert.getSubjectString()); + + if (subjectCert.getIssuerString() !== issuerCert.getSubjectString()) { + throw new Error('Invalid certificate path: subject issuer did not match issuer subject'); + } + + const subjectCertStruct = ASN1HEX.getTLVbyList(subjectCert.hex, 0, [0]); + const alg = subjectCert.getSignatureAlgorithmField(); + const signatureHex = subjectCert.getSignatureValueHex(); + + const Signature = new crypto.Signature({ alg }); + Signature.init(issuerPem); + Signature.updateHex(subjectCertStruct); + + if (!Signature.verify(signatureHex)) { + throw new Error('Invalid certificate path: invalid signature'); + } + } + + return true; +} |