diff --git a/udap-common.js b/udap-common.js index 230a092..cb10c69 100644 --- a/udap-common.js +++ b/udap-common.js @@ -252,16 +252,6 @@ function getCertsAndPrivKeysFromBinary(pkcs12String, password) { //This method will take the certificate used to sign the JWT, and then compare it with the community cert+chain to ensure it's valid. async function validateCertWithCrlAndCertChain(udapJwtCertObject, caTrustAnchorObject) { - // Validate cert is not on CRL - try { - await validateCrl(udapJwtCertObject, caTrustAnchorObject) - } - catch (e) { - console.error("cert Expiration/Revocation Exception:") - console.error(e) - throw e - } - //Validate cert is part of our trust community. try { await validateCertChain(udapJwtCertObject, caTrustAnchorObject) @@ -275,7 +265,7 @@ async function validateCertWithCrlAndCertChain(udapJwtCertObject, caTrustAnchorO return true } -async function validateCrl(jwtCertObject, caTrustAnchorObject) { +async function validateCrl(certObject, parentCertObject) { const distributionPoints = [] //Internal method to deal with the different things we can see in the cRLDistributionPoints extension. @@ -293,7 +283,17 @@ async function validateCrl(jwtCertObject, caTrustAnchorObject) { } } - const ext = caTrustAnchorObject.getExtension('cRLDistributionPoints') + const ext = certObject.getExtension('cRLDistributionPoints') + //Skip CRL validation if there's none called out on the cert. + if(!ext) { + console.warn("A certificate was found in the chain that does not have a CRL. This may introduce security risks.") + return + } + + if(!parentCertObject) { + console.warn("No CRL issuer was provided - unable to verify proper issuance of the CRL. This may introduce security risks.") + } + getDistributionPoints(asn1.fromDer(ext.value)) console.debug("Distribution Points: ") console.debug(distributionPoints) @@ -315,12 +315,40 @@ async function validateCrl(jwtCertObject, caTrustAnchorObject) { const crl = new pkijs.CertificateRevocationList({ schema: asn1crl.result }) - + /* TODO: Get this working- right now we're not validating the CRL itself. + if(parentCertObject) { + console.log("Checking the signature of the CRL...") + console.log("Issuer to validate against:") + console.log(JSON.stringify(parentCertObject)) + console.log("CRL") + console.log(JSON.stringify(crl)) + + + var parentCertAsn1 = pki.certificateToAsn1(parentCertObject) + var parentCertDer = asn1.toDer(parentCertAsn1) + var parentCertBER = new Uint8Array(parentCertDer).buffer + var asn1jsParentCert = asn1js.fromBER(parentCertBER) + + var pkijsCert = new pkijs.Certificate({ schema: asn1jsParentCert.result }); + console.log(JSON.stringify(pkijsCert)) + + pkijs.setEngine("NodeJS", new pkijs.CryptoEngine({ crypto: new Crypto() })); + + const valid = await crl.verify( + { + "issuerCertificate": pkijsCert + } + ) + if(!valid) { + throw new Error("The signature validation of the CRL failed!") + } + } + */ for (let index in crl.revokedCertificates) { var revokedCertificate = crl.revokedCertificates[index] var revCertSerial = pvutils.bufferToHexCodes(revokedCertificate.userCertificate.valueBlock.valueHex) console.debug("Cert Serial number: " + revCertSerial) - if (jwtCertObject.serialNumber.toLowerCase() == revCertSerial.toLowerCase()) { + if (certObject.serialNumber.toLowerCase() == revCertSerial.toLowerCase()) { console.debug("Cert on CRL:") throw new Error("certificate revoked") } @@ -343,17 +371,17 @@ async function validateCrl(jwtCertObject, caTrustAnchorObject) { //It will first fetch the chain at runtime, and then compare against the trust anchor. async function validateCertChain(cert, caTrustAnchorObject) { console.debug("Inbound Cert to validate: ") - console.debug(cert) + console.debug(JSON.stringify(cert)) console.debug("Trust Anchor to validate against: ") - console.debug(caTrustAnchorObject) + console.debug(JSON.stringify(caTrustAnchorObject)) try { const caTrustAnchor = caTrustAnchorObject var caStore = pki.createCaStore() caStore.addCertificate(caTrustAnchor) - const inboundCertChain = await getCertChain(cert) + const inboundCertChain = await buildCertChain(caTrustAnchor, cert) var chainVerified = pki.verifyCertificateChain(caStore, inboundCertChain) console.debug('Certificate chain verified: ', chainVerified) @@ -367,44 +395,73 @@ async function validateCertChain(cert, caTrustAnchorObject) { } //Gets the certificate chain from the inbound certificate used at runtime. -async function getCertChain(inboundCert) { +//This method will check all the CRLs along the way, and if we encounter the root trust anchor, we'll stop. +//Anything in the chain that's "above" our designated trust anchor is ignored. +async function buildCertChain(trustAnchor, inboundCert) { const certChain = [] + var previousCert = null var currentCert = inboundCert - var parent = null + var parentCert = null do { - certChain.push(currentCert) - parent = currentCert.getExtension('authorityInfoAccess') - if (parent != null) { + const parentUrl = currentCert.getExtension('authorityInfoAccess') + if (parentUrl != null) { //TODO: Try to parse this like CRL sample .fromDer - var parentUrl = parent.value.toString().split('\u0002') - var parsePos = parentUrl[1].indexOf('http') - var aiaUrl = parentUrl[1].substring(parsePos) - console.debug("AIA Cert URI: " + aiaUrl) - - const httpResponse = await axios.request({ - 'url': aiaUrl, - 'responseType': 'arraybuffer', - 'method': 'get', - 'headers': { 'Accept': 'application/x-x509-ca-cert' } - }) - console.debug("1. HttpResponse Data:") - console.debug(httpResponse.data) - if (httpResponse.data != null) { - var cerDer = forge.util.createBuffer(httpResponse.data, 'raw') - var asn1Cert = asn1.fromDer(cerDer) - console.debug("AIA Cert: " + asn1.prettyPrint(asn1Cert)) - currentCert = pki.certificateFromAsn1(asn1Cert) - } - else { - throw new Error('Could not retrieve cert: ' + httpResponse.statusCode) - } + var parentUrlValue = parentUrl.value.toString().split('\u0002') + var parsePos = parentUrlValue[1].indexOf('http') + var aiaUrl = parentUrlValue[1].substring(parsePos) + console.debug("AIA Cert URI: " + JSON.stringify(aiaUrl)) + parentCert = await getCertificate(aiaUrl) + + if (!parentCert) { + throw new Error('A parent certificate was identified but could not be retrieved.') + } } else { - currentCert = parent + parentCert = null } + + await validateCrl(currentCert, parentCert) + + console.log("Certificate has been validated- adding to the chain!") + certChain.push(currentCert) + previousCert = currentCert + currentCert = parentCert } - while (currentCert != null) + while (currentCert != null && previousCert.subject.hash != trustAnchor.subject.hash) + console.debug("2. Finished with chain") + console.debug("Chain length: " + certChain.length) + console.debug(JSON.stringify(certChain)) return certChain } + +async function getCertificate(certificateUrl) { + const httpResponse = await axios.request({ + 'url': certificateUrl, + 'responseType': 'arraybuffer', + 'method': 'get', + 'headers': { 'Accept': 'application/x-x509-ca-cert' } + }) + console.debug("1. HttpResponse Data:") + console.debug(httpResponse.data) + + try { + var cerDer = forge.util.createBuffer(httpResponse.data, 'raw') + var asn1Cert = asn1.fromDer(cerDer) + console.debug("AIA Cert: " + asn1.prettyPrint(asn1Cert)) + return pki.certificateFromAsn1(asn1Cert) + } + catch(error) { + console.debug("The certificate was not provided in DER format. Attempting to parse PEM...") + } + + try { + return pki.certificateFromPem(httpResponse.data) + } + catch(error) { + console.debug("The certificate was not provided in PEM format. Not sure what to do...") + } + + return null +}