Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated to validate CRLs for each certificate found in the chain. #2

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 103 additions & 46 deletions udap-common.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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.
Expand All @@ -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)
Expand All @@ -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")
}
Expand All @@ -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)
Expand All @@ -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
}
Loading