diff --git a/plugins/crypto/mbedtls/ua_pki_mbedtls.c b/plugins/crypto/mbedtls/ua_pki_mbedtls.c index cb02c83eace..298e92e8936 100644 --- a/plugins/crypto/mbedtls/ua_pki_mbedtls.c +++ b/plugins/crypto/mbedtls/ua_pki_mbedtls.c @@ -3,7 +3,7 @@ * * Copyright 2018 (c) Mark Giraud, Fraunhofer IOSB * Copyright 2019 (c) Kalycito Infotech Private Limited - * Copyright 2019 (c) Julius Pfrommer, Fraunhofer IOSB + * Copyright 2019, 2024 (c) Julius Pfrommer, Fraunhofer IOSB */ #include @@ -19,13 +19,7 @@ #include #include #include - -#include - -#define REMOTECERTIFICATETRUSTED 1 -#define ISSUERKNOWN 2 -#define DUALPARENT 3 -#define PARENTFOUND 4 +#include /* Find binary substring. Taken and adjusted from * http://tungchingkai.blogspot.com/2011/07/binary-strstr.html */ @@ -242,288 +236,204 @@ reloadCertificates(const UA_CertificateVerification *cv, CertInfo *ci) { #endif -static UA_StatusCode -certificateVerification_verify(const UA_CertificateVerification *cv, - const UA_ByteString *certificate) { - CertInfo *ci; - if(!cv) - return UA_STATUSCODE_BADINTERNALERROR; - ci = (CertInfo*)cv->context; - if(!ci) - return UA_STATUSCODE_BADINTERNALERROR; - -#ifdef __linux__ /* Reload certificates if folder paths are specified */ - UA_StatusCode certFlag = reloadCertificates(cv, ci); - if(certFlag != UA_STATUSCODE_GOOD) { - return certFlag; - } +/* We need to access some private fields below */ +#ifndef MBEDTLS_PRIVATE +#define MBEDTLS_PRIVATE(x) x #endif - if(ci->trustListFolder.length == 0 && - ci->issuerListFolder.length == 0 && - ci->revocationListFolder.length == 0 && - ci->rejectedListFolder.length == 0 && - ci->certificateTrustList.raw.len == 0 && - ci->certificateIssuerList.raw.len == 0 && - ci->certificateRevocationList.raw.len == 0) { - UA_LOG_WARNING(cv->logging, UA_LOGCATEGORY_USERLAND, - "No certificate store configured. 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; - } - - /* 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; - } +/* 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) { + char inbuf[512], snbuf[512]; + if(mbedtls_x509_dn_gets(inbuf, 512, &cert->issuer) < 0) + return NULL; + do { + for(mbedtls_x509_crt *i = stack; i; i = i->next) { + /* Compare issuer name and subject name */ + if(mbedtls_x509_dn_gets(snbuf, 512, &i->subject) < 0) + continue; + if(strncmp(inbuf, snbuf, 512) != 0) + continue; + /* Skip when the key does not match the signature */ + if(!mbedtls_pk_can_do(&i->pk, cert->MBEDTLS_PRIVATE(sig_pk))) + continue; + if(prev == NULL) + return i; + prev = NULL; /* This was the last issuer we tried to verify */ } - } - - /* 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; - } + /* Switch from the stack that came with the cert to the ctx->skIssue list */ + stack = (stack != &ci->certificateIssuerList) ? &ci->certificateIssuerList : NULL; + } while(stack); + return NULL; +} - /* 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; - } +static UA_Boolean +mbedtlsCheckRevoked(CertInfo *ci, mbedtls_x509_crt *cert) { + char inbuf[512], inbuf2[512]; + if(mbedtls_x509_dn_gets(inbuf, 512, &cert->issuer) < 0) + return true; + for(mbedtls_x509_crl *crl = &ci->certificateRevocationList; crl; crl = crl->next) { + /* Is the CRL for the issuer of the certificate? */ + if(mbedtls_x509_dn_gets(inbuf2, 512, &crl->issuer) < 0) + return true; + if(strncmp(inbuf, inbuf2, 512) != 0) + continue; + /* Is the serial number of the certificate contained in the CRL? */ + if(mbedtls_x509_crt_is_revoked(cert, crl) != 0) + return true; + } + return false; +} - /* 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; - } +/* Verify that the public key of the issuer was used to sign the certificate */ +static UA_Boolean +mbedtlsCheckSignature(const mbedtls_x509_crt *cert, mbedtls_x509_crt *issuer) { + size_t hash_len; + unsigned char hash[MBEDTLS_MD_MAX_SIZE]; + mbedtls_md_type_t md = cert->MBEDTLS_PRIVATE(sig_md); +#if !defined(MBEDTLS_USE_PSA_CRYPTO) + const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(md); + hash_len = mbedtls_md_get_size(md_info); + if(mbedtls_md(md_info, cert->tbs.p, cert->tbs.len, hash) != 0) + return false; +#else + if(psa_hash_compute(mbedtls_md_psa_alg_from_type(md), cert->tbs.p, + cert->tbs.len, hash, sizeof(hash), &hash_len) != PSA_SUCCESS) + return false; +#endif + const mbedtls_x509_buf *sig = &cert->MBEDTLS_PRIVATE(sig); + void *sig_opts = cert->MBEDTLS_PRIVATE(sig_opts); + mbedtls_pk_type_t pktype = cert->MBEDTLS_PRIVATE(sig_pk); + return (mbedtls_pk_verify_ext(pktype, sig_opts, &issuer->pk, md, + hash, hash_len, sig->p, sig->len) == 0); +} - } +#define UA_MBEDTLS_MAX_CHAIN_LENGTH 10 +static UA_StatusCode +mbedtlsVerifyChain(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(!mbedtlsCheckSignature(cert, issuer)) { + ret = UA_STATUSCODE_BADCERTIFICATEINVALID; /* Wrong issuer, try again */ + continue; } - } - 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 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; } - /* 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; - } + /* 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; - /* 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; - } + /* We have found the issuer certificate used for the signature. Recurse + * to the next certificate in the chain (verify the current issuer). */ + ret = mbedtlsVerifyChain(ci, stack, old_issuers, issuer, depth + 1); + } + /* 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; } - } - // TODO: Extend verification + return ret; +} - /* 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; - } -#endif +/* 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(const UA_CertificateVerification *cv, + const UA_ByteString *certificate) { + if(!cv || !certificate) + return UA_STATUSCODE_BADINTERNALERROR; + UA_StatusCode ret = UA_STATUSCODE_GOOD; + CertInfo *ci = (CertInfo*)cv->context; - 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(cv->logging, UA_LOGCATEGORY_SECURITYPOLICY, - "Verifying the certificate failed with error: %.*s", len-1, buff); +#ifdef __linux__ /* Reload certificates if folder paths are specified */ + ret = reloadCertificates(cv, ci); + if(ret != UA_STATUSCODE_GOOD) + return ret; #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; - } -#ifdef UA_ENABLE_CERT_REJECTED_DIR - if(ci->rejectedListFolder.length !=0) { - char rejectedFileName[256] = {0}; - UA_ByteString thumbprint; - UA_ByteString_allocBuffer(&thumbprint, UA_SHA1_LENGTH); - if(mbedtls_thumbprint_sha1(certificate, &thumbprint) == UA_STATUSCODE_GOOD) { - static const char hex2char[] = "0123456789ABCDEF"; - for (size_t pos = 0, namePos = 0; pos < thumbprint.length; pos++) { - rejectedFileName[namePos++] = hex2char[(thumbprint.data[pos] & 0xf0) >> 4]; - rejectedFileName[namePos++] = hex2char[thumbprint.data[pos] & 0x0f]; - } - strcat(rejectedFileName, ".der"); - } else { - UA_UInt64 dt = (UA_UInt64) UA_DateTime_now(); - sprintf(rejectedFileName, "cert_%" PRIu64 ".der", dt); - } - UA_ByteString_clear(&thumbprint); - - char *rejectedFullFileName = (char *) calloc(ci->rejectedListFolder.length + 1 /* '/' */ + strlen(rejectedFileName) + 1, sizeof(char)); - if (rejectedFullFileName) { - memcpy(rejectedFullFileName, ci->rejectedListFolder.data, ci->rejectedListFolder.length); - rejectedFullFileName[ci->rejectedListFolder.length] = '/'; - memcpy(&rejectedFullFileName[ci->rejectedListFolder.length + 1], rejectedFileName, strlen(rejectedFileName)); - FILE * fp_rejectedFile = fopen(rejectedFullFileName, "wb"); - if (fp_rejectedFile) { - fwrite(certificate->data, sizeof(certificate->data[0]), certificate->length, fp_rejectedFile); - fclose(fp_rejectedFile); - } - free(rejectedFullFileName); - } - } -#endif + /* 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 = mbedtlsVerifyChain(ci, &cert, old_issuers, &cert, 0); + mbedtls_x509_crt_free(&cert); + return ret; } static UA_StatusCode @@ -804,4 +714,4 @@ UA_PKI_decryptPrivateKey(const UA_ByteString privateKey, return UA_STATUSCODE_GOOD; } -#endif +#endif /* UA_ENABLE_ENCRYPTION_MBEDTLS */ diff --git a/plugins/crypto/openssl/ua_pki_openssl.c b/plugins/crypto/openssl/ua_pki_openssl.c index e3f6a7cc7af..525823e8474 100644 --- a/plugins/crypto/openssl/ua_pki_openssl.c +++ b/plugins/crypto/openssl/ua_pki_openssl.c @@ -131,9 +131,7 @@ UA_CertificateVerification_clear (UA_CertificateVerification * cv) { context->cv = NULL; UA_free (context); - cv->context = NULL; - - return; + memset(cv, 0, sizeof(UA_CertificateVerification)); } static UA_StatusCode @@ -405,203 +403,226 @@ UA_ReloadCertFromFolder (CertContext * ctx) { #endif /* end of __linux__ */ -static UA_StatusCode -UA_X509_Store_CTX_Error_To_UAError (int opensslErr) { - UA_StatusCode ret; +static const unsigned char openssl_PEM_PRE[28] = "-----BEGIN CERTIFICATE-----"; + +/* Extract the leaf certificate from a bytestring that may contain an entire chain */ +static X509 * +openSSLLoadLeafCertificate(UA_ByteString cert, size_t *offset) { + if(cert.length <= *offset) + return NULL; + cert.length -= *offset; + cert.data += *offset; + + /* Detect DER encoding. Extract the encoding length and cut. */ + if(cert.length >= 4 && cert.data[0] == 0x30 && cert.data[1] == 0x82) { + /* The certificate length is encoded after the magic bytes */ + size_t certLen = 4; /* Magic numbers + length bytes */ + certLen += (size_t)(((uint16_t)cert.data[2]) << 8); + certLen += cert.data[3]; + if(certLen > cert.length) + return NULL; + cert.length = certLen; + *offset += certLen; + const UA_Byte *dataPtr = cert.data; + return d2i_X509(NULL, &dataPtr, (long)cert.length); + } + + /* Assume PEM encoding. Detect multiple certificates and cut. */ + if(cert.length > 27 * 4) { + const unsigned char *match = + UA_Bstrstr(openssl_PEM_PRE, 27, &cert.data[27*2], cert.length - (27*2)); + if(match) + cert.length = (uintptr_t)(match - cert.data); + } + *offset += cert.length; + + BIO *bio = BIO_new_mem_buf((void *) cert.data, (int)cert.length); + X509 *result = PEM_read_bio_X509(bio, NULL, NULL, NULL); + BIO_free(bio); + return result; +} - switch (opensslErr) { - case X509_V_ERR_CERT_HAS_EXPIRED: - case X509_V_ERR_CERT_NOT_YET_VALID: - case X509_V_ERR_CRL_NOT_YET_VALID: - case X509_V_ERR_CRL_HAS_EXPIRED: - case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: - case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: - case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: - case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: - ret = UA_STATUSCODE_BADCERTIFICATETIMEINVALID; - break; - case X509_V_ERR_CERT_REVOKED: - ret = UA_STATUSCODE_BADCERTIFICATEREVOKED; - break; - case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: - ret = UA_STATUSCODE_BADCERTIFICATEUNTRUSTED; - break; - case X509_V_ERR_CERT_SIGNATURE_FAILURE: - case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: - ret = UA_STATUSCODE_BADSECURITYCHECKSFAILED; - break; - case X509_V_ERR_UNABLE_TO_GET_CRL: - ret = UA_STATUSCODE_BADCERTIFICATEREVOCATIONUNKNOWN; - break; - default: - ret = UA_STATUSCODE_BADCERTIFICATEINVALID; - break; - } - return ret; - } +/* The bytestring might contain an entire certificate chain. The first + * stack-element is the leaf certificate itself. The remaining ones are + * potential issuer certificates. */ +static STACK_OF(X509) * +openSSLLoadCertificateStack(const UA_ByteString cert) { + size_t offset = 0; + X509 *x509 = NULL; + STACK_OF(X509) *result = sk_X509_new_null(); + if(!result) + return NULL; + while((x509 = openSSLLoadLeafCertificate(cert, &offset))) { + sk_X509_push(result, x509); + } + return result; +} -static UA_StatusCode -UA_CertificateVerification_Verify (const UA_CertificateVerification *cv, - const UA_ByteString * certificate) { - X509_STORE_CTX *storeCtx = NULL; - X509_STORE *store = NULL; - CertContext *ctx = NULL; - UA_StatusCode ret = UA_STATUSCODE_GOOD; +/* Return the first matching issuer candidate AFTER prev */ +static X509 * +openSSLFindNextIssuer(CertContext *ctx, STACK_OF(X509) *stack, X509 *x509, X509 *prev) { + /* First check issuers from the stack - provided in the same bytestring as + * the certificate. This can also return x509 itself. */ + do { + int size = sk_X509_num(stack); + for(int i = 0; i < size; i++) { + X509 *candidate = sk_X509_value(stack, i); + if(X509_check_issued(candidate, x509) != 0) + continue; + if(prev == NULL) + return candidate; + prev = NULL; + } + /* Switch to search in the ctx->skIssue list */ + stack = (stack != ctx->skIssue) ? ctx->skIssue : NULL; + } while(stack); + return NULL; +} - if ((cv == NULL) || (cv->context == NULL)) { - return UA_STATUSCODE_BADINTERNALERROR; +static UA_Boolean +openSSLCheckRevoked(CertContext *ctx, X509 *cert) { + const ASN1_INTEGER *sn = X509_get0_serialNumber(cert); + const X509_NAME *in = X509_get_issuer_name(cert); + int size = sk_X509_CRL_num(ctx->skCrls); + for(int i = 0; i < size; i++) { + /* The crl contains a list of serial numbers from the same issuer */ + X509_CRL *crl = sk_X509_CRL_value(ctx->skCrls, i); + if(X509_NAME_cmp(in, X509_CRL_get_issuer(crl)) != 0) + continue; + STACK_OF(X509_REVOKED) *rs = X509_CRL_get_REVOKED(crl); + int rsize = sk_X509_REVOKED_num(rs); + for(int j = 0; j < rsize; j++) { + X509_REVOKED *r = sk_X509_REVOKED_value(rs, j); + if(ASN1_INTEGER_cmp(sn, X509_REVOKED_get0_serialNumber(r)) == 0) + return true; + } } - ctx = (CertContext *) cv->context; + return false; +} - /* Parse the certificate */ - X509 *certificateX509 = UA_OpenSSL_LoadCertificate(certificate); - if(!certificateX509) { - ret = UA_STATUSCODE_BADCERTIFICATEINVALID; - goto cleanup; - } +#define UA_OPENSSL_MAX_CHAIN_LENGTH 10 - /* Reload PKI folder */ -#ifdef __linux__ - ret = UA_ReloadCertFromFolder (ctx); - if(ret != UA_STATUSCODE_GOOD) - goto cleanup; -#endif +static UA_StatusCode +openSSL_verifyChain(CertContext *ctx, STACK_OF(X509) *stack, X509 **old_issuers, + X509 *cert, int depth) { + /* Maxiumum chain length */ + if(depth == UA_OPENSSL_MAX_CHAIN_LENGTH) + return UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE; + + /* Verification Step: Validity Period */ + ASN1_TIME *notBefore = X509_get_notBefore(cert); + ASN1_TIME *notAfter = X509_get_notAfter(cert); + if(X509_cmp_current_time(notBefore) != -1 || X509_cmp_current_time(notAfter) != 1) + return (depth == 0) ? UA_STATUSCODE_BADCERTIFICATETIMEINVALID : + UA_STATUSCODE_BADCERTIFICATEISSUERTIMEINVALID; + + /* Verification Step: Revocation Check */ + if(openSSLCheckRevoked(ctx, 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. */ + X509 *issuer = NULL; + UA_StatusCode ret = UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE; + while(ret != UA_STATUSCODE_GOOD) { + /* Find the issuer. We jump back here to find a different path if a + * subsequent check fails. */ + issuer = openSSLFindNextIssuer(ctx, stack, cert, issuer); + if(!issuer) + break; - /* Accept the certificate without verification of no trust and issuer list - * are loaded */ - if(sk_X509_CRL_num(ctx->skCrls) == 0 && - sk_X509_num(ctx->skIssue) == 0 && - sk_X509_num(ctx->skTrusted) == 0) { - UA_LOG_WARNING(cv->logging, UA_LOGCATEGORY_USERLAND, - "No certificate store configured. Accepting the certificate."); - goto cleanup; - } - - store = X509_STORE_new(); - storeCtx = X509_STORE_CTX_new(); - if(store == NULL || storeCtx == NULL) { - ret = UA_STATUSCODE_BADOUTOFMEMORY; - goto cleanup; - } - - X509_STORE_set_flags(store, 0); - int opensslRet = X509_STORE_CTX_init(storeCtx, store, certificateX509, - ctx->skIssue); - if(opensslRet != 1) { - ret = UA_STATUSCODE_BADINTERNALERROR; - goto cleanup; - } -#if defined(OPENSSL_API_COMPAT) && OPENSSL_API_COMPAT < 0x10100000L - (void) X509_STORE_CTX_trusted_stack (storeCtx, ctx->skTrusted); -#else - (void) X509_STORE_CTX_set0_trusted_stack (storeCtx, ctx->skTrusted); -#endif + /* Verification Step: Signature */ + int opensslRet = X509_verify(cert, X509_get0_pubkey(issuer)); + if(opensslRet == -1) { + return UA_STATUSCODE_BADCERTIFICATEINVALID; /* Ill-formed signature */ + } else if(opensslRet == 0) { + ret = UA_STATUSCODE_BADCERTIFICATEINVALID; /* Wrong issuer, try again */ + continue; + } - /* Set crls to ctx */ - if (sk_X509_CRL_num (ctx->skCrls) > 0) { - X509_STORE_CTX_set0_crls (storeCtx, ctx->skCrls); - } + /* 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(cert == issuer || X509_cmp(cert, issuer) == 0) { + ret = UA_STATUSCODE_BADCERTIFICATEUNTRUSTED; + continue; + } - /* Set flag to check if the certificate has an invalid signature */ - X509_STORE_CTX_set_flags (storeCtx, X509_V_FLAG_CHECK_SS_SIGNATURE); + /* 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; i++) { + if(old_issuers[i] == issuer) + return UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE; + } + old_issuers[depth] = issuer; - if (X509_check_issued(certificateX509,certificateX509) != X509_V_OK) { - X509_STORE_CTX_set_flags (storeCtx, X509_V_FLAG_CRL_CHECK); + /* We have found the issuer certificate used for the signature. Recurse + * to the next certificate in the chain (verify the current issuer). */ + ret = openSSL_verifyChain(ctx, stack, old_issuers, issuer, depth + 1); } - /* This condition will 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 */ - /** \todo Can the ca-parameter of X509_check_purpose can be used? */ - if(X509_check_purpose(certificateX509, X509_PURPOSE_CRL_SIGN, 0) && X509_check_ca(certificateX509)) { - return UA_STATUSCODE_BADCERTIFICATEUSENOTALLOWED; + /* Is the certificate in the trust list? If yes, then we are done. */ + if(ret == UA_STATUSCODE_BADCERTIFICATEUNTRUSTED) { + for(int i = 0; i < sk_X509_num(ctx->skTrusted); i++) { + if(X509_cmp(cert, sk_X509_value(ctx->skTrusted, i)) == 0) + return UA_STATUSCODE_GOOD; + } } - opensslRet = X509_verify_cert (storeCtx); - if (opensslRet == 1) { - ret = UA_STATUSCODE_GOOD; - - /* Check if the not trusted certificate has a CRL file. If there is no CRL file available for the corresponding - * parent certificate then return status code UA_STATUSCODE_BADCERTIFICATEISSUERREVOCATIONUNKNOWN. Refer the test - * case CTT/Security/Security Certificate Validation/002.js */ - if (X509_check_issued(certificateX509,certificateX509) != X509_V_OK) { - /* Free X509_STORE_CTX and reuse it for certification verification */ - if (storeCtx != NULL) { - X509_STORE_CTX_free(storeCtx); - } - - /* Initialised X509_STORE_CTX sructure*/ - storeCtx = X509_STORE_CTX_new(); - - /* Sets up X509_STORE_CTX structure for a subsequent verification operation */ - X509_STORE_set_flags(store, 0); - X509_STORE_CTX_init (storeCtx, store, certificateX509,ctx->skIssue); + return ret; +} - /* Set trust list to ctx */ - (void) X509_STORE_CTX_trusted_stack (storeCtx, ctx->skTrusted); +/* 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 +UA_CertificateVerification_Verify(const UA_CertificateVerification *cv, + const UA_ByteString *certificate) { + if(!cv || !certificate) + return UA_STATUSCODE_BADINTERNALERROR; - /* Set crls to ctx */ - X509_STORE_CTX_set0_crls (storeCtx, ctx->skCrls); + UA_StatusCode ret = UA_STATUSCODE_GOOD; + CertContext *ctx = (CertContext *)cv->context; - /* Set flags for CRL check */ - X509_STORE_CTX_set_flags (storeCtx, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); +#ifdef __linux__ + ret = UA_ReloadCertFromFolder(ctx); + if(ret != UA_STATUSCODE_GOOD) + return ret; +#endif - opensslRet = X509_verify_cert (storeCtx); - if (opensslRet != 1) { - opensslRet = X509_STORE_CTX_get_error (storeCtx); - if (opensslRet == X509_V_ERR_UNABLE_TO_GET_CRL) { - ret = UA_STATUSCODE_BADCERTIFICATEISSUERREVOCATIONUNKNOWN; - } else { - ret = UA_X509_Store_CTX_Error_To_UAError (opensslRet); - } - } - } + /* Verification Step: Certificate Structure */ + STACK_OF(X509) *stack = openSSLLoadCertificateStack(*certificate); + if(!stack || sk_X509_num(stack) < 1) { + if(stack) + sk_X509_pop_free(stack, X509_free); + 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 */ + X509 *leaf = sk_X509_value(stack, 0); + if(X509_check_purpose(leaf, X509_PURPOSE_CRL_SIGN, 0) && X509_check_ca(leaf)) { + sk_X509_pop_free(stack, X509_free); + return UA_STATUSCODE_BADCERTIFICATEUSENOTALLOWED; } - else { - opensslRet = X509_STORE_CTX_get_error (storeCtx); - - /* Check the issued certificate of a CA that is not trusted but available */ - if(opensslRet == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN){ - int trusted_cert_len = sk_X509_num(ctx->skTrusted); - int cmpVal; - X509 *trusted_cert; - const ASN1_OCTET_STRING *trusted_cert_keyid; - const ASN1_OCTET_STRING *remote_cert_keyid; - - for (int i = 0; i < trusted_cert_len; i++) { - trusted_cert = sk_X509_value(ctx->skTrusted, i); - - /* Fetch the Subject key identifier of the certificate in trust list */ - trusted_cert_keyid = X509_get0_subject_key_id(trusted_cert); - - /* Fetch the Subject key identifier of the remote certificate */ - remote_cert_keyid = X509_get0_subject_key_id(certificateX509); - - if(trusted_cert_keyid && remote_cert_keyid) { - /* Check remote certificate is present in the trust list */ - cmpVal = ASN1_OCTET_STRING_cmp(trusted_cert_keyid, remote_cert_keyid); - if(cmpVal == 0) { - ret = UA_STATUSCODE_GOOD; - goto cleanup; - } - } - } - } - /* Return expected OPCUA error code */ - ret = UA_X509_Store_CTX_Error_To_UAError (opensslRet); - } + /* These steps are performed outside of this method. + * Because we need the server or client context. + * - Security Policy + * - Host Name + * - URI */ -cleanup: - if(store) - X509_STORE_free(store); - if(storeCtx) - X509_STORE_CTX_free(storeCtx); - if(certificateX509) - X509_free(certificateX509); + /* Verification Step: Build Certificate Chain + * We perform the checks for each certificate inside. */ + X509 *old_issuers[UA_OPENSSL_MAX_CHAIN_LENGTH]; + ret = openSSL_verifyChain(ctx, stack, old_issuers, leaf, 0); + sk_X509_pop_free(stack, X509_free); return ret; } diff --git a/plugins/ua_config_default.c b/plugins/ua_config_default.c index f2746d29cc0..2577edbc581 100644 --- a/plugins/ua_config_default.c +++ b/plugins/ua_config_default.c @@ -439,7 +439,12 @@ setDefaultConfig(UA_ServerConfig *conf, UA_UInt16 portNumber) { /* Certificate Verification that accepts every certificate. Can be * overwritten when the policy is specialized. */ + if(conf->secureChannelPKI.clear) + conf->secureChannelPKI.clear(&conf->secureChannelPKI); UA_CertificateVerification_AcceptAll(&conf->secureChannelPKI); + + if(conf->sessionPKI.clear) + conf->sessionPKI.clear(&conf->sessionPKI); UA_CertificateVerification_AcceptAll(&conf->sessionPKI); /* * Global Node Lifecycle * */ @@ -987,6 +992,8 @@ UA_ServerConfig_setDefaultWithSecurityPolicies(UA_ServerConfig *conf, return retval; } + if(conf->sessionPKI.clear) + conf->sessionPKI.clear(&conf->sessionPKI); retval = UA_CertificateVerification_Trustlist(&conf->sessionPKI, trustList, trustListSize, issuerList, issuerListSize, @@ -1185,6 +1192,8 @@ UA_ClientConfig_setDefaultEncryption(UA_ClientConfig *config, if(retval != UA_STATUSCODE_GOOD) return retval; + if(config->certificateVerification.clear) + config->certificateVerification.clear(&config->certificateVerification); retval = UA_CertificateVerification_Trustlist(&config->certificateVerification, trustList, trustListSize, NULL, 0, diff --git a/src/ua_securechannel.c b/src/ua_securechannel.c index abf3151b47e..16203166173 100644 --- a/src/ua_securechannel.c +++ b/src/ua_securechannel.c @@ -73,6 +73,7 @@ hideErrors(UA_TcpErrorMessage *const error) { switch(error->error) { case UA_STATUSCODE_BADCERTIFICATEUNTRUSTED: case UA_STATUSCODE_BADCERTIFICATEREVOKED: + case UA_STATUSCODE_BADCERTIFICATEISSUERREVOKED: error->error = UA_STATUSCODE_BADSECURITYCHECKSFAILED; error->reason = UA_STRING_NULL; break;