Skip to content

Commit

Permalink
refactor(plugins): Refactor mbedTLS certificate verification to inclu…
Browse files Browse the repository at this point in the history
…de intermediate steps
  • Loading branch information
jpfr committed Sep 25, 2024
1 parent bcee07a commit 30f29b9
Showing 1 changed file with 141 additions and 225 deletions.
366 changes: 141 additions & 225 deletions plugins/crypto/mbedtls/ua_pki_mbedtls.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#include <mbedtls/error.h>
#include <mbedtls/version.h>

#include "mbedtls_helpers.h"

#define REMOTECERTIFICATETRUSTED 1
#define ISSUERKNOWN 2
#define DUALPARENT 3
Expand Down Expand Up @@ -236,251 +238,165 @@ reloadCertificates(CertInfo *ci) {

#endif

static UA_StatusCode
certificateVerification_verify(void *verificationContext,
const UA_ByteString *certificate) {
CertInfo *ci = (CertInfo*)verificationContext;
if(!ci)
return UA_STATUSCODE_BADINTERNALERROR;

#ifdef __linux__ /* Reload certificates if folder paths are specified */
UA_StatusCode certFlag = reloadCertificates(ci);
if(certFlag != UA_STATUSCODE_GOOD) {
return certFlag;
}
#endif

if(ci->trustListFolder.length == 0 &&
ci->issuerListFolder.length == 0 &&
ci->revocationListFolder.length == 0 &&
ci->certificateTrustList.raw.len == 0 &&
ci->certificateIssuerList.raw.len == 0 &&
ci->certificateRevocationList.raw.len == 0) {
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
"PKI plugin unconfigured. Accepting the certificate.");
return UA_STATUSCODE_GOOD;
}

/* Parse the certificate */
mbedtls_x509_crt remoteCertificate;

/* Temporary Object to parse the trustList */
mbedtls_x509_crt *tempCert = NULL;

/* Temporary Object to parse the revocationList */
mbedtls_x509_crl *tempCrl = NULL;

/* Temporary Object to identify the parent CA when there is no intermediate CA */
mbedtls_x509_crt *parentCert = NULL;

/* Temporary Object to identify the parent CA when there is intermediate CA */
mbedtls_x509_crt *parentCert_2 = NULL;

/* Flag value to identify if the issuer certificate is found */
int issuerKnown = 0;

/* Flag value to identify if the parent certificate found */
int parentFound = 0;

mbedtls_x509_crt_init(&remoteCertificate);
int mbedErr = mbedtls_x509_crt_parse(&remoteCertificate, certificate->data,
certificate->length);
if(mbedErr) {
/* char errBuff[300]; */
/* mbedtls_strerror(mbedErr, errBuff, 300); */
/* UA_LOG_WARNING(data->policyContext->securityPolicy->logger, UA_LOGCATEGORY_SECURITYPOLICY, */
/* "Could not parse the remote certificate with error: %s", errBuff); */
return UA_STATUSCODE_BADSECURITYCHECKSFAILED;
}
/* Return the first matching issuer candidate AFTER prev */
static mbedtls_x509_crt *
mbedtlsFindNextIssuer(CertInfo *ci, mbedtls_x509_crt *stack,
mbedtls_x509_crt *cert, mbedtls_x509_crt *prev) {
/* First check issuers from certificate chain included with cert.
* The iterate over the issuer list. Can return cert itself. */
do {
for(mbedtls_x509_crt *i = stack; i; i = i->next) {
if(!x509_name_eq(&cert->issuer, &i->subject))
continue;
if(prev == NULL)
return i;
prev = NULL;
}
/* Switch to search in the ctx->skIssue list */
stack = (stack != &ci->certificateIssuerList) ? &ci->certificateIssuerList : NULL;
} while(stack);
return NULL;
}

/* Verify */
mbedtls_x509_crt_profile crtProfile = {
MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA1) | MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA256),
0xFFFFFF, 0x000000, 128 * 8 // in bits
}; // TODO: remove magic numbers

uint32_t flags = 0;
mbedErr = mbedtls_x509_crt_verify_with_profile(&remoteCertificate,
&ci->certificateTrustList,
&ci->certificateRevocationList,
&crtProfile, NULL, &flags, NULL, NULL);

/* Flag to check if the remote certificate is trusted or not */
int TRUSTED = 0;

/* Check if the remoteCertificate is present in the trustList while mbedErr value is not zero */
if(mbedErr && !(flags & MBEDTLS_X509_BADCERT_EXPIRED) && !(flags & MBEDTLS_X509_BADCERT_FUTURE)) {
for(tempCert = &ci->certificateTrustList; tempCert != NULL; tempCert = tempCert->next) {
if(remoteCertificate.raw.len == tempCert->raw.len &&
memcmp(remoteCertificate.raw.p, tempCert->raw.p, remoteCertificate.raw.len) == 0) {
TRUSTED = REMOTECERTIFICATETRUSTED;
break;
}
static UA_Boolean
mbedtlsCheckRevoked(CertInfo *ci, mbedtls_x509_crt *cert) {
for(mbedtls_x509_crl *crl = &ci->certificateRevocationList; crl; crl = crl->next) {
if(!x509_name_eq(&cert->issuer, &crl->issuer))
continue;
for(mbedtls_x509_crl_entry *cur = &crl->entry; cur; cur = cur->next) {
if(cert->serial.len == cur->serial.len &&
memcmp(cert->serial.p, cur->serial.p, cert->serial.len) == 0)
return true;
}
}
return false;
}

/* If the remote certificate is present in the trustList then check if the issuer certificate
* of remoteCertificate is present in issuerList */
if(TRUSTED && mbedErr) {
mbedErr = mbedtls_x509_crt_verify_with_profile(&remoteCertificate,
&ci->certificateIssuerList,
&ci->certificateRevocationList,
&crtProfile, NULL, &flags, NULL, NULL);

/* Check if the parent certificate has a CRL file available */
if(!mbedErr) {
/* Flag value to identify if that there is an intermediate CA present */
int dualParent = 0;

/* Identify the topmost parent certificate for the remoteCertificate */
for(parentCert = &ci->certificateIssuerList; parentCert != NULL; parentCert = parentCert->next ) {
if(memcmp(remoteCertificate.issuer_raw.p, parentCert->subject_raw.p, parentCert->subject_raw.len) == 0) {
for(parentCert_2 = &ci->certificateTrustList; parentCert_2 != NULL; parentCert_2 = parentCert_2->next) {
if(memcmp(parentCert->issuer_raw.p, parentCert_2->subject_raw.p, parentCert_2->subject_raw.len) == 0) {
dualParent = DUALPARENT;
break;
}
}
parentFound = PARENTFOUND;
}

if(parentFound == PARENTFOUND)
break;
}

/* Check if there is an intermediate certificate between the topmost parent
* certificate and child certificate
* If yes the topmost parent certificate is to be checked whether it has a
* CRL file avaiable */
if(dualParent == DUALPARENT && parentFound == PARENTFOUND) {
parentCert = parentCert_2;
}
#define UA_MBEDTLS_MAX_CHAIN_LENGTH 10

/* If a parent certificate is found traverse the revocationList and identify
* if there is any CRL file that corresponds to the parentCertificate */
if(parentFound == PARENTFOUND) {
tempCrl = &ci->certificateRevocationList;
while(tempCrl != NULL) {
if(tempCrl->version != 0 &&
tempCrl->issuer_raw.len == parentCert->subject_raw.len &&
memcmp(tempCrl->issuer_raw.p,
parentCert->subject_raw.p,
tempCrl->issuer_raw.len) == 0) {
issuerKnown = ISSUERKNOWN;
break;
}

tempCrl = tempCrl->next;
}

/* If the CRL file corresponding to the parent certificate is not present
* then return UA_STATUSCODE_BADCERTIFICATEISSUERREVOCATIONUNKNOWN */
if(issuerKnown) {
flags = 0;
mbedErr = mbedtls_x509_crt_verify_with_profile(parentCert,
&ci->certificateIssuerList,
&ci->certificateRevocationList,
&crtProfile, NULL, &flags, NULL, NULL);
} else {
return UA_STATUSCODE_BADCERTIFICATEISSUERREVOCATIONUNKNOWN;
}
static UA_StatusCode
mbedtls_verifyChain(CertInfo *ci, mbedtls_x509_crt *stack, mbedtls_x509_crt **old_issuers,
mbedtls_x509_crt *cert, int depth) {
/* Maxiumum chain length */
if(depth == UA_MBEDTLS_MAX_CHAIN_LENGTH)
return UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE;

/* Verification Step: Validity Period */
if(mbedtls_x509_time_is_future(&cert->valid_from) ||
mbedtls_x509_time_is_past(&cert->valid_to))
return (depth == 0) ? UA_STATUSCODE_BADCERTIFICATETIMEINVALID :
UA_STATUSCODE_BADCERTIFICATEISSUERTIMEINVALID;

/* Verification Step: Revocation Check */
if(mbedtlsCheckRevoked(ci, cert))
return (depth == 0) ? UA_STATUSCODE_BADCERTIFICATEREVOKED :
UA_STATUSCODE_BADCERTIFICATEISSUERREVOKED;

/* Return the most specific error code. BADCERTIFICATECHAININCOMPLETE is
* returned only if all possible chains are incomplete. */
mbedtls_x509_crt *issuer = NULL;
UA_StatusCode ret = UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE;
while(ret != UA_STATUSCODE_GOOD) {
/* Find the issuer. This can return the same certificate if it is
* self-signed (subject == issuer). We come back here to try a different
* "path" if a subsequent verification fails. */
issuer = mbedtlsFindNextIssuer(ci, stack, cert, issuer);
if(!issuer)
break;

/* Verification Step: Signature */
if(!x509_crt_check_signature(cert, issuer)) {
ret = UA_STATUSCODE_BADCERTIFICATEINVALID; /* Wrong issuer, try again */
continue;
}

}
/* The certificate is self-signed. We have arrived at the top of the
* chain. We check whether the certificate is trusted below. This is the
* only place where we return UA_STATUSCODE_BADCERTIFICATEUNTRUSTED.
* This sinals that the chain is complete (but can be still
* untrusted). */
if(issuer == cert || (cert->tbs.len == issuer->tbs.len &&
memcmp(cert->tbs.p, issuer->tbs.p, cert->tbs.len) == 0)) {
ret = UA_STATUSCODE_BADCERTIFICATEUNTRUSTED;
continue;
}

/* Detect (endless) loops of issuers. The last one can be skipped by the
* check for self-signed just before. */
for(int i = 0; i < depth - 1; i++) {
if(old_issuers[i] == issuer)
return UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE;
}
old_issuers[depth] = issuer;

/* We have found the issuer certificate used for the signature. Recurse
* to the next certificate in the chain (verify the current issuer). */
ret = mbedtls_verifyChain(ci, stack, old_issuers, issuer, depth + 1);
}
else if(!mbedErr && !TRUSTED) {
/* This else if section is to identify if the parent certificate which is present in trustList
* has CRL file corresponding to it */

/* Identify the parent certificate of the remoteCertificate */
for(parentCert = &ci->certificateTrustList; parentCert != NULL; parentCert = parentCert->next) {
if(memcmp(remoteCertificate.issuer_raw.p, parentCert->subject_raw.p, parentCert->subject_raw.len) == 0) {
parentFound = PARENTFOUND;
break;
}

/* The chain is complete, but we haven't yet identified a trusted
* certificate "on the way down". Can we trust this certificate? */
if(ret == UA_STATUSCODE_BADCERTIFICATEUNTRUSTED) {
for(mbedtls_x509_crt *t = &ci->certificateTrustList; t; t = t->next) {
if(cert->tbs.len == t->tbs.len &&
memcmp(cert->tbs.p, t->tbs.p, cert->tbs.len) == 0)
return UA_STATUSCODE_GOOD;
}
}

/* If the parent certificate is found traverse the revocationList and identify
* if there is any CRL file that corresponds to the parentCertificate */
if(parentFound == PARENTFOUND &&
memcmp(remoteCertificate.issuer_raw.p, remoteCertificate.subject_raw.p, remoteCertificate.subject_raw.len) != 0) {
tempCrl = &ci->certificateRevocationList;
while(tempCrl != NULL) {
if(tempCrl->version != 0 &&
tempCrl->issuer_raw.len == parentCert->subject_raw.len &&
memcmp(tempCrl->issuer_raw.p,
parentCert->subject_raw.p,
tempCrl->issuer_raw.len) == 0) {
issuerKnown = ISSUERKNOWN;
break;
}

tempCrl = tempCrl->next;
}

/* If the CRL file corresponding to the parent certificate is not present
* then return UA_STATUSCODE_BADCERTIFICATEREVOCATIONUNKNOWN */
if(issuerKnown) {
flags = 0;
mbedErr = mbedtls_x509_crt_verify_with_profile(parentCert,
&ci->certificateIssuerList,
&ci->certificateRevocationList,
&crtProfile, NULL, &flags, NULL, NULL);
} else {
return UA_STATUSCODE_BADCERTIFICATEISSUERREVOCATIONUNKNOWN;
}
return ret;
}

}
/* This follows Part 6, 6.1.3 Determining if a Certificate is trusted.
* It defines a sequence of steps for certificate verification. */
static UA_StatusCode
certificateVerification_verify(void *verificationContext,
const UA_ByteString *certificate) {
if(!verificationContext || !certificate)
return UA_STATUSCODE_BADINTERNALERROR;

}
UA_StatusCode ret = UA_STATUSCODE_GOOD;
CertInfo *ci = (CertInfo*)verificationContext;

// TODO: Extend verification

/* This condition will check whether the certificate is a User certificate
* or a CA certificate. If the MBEDTLS_X509_KU_KEY_CERT_SIGN and
* MBEDTLS_X509_KU_CRL_SIGN of key_usage are set, then the certificate
* shall be condidered as CA Certificate and cannot be used to establish a
* connection. Refer the test case CTT/Security/Security Certificate Validation/029.js
* for more details */
#if MBEDTLS_VERSION_NUMBER >= 0x02060000 && MBEDTLS_VERSION_NUMBER < 0x03000000
if((remoteCertificate.key_usage & MBEDTLS_X509_KU_KEY_CERT_SIGN) &&
(remoteCertificate.key_usage & MBEDTLS_X509_KU_CRL_SIGN)) {
return UA_STATUSCODE_BADCERTIFICATEUSENOTALLOWED;
}
#else
if((remoteCertificate.private_key_usage & MBEDTLS_X509_KU_KEY_CERT_SIGN) &&
(remoteCertificate.private_key_usage & MBEDTLS_X509_KU_CRL_SIGN)) {
return UA_STATUSCODE_BADCERTIFICATEUSENOTALLOWED;
}
#ifdef __linux__ /* Reload certificates if folder paths are specified */
ret = reloadCertificates(ci);
if(ret != UA_STATUSCODE_GOOD)
return ret;
#endif


UA_StatusCode retval = UA_STATUSCODE_GOOD;
if(mbedErr) {
#if UA_LOGLEVEL <= 400
char buff[100];
int len = mbedtls_x509_crt_verify_info(buff, 100, "", flags);
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Verifying the certificate failed with error: %.*s", len-1, buff);
#endif
if(flags & (uint32_t)MBEDTLS_X509_BADCERT_NOT_TRUSTED) {
retval = UA_STATUSCODE_BADCERTIFICATEUNTRUSTED;
} else if(flags & (uint32_t)MBEDTLS_X509_BADCERT_FUTURE ||
flags & (uint32_t)MBEDTLS_X509_BADCERT_EXPIRED) {
retval = UA_STATUSCODE_BADCERTIFICATETIMEINVALID;
} else if(flags & (uint32_t)MBEDTLS_X509_BADCERT_REVOKED ||
flags & (uint32_t)MBEDTLS_X509_BADCRL_EXPIRED) {
retval = UA_STATUSCODE_BADCERTIFICATEREVOKED;
} else {
retval = UA_STATUSCODE_BADSECURITYCHECKSFAILED;
}
/* Verification Step: Certificate Structure
* This parses the entire certificate chain contained in the bytestring. */
mbedtls_x509_crt cert;
mbedtls_x509_crt_init(&cert);
int mbedErr = mbedtls_x509_crt_parse(&cert, certificate->data,
certificate->length);
if(mbedErr)
return UA_STATUSCODE_BADCERTIFICATEINVALID;

/* Verification Step: Certificate Usage
* Check whether the certificate is a User certificate or a CA certificate.
* If the KU_KEY_CERT_SIGN and KU_CRL_SIGN of key_usage are set, then the
* certificate shall be condidered as CA Certificate and cannot be used to
* establish a connection. Refer the test case CTT/Security/Security
* Certificate Validation/029.js for more details */
unsigned int ca_flags = MBEDTLS_X509_KU_KEY_CERT_SIGN | MBEDTLS_X509_KU_CRL_SIGN;
if(mbedtls_x509_crt_check_key_usage(&cert, ca_flags)) {
mbedtls_x509_crt_free(&cert);
return UA_STATUSCODE_BADCERTIFICATEUSENOTALLOWED;
}

mbedtls_x509_crt_free(&remoteCertificate);
return retval;
/* These steps are performed outside of this method.
* Because we need the server or client context.
* - Security Policy
* - Host Name
* - URI */

/* Verification Step: Build Certificate Chain
* We perform the checks for each certificate inside. */
mbedtls_x509_crt *old_issuers[UA_MBEDTLS_MAX_CHAIN_LENGTH];
ret = mbedtls_verifyChain(ci, &cert, old_issuers, &cert, 0);
mbedtls_x509_crt_free(&cert);
return ret;
}

static UA_StatusCode
Expand Down

0 comments on commit 30f29b9

Please sign in to comment.