diff --git a/.gitignore b/.gitignore index 407de7d62fd..88475918580 100644 --- a/.gitignore +++ b/.gitignore @@ -90,6 +90,8 @@ Pipfile.lock .vscode /.vs **/libxml2 +debian/* +/CMakeSettings.json # clangd cache .cache/ # project local vim settings diff --git a/CMakeLists.txt b/CMakeLists.txt index ceee2b3e6db..c55346504ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,7 +44,7 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) # overwritten with more detailed information if git is available. set(OPEN62541_VER_MAJOR 1) set(OPEN62541_VER_MINOR 4) -set(OPEN62541_VER_PATCH 7) +set(OPEN62541_VER_PATCH 8) set(OPEN62541_VER_LABEL "-undefined") # like "-rc1" or "-g4538abcd" or "-g4538abcd-dirty" set(OPEN62541_VER_COMMIT "unknown-commit") @@ -496,6 +496,11 @@ if(UA_ENABLE_TPM2_SECURITY) list(APPEND open62541_LIBRARIES ${TPM2_LIB}) endif() +if(MINGW) + # GCC stack protector support + list(APPEND open62541_LIBRARIES ws2_32 ssp) +endif() + ##################### # Compiler Settings # ##################### @@ -917,7 +922,8 @@ set(plugin_sources ${PROJECT_SOURCE_DIR}/plugins/ua_log_stdout.c ${PROJECT_SOURCE_DIR}/plugins/ua_nodestore_hashmap.c ${PROJECT_SOURCE_DIR}/plugins/ua_config_default.c ${PROJECT_SOURCE_DIR}/plugins/crypto/ua_certificategroup_none.c - ${PROJECT_SOURCE_DIR}/plugins/crypto/ua_securitypolicy_none.c) + ${PROJECT_SOURCE_DIR}/plugins/crypto/ua_securitypolicy_none.c + ${PROJECT_SOURCE_DIR}/plugins/ua_config_default.c) if(UA_ARCHITECTURE_POSIX OR UA_ARCHITECTURE_WIN32) list(APPEND plugin_sources @@ -1045,20 +1051,22 @@ endif() set(UA_FILE_NODESETS) # List of nodeset-xml files to be considered in the generated information model set(UA_NODESET_DIR ${PROJECT_SOURCE_DIR}/deps/ua-nodeset CACHE STRING "The path to the node-set directory (e.g. from https://github.com/OPCFoundation/UA-Nodeset)") +unset(UA_FILE_NS0_PRIVATE) +if(UA_FILE_NS0) + set(UA_FILE_NS0_PRIVATE "${UA_FILE_NS0}") +endif() + if(UA_NAMESPACE_ZERO STREQUAL "FULL") # Use the "full" schema files also for datatypes and statuscodes set(UA_SCHEMA_DIR ${UA_NODESET_DIR}/Schema CACHE INTERNAL "") - # Set the full Nodeset for NS0. - # Use a new variable UA_FILE_NS0_INTERNAL so we don't pollute the options. - if(UA_FILE_NS0) - set(UA_FILE_NS0_INTERNAL ${UA_FILE_NS0}) - else() - set(UA_FILE_NS0_INTERNAL ${UA_SCHEMA_DIR}/Opc.Ua.NodeSet2.xml CACHE INTERNAL "") + # Set the full Nodeset for NS0 + if(NOT UA_FILE_NS0_PRIVATE) + set(UA_FILE_NS0_PRIVATE ${UA_SCHEMA_DIR}/Opc.Ua.NodeSet2.xml) endif() # Check that the submodule was checked out or manually downloaded into the folder - if(NOT EXISTS "${UA_FILE_NS0_INTERNAL}") + if(NOT EXISTS "${UA_FILE_NS0_PRIVATE}") message(STATUS "Submodule update") execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} @@ -1071,12 +1079,9 @@ else() # Directory with the schema files for installation set(UA_SCHEMA_DIR ${PROJECT_SOURCE_DIR}/tools/schema CACHE INTERNAL "") - # Set the reduced Nodeset for NS0. - # Use a new variable UA_FILE_NS0_INTERNAL so we don't pollute the options. - if(UA_FILE_NS0) - set(UA_FILE_NS0_INTERNAL ${UA_FILE_NS0}) - else() - set(UA_FILE_NS0_INTERNAL ${UA_SCHEMA_DIR}/Opc.Ua.NodeSet2.Reduced.xml CACHE INTERNAL "") + # Set the reduced Nodeset for NS0 + if(NOT UA_FILE_NS0_PRIVATE) + set(UA_FILE_NS0_PRIVATE ${UA_SCHEMA_DIR}/Opc.Ua.NodeSet2.Reduced.xml CACHE INTERNAL "") endif() # Set feature-specific datatypes definitions and nodesets @@ -1135,7 +1140,7 @@ else() endif() endif() -list(INSERT UA_FILE_NODESETS 0 "${UA_FILE_NS0_INTERNAL}") +list(INSERT UA_FILE_NODESETS 0 "${UA_FILE_NS0_PRIVATE}") set(UA_FILE_NODEIDS ${UA_SCHEMA_DIR}/NodeIds.csv) set(UA_FILE_STATUSCODES ${UA_SCHEMA_DIR}/StatusCode.csv) set(UA_FILE_TYPES_BSD ${UA_SCHEMA_DIR}/Opc.Ua.Types.bsd) @@ -1161,7 +1166,6 @@ ua_generate_datatypes(INTERNAL NAME "transport" TARGET_SUFFIX "transport" NAMESP # statuscode explanation add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/open62541/statuscodes.h ${PROJECT_BINARY_DIR}/src_generated/open62541/statuscodes.c - PRE_BUILD COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/generate_statuscode_descriptions.py ${UA_FILE_STATUSCODES} ${PROJECT_BINARY_DIR}/src_generated/open62541/statuscodes DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/tools/generate_statuscode_descriptions.py @@ -1181,7 +1185,6 @@ add_custom_target(open62541-generator-statuscode DEPENDS if(UA_ENABLE_AMALGAMATION) # single-file release add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/open62541.h - PRE_BUILD COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/amalgamate.py ${OPEN62541_VERSION} ${CMAKE_CURRENT_BINARY_DIR}/open62541.h ${exported_headers} ${NODESETLOADER_PUBLIC_HEADERS} ${plugin_headers} @@ -1189,7 +1192,6 @@ if(UA_ENABLE_AMALGAMATION) ${exported_headers} ${plugin_headers}) add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/open62541.c - PRE_BUILD COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/amalgamate.py ${OPEN62541_VERSION} ${CMAKE_CURRENT_BINARY_DIR}/open62541.c ${lib_headers} ${NODESETLOADER_PRIVATE_HEADERS} ${lib_sources} ${plugin_sources} @@ -1216,7 +1218,7 @@ if(UA_ENABLE_NODESET_INJECTOR) message(STATUS "Nodesetinjector feature enabled") cmake_minimum_required(VERSION 3.20) add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/open62541/nodesetinjector.h - ${PROJECT_BINARY_DIR}/src_generated/open62541/nodesetinjector.c PRE_BUILD + ${PROJECT_BINARY_DIR}/src_generated/open62541/nodesetinjector.c COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/nodeset_injector/generate_nodesetinjector.py ${PROJECT_BINARY_DIR}/src_generated/open62541/nodesetinjector) add_custom_target(open62541-generator-nodesetinjector DEPENDS diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index ac7553d6c7a..a9da3e700ec 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -22,14 +22,12 @@ list(APPEND GENERATED_RST "") # Generated type definitions add_custom_command(OUTPUT ${DOC_SRC_DIR}/types_generated.rst COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_BINARY_DIR}/src_generated/open62541/types_generated.rst ${DOC_SRC_DIR} - PRE_BUILD DEPENDS ${PROJECT_BINARY_DIR}/src_generated/open62541/types_generated.rst) list(APPEND GENERATED_RST ${DOC_SRC_DIR}/types_generated.rst) macro(generate_rst in out) add_custom_command(OUTPUT ${out} DEPENDS ${PROJECT_SOURCE_DIR}/tools/c2rst.py ${in} - PRE_BUILD COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/c2rst.py ${in} ${out}) list(APPEND GENERATED_RST "${out}") endmacro() diff --git a/plugins/crypto/mbedtls/certificategroup.c b/plugins/crypto/mbedtls/certificategroup.c index a0eea29a469..6a359612f85 100644 --- a/plugins/crypto/mbedtls/certificategroup.c +++ b/plugins/crypto/mbedtls/certificategroup.c @@ -59,6 +59,8 @@ struct MemoryCertStore { mbedtls_x509_crt issuerCertificates; mbedtls_x509_crl trustedCrls; mbedtls_x509_crl issuerCrls; + + UA_CertificateGroup *cg; }; static UA_Boolean mbedtlsCheckCA(mbedtls_x509_crt *cert); @@ -372,10 +374,17 @@ mbedtlsSameName(UA_String name, const mbedtls_x509_name *name2) { return UA_String_equal(&name, &nameString); } +static UA_Boolean +mbedtlsSameBuf(mbedtls_x509_buf *a, mbedtls_x509_buf *b) { + if(a->len != b->len) + return false; + return (memcmp(a->p, b->p, a->len) == 0); +} + /* Return the first matching issuer candidate AFTER prev. * This can return the cert itself if self-signed. */ static mbedtls_x509_crt * -mbedtlsFindNextIssuer(MemoryCertStore *context, mbedtls_x509_crt *stack, +mbedtlsFindNextIssuer(CertInfo *ci, mbedtls_x509_crt *stack, mbedtls_x509_crt *cert, mbedtls_x509_crt *prev) { char inbuf[UA_MBEDTLS_MAX_DN_LENGTH]; int nameLen = mbedtls_x509_dn_gets(inbuf, UA_MBEDTLS_MAX_DN_LENGTH, &cert->issuer); @@ -395,30 +404,47 @@ mbedtlsFindNextIssuer(MemoryCertStore *context, mbedtls_x509_crt *stack, mbedtls_pk_can_do(&i->pk, cert->MBEDTLS_PRIVATE(sig_pk))) return i; } - /* Switch from the stack that came with the cert to the ctx->skIssue list */ - stack = (stack != &context->issuerCertificates) ? &context->issuerCertificates: NULL; + + /* Switch from the stack that came with the cert to the issuer list and + * then to the trust list. */ + if(stack == &ci->certificateTrustList) + stack = NULL; + else if(stack == &ci->certificateIssuerList) + stack = &ci->certificateTrustList; + else + stack = &ci->certificateIssuerList; } while(stack); return NULL; } -static UA_Boolean -mbedtlsCheckRevoked(MemoryCertStore *context, mbedtls_x509_crt *cert) { +static UA_StatusCode +mbedtlsCheckRevoked(CertInfo *ci, mbedtls_x509_crt *cert) { + /* Parse the Issuer Name */ char inbuf[UA_MBEDTLS_MAX_DN_LENGTH]; int nameLen = mbedtls_x509_dn_gets(inbuf, UA_MBEDTLS_MAX_DN_LENGTH, &cert->issuer); if(nameLen < 0) - return true; + return UA_STATUSCODE_BADINTERNALERROR; UA_String issuerName = {(size_t)nameLen, (UA_Byte*)inbuf}; - for(mbedtls_x509_crl *crl = &context->trustedCrls; crl; crl = crl->next) { - if(mbedtlsSameName(issuerName, &crl->issuer) && - mbedtls_x509_crt_is_revoked(cert, crl) != 0) - return true; + + if(ci->certificateRevocationList.raw.len == 0) { + UA_LOG_WARNING(ci->cv->logging, UA_LOGCATEGORY_SECURITYPOLICY, + "Zero revocation lists have been loaded. " + "This seems intentional - omitting the check."); + return UA_STATUSCODE_GOOD; } - for(mbedtls_x509_crl *crl = &context->issuerCrls; crl; crl = crl->next) { - if(mbedtlsSameName(issuerName, &crl->issuer) && - mbedtls_x509_crt_is_revoked(cert, crl) != 0) - return true; + + /* Loop over the crl and match the Issuer Name */ + UA_StatusCode res = UA_STATUSCODE_BADCERTIFICATEREVOCATIONUNKNOWN; + for(mbedtls_x509_crl *crl = &ci->certificateRevocationList; crl; crl = crl->next) { + /* Is the CRL for certificates from the cert issuer? + * Is the serial number of the certificate contained in the CRL? */ + if(mbedtlsSameName(issuerName, &crl->issuer)) { + if(mbedtls_x509_crt_is_revoked(cert, crl) != 0) + return UA_STATUSCODE_BADCERTIFICATEREVOKED; + res = UA_STATUSCODE_GOOD; /* There was at least one crl that did not revoke (so far) */ + } } - return false; + return res; } /* Verify that the public key of the issuer was used to sign the certificate */ @@ -445,7 +471,7 @@ mbedtlsCheckSignature(const mbedtls_x509_crt *cert, mbedtls_x509_crt *issuer) { } static UA_StatusCode -mbedtlsVerifyChain(MemoryCertStore *context, mbedtls_x509_crt *stack, mbedtls_x509_crt **old_issuers, +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) @@ -457,11 +483,6 @@ mbedtlsVerifyChain(MemoryCertStore *context, mbedtls_x509_crt *stack, mbedtls_x5 return (depth == 0) ? UA_STATUSCODE_BADCERTIFICATETIMEINVALID : UA_STATUSCODE_BADCERTIFICATEISSUERTIMEINVALID; - /* Verification Step: Revocation Check */ - if(mbedtlsCheckRevoked(context, 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; @@ -470,7 +491,7 @@ mbedtlsVerifyChain(MemoryCertStore *context, mbedtls_x509_crt *stack, mbedtls_x5 /* 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(context, stack, cert, issuer); + issuer = mbedtlsFindNextIssuer(ci, stack, cert, issuer); if(!issuer) break; @@ -491,16 +512,28 @@ mbedtlsVerifyChain(MemoryCertStore *context, mbedtls_x509_crt *stack, mbedtls_x5 * chain. We check whether the certificate is trusted below. This is the * only place where we return UA_STATUSCODE_BADCERTIFICATEUNTRUSTED. * This signals 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)) { + * untrusted). + * + * Break here as we have reached the end of the chain. Omit the + * Revocation Check for self-signed certificates. */ + if(issuer == cert || mbedtlsSameBuf(&cert->tbs, &issuer->tbs)) { ret = UA_STATUSCODE_BADCERTIFICATEUNTRUSTED; - continue; + break; + } + + /* Verification Step: Revocation Check */ + ret = mbedtlsCheckRevoked(ci, cert); + if(depth > 0) { + if(ret == UA_STATUSCODE_BADCERTIFICATEREVOKED) + ret = UA_STATUSCODE_BADCERTIFICATEISSUERREVOKED; + if(ret == UA_STATUSCODE_BADCERTIFICATEREVOCATIONUNKNOWN) + ret = UA_STATUSCODE_BADCERTIFICATEISSUERREVOCATIONUNKNOWN; } + if(ret != UA_STATUSCODE_GOOD) + 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++) { + /* Detect (endless) loops of issuers */ + for(int i = 0; i < depth; i++) { if(old_issuers[i] == issuer) return UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE; } @@ -508,15 +541,14 @@ mbedtlsVerifyChain(MemoryCertStore *context, mbedtls_x509_crt *stack, mbedtls_x5 /* We have found the issuer certificate used for the signature. Recurse * to the next certificate in the chain (verify the current issuer). */ - ret = mbedtlsVerifyChain(context, stack, old_issuers, issuer, depth + 1); + 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 = &context->trustedCertificates; t; t = t->next) { - if(cert->tbs.len == t->tbs.len && - memcmp(cert->tbs.p, t->tbs.p, cert->tbs.len) == 0) + for(mbedtls_x509_crt *t = &ci->certificateTrustList; t; t = t->next) { + if(mbedtlsSameBuf(&cert->tbs, &t->tbs)) return UA_STATUSCODE_GOOD; } } @@ -627,6 +659,7 @@ UA_CertificateGroup_Memorystore(UA_CertificateGroup *certGroup, retval = UA_STATUSCODE_BADOUTOFMEMORY; goto cleanup; } + context->cg = certGroup; certGroup->context = context; /* Default values */ context->maxTrustListSize = 65535; diff --git a/plugins/crypto/mbedtls/ua_mbedtls_create_certificate.c b/plugins/crypto/mbedtls/ua_mbedtls_create_certificate.c new file mode 100644 index 00000000000..c828c7c3ec4 --- /dev/null +++ b/plugins/crypto/mbedtls/ua_mbedtls_create_certificate.c @@ -0,0 +1,550 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2023 Fraunhofer IOSB (Author: Noel Graf) + * + */ + +#include +#include + +#include "securitypolicy_mbedtls_common.h" +#include "../../arch/eventloop_posix.h" + +#if defined(UA_ENABLE_ENCRYPTION_MBEDTLS) + +#include +#include +#include +#include +#include +#include +#include + +#define SET_OID(x, oid) \ + do { x.len = MBEDTLS_OID_SIZE(oid); x.p = (unsigned char *) oid; } while (0) + +#define MBEDTLS_ASN1_CHK_CLEANUP_ADD(g, f) \ + do \ + { \ + if ((ret = (f)) < 0) \ + goto cleanup; \ + else \ + (g) += ret; \ + } while (0) + +#if MBEDTLS_VERSION_NUMBER < 0x02170000 +#define MBEDTLS_X509_SAN_OTHER_NAME 0 +#define MBEDTLS_X509_SAN_RFC822_NAME 1 +#define MBEDTLS_X509_SAN_DNS_NAME 2 +#define MBEDTLS_X509_SAN_X400_ADDRESS_NAME 3 +#define MBEDTLS_X509_SAN_DIRECTORY_NAME 4 +#define MBEDTLS_X509_SAN_EDI_PARTY_NAME 5 +#define MBEDTLS_X509_SAN_UNIFORM_RESOURCE_IDENTIFIER 6 +#define MBEDTLS_X509_SAN_IP_ADDRESS 7 +#define MBEDTLS_X509_SAN_REGISTERED_ID 8 +#endif + +#define MBEDTLS_SAN_MAX_LEN 64 +typedef struct mbedtls_write_san_node{ + int type; + char* host; + size_t hostlen; +} mbedtls_write_san_node; + +typedef struct mbedtls_write_san_list{ + mbedtls_write_san_node node; + struct mbedtls_write_san_list* next; +} mbedtls_write_san_list; + +static size_t mbedtls_get_san_list_deep(const mbedtls_write_san_list* sanlist); + +int mbedtls_x509write_crt_set_subject_alt_name(mbedtls_x509write_cert *ctx, const mbedtls_write_san_list* sanlist); + +#if MBEDTLS_VERSION_NUMBER < 0x03030000 +int mbedtls_x509write_crt_set_ext_key_usage(mbedtls_x509write_cert *ctx, + const mbedtls_asn1_sequence *exts); +#endif + +static int write_certificate(mbedtls_x509write_cert *crt, UA_CertificateFormat certFormat, + UA_ByteString *outCertificate, int (*f_rng)(void *, unsigned char *, size_t), + void *p_rng); + +static int write_private_key(mbedtls_pk_context *key, UA_CertificateFormat keyFormat, UA_ByteString *outPrivateKey); + +UA_StatusCode +UA_CreateCertificate(const UA_Logger *logger, const UA_String *subject, + size_t subjectSize, const UA_String *subjectAltName, + size_t subjectAltNameSize, UA_CertificateFormat certFormat, + UA_KeyValueMap *params, UA_ByteString *outPrivateKey, + UA_ByteString *outCertificate) { + if(!outPrivateKey || !outCertificate || !logger || !subjectAltName || !subject || + subjectAltNameSize == 0 || subjectSize == 0 || + (certFormat != UA_CERTIFICATEFORMAT_DER && certFormat != UA_CERTIFICATEFORMAT_PEM)) + return UA_STATUSCODE_BADINVALIDARGUMENT; + + /* Use the maximum size */ + UA_UInt16 keySizeBits = 4096; + /* Default to 1 year */ + UA_UInt16 expiresInDays = 365; + + if(params) { + const UA_UInt16 *keySizeBitsValue = (const UA_UInt16 *)UA_KeyValueMap_getScalar( + params, UA_QUALIFIEDNAME(0, "key-size-bits"), &UA_TYPES[UA_TYPES_UINT16]); + if(keySizeBitsValue) + keySizeBits = *keySizeBitsValue; + + const UA_UInt16 *expiresInDaysValue = (const UA_UInt16 *)UA_KeyValueMap_getScalar( + params, UA_QUALIFIEDNAME(0, "expires-in-days"), &UA_TYPES[UA_TYPES_UINT16]); + if(expiresInDaysValue) + expiresInDays = *expiresInDaysValue; + } + + UA_ByteString_init(outPrivateKey); + UA_ByteString_init(outCertificate); + + mbedtls_pk_context key; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_entropy_context entropy; + const char *pers = "gen_key"; + mbedtls_x509write_cert crt; + + UA_StatusCode errRet = UA_STATUSCODE_GOOD; + + /* Set to sane values */ + mbedtls_pk_init(&key); + mbedtls_ctr_drbg_init(&ctr_drbg); + mbedtls_entropy_init(&entropy); + mbedtls_x509write_crt_init(&crt); + + /* Seed the random number generator */ + if (mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *)pers, strlen(pers)) != 0) { + UA_LOG_ERROR(logger, UA_LOGCATEGORY_SECURECHANNEL, + "Failed to initialize the random number generator."); + errRet = UA_STATUSCODE_BADINTERNALERROR; + goto cleanup; + } + + /* Generate an RSA key pair */ + if (mbedtls_pk_setup(&key, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)) != 0 || + mbedtls_rsa_gen_key(mbedtls_pk_rsa(key), mbedtls_ctr_drbg_random, &ctr_drbg, keySizeBits, 65537) != 0) { + UA_LOG_ERROR(logger, UA_LOGCATEGORY_SECURECHANNEL, + "Failed to generate RSA key pair."); + errRet = UA_STATUSCODE_BADINTERNALERROR; + goto cleanup; + } + + /* Setting certificate values */ + mbedtls_x509write_crt_set_version(&crt, MBEDTLS_X509_CRT_VERSION_3); + mbedtls_x509write_crt_set_md_alg(&crt, MBEDTLS_MD_SHA256); + + size_t subject_char_len = 0; + for(size_t i = 0; i < subjectSize; i++) { + subject_char_len += subject[i].length; + } + char *subject_char = (char*)UA_malloc(subject_char_len + subjectSize); + if(!subject_char) { + UA_LOG_ERROR(logger, UA_LOGCATEGORY_SECURECHANNEL, + "Cannot allocate memory for subject. Out of memory."); + errRet = UA_STATUSCODE_BADOUTOFMEMORY; + goto cleanup; + } + + size_t pos = 0; + for(size_t i = 0; i < subjectSize; i++) { + subject_char_len += subject[i].length; + memcpy(subject_char + pos, subject[i].data, subject[i].length); + pos += subject[i].length; + if(i < subjectSize - 1) + subject_char[pos++] = ','; + else + subject_char[pos++] = '\0'; + } + + if((mbedtls_x509write_crt_set_subject_name(&crt, subject_char)) != 0) { + UA_LOG_ERROR(logger, UA_LOGCATEGORY_SECURECHANNEL, + "Setting subject failed."); + errRet = UA_STATUSCODE_BADINTERNALERROR; + UA_free(subject_char); + goto cleanup; + } + + if((mbedtls_x509write_crt_set_issuer_name(&crt, subject_char)) != 0) { + UA_LOG_ERROR(logger, UA_LOGCATEGORY_SECURECHANNEL, + "Setting issuer failed."); + errRet = UA_STATUSCODE_BADINTERNALERROR; + UA_free(subject_char); + goto cleanup; + } + + UA_free(subject_char); + + mbedtls_write_san_list *cur = NULL; + mbedtls_write_san_list *cur_tmp = NULL; + mbedtls_write_san_list *head = NULL; + for(size_t i = 0; i < subjectAltNameSize; i++) { + char *sanType; + char *sanValue; + size_t sanValueLength; + char *subAlt = (char *)UA_malloc(subjectAltName[i].length + 1); + memcpy(subAlt, subjectAltName[i].data, subjectAltName[i].length); + + /* null-terminate the copied string */ + subAlt[subjectAltName[i].length] = 0; + /* split into SAN type and value */ + sanType = strtok(subAlt, ":"); + sanValue = (char *)subjectAltName[i].data + strlen(sanType) + 1; + sanValueLength = strlen(sanValue); + + if(sanType) { + cur_tmp = (mbedtls_write_san_list*)mbedtls_calloc(1, sizeof(mbedtls_write_san_list)); + cur_tmp->next = NULL; + cur_tmp->node.host = sanValue; + cur_tmp->node.hostlen = sanValueLength; + + if(strcmp(sanType, "DNS") == 0) { + cur_tmp->node.type = MBEDTLS_X509_SAN_DNS_NAME; + } else if(strcmp(sanType, "URI") == 0) { + cur_tmp->node.type = MBEDTLS_X509_SAN_UNIFORM_RESOURCE_IDENTIFIER; + } else if(strcmp(sanType, "IP") == 0) { + uint8_t ip[4] = {0}; + if(UA_inet_pton(AF_INET, sanValue, ip) <= 0) { + UA_LOG_WARNING(logger, UA_LOGCATEGORY_SECURECHANNEL, "IP SAN preparation failed"); + mbedtls_free(cur_tmp); + UA_free(subAlt); + continue; + } + cur_tmp->node.type = MBEDTLS_X509_SAN_IP_ADDRESS; + cur_tmp->node.host = (char *)ip; + cur_tmp->node.hostlen = sizeof(ip); + } else if(strcmp(sanType, "RFC822") == 0) { + cur_tmp->node.type = MBEDTLS_X509_SAN_RFC822_NAME; + } else { + UA_LOG_WARNING(logger, UA_LOGCATEGORY_SECURECHANNEL, "Given an unsupported SAN"); + mbedtls_free(cur_tmp); + UA_free(subAlt); + continue; + } + } else { + UA_LOG_WARNING(logger, UA_LOGCATEGORY_SECURECHANNEL, "Invalid Input format"); + UA_free(subAlt); + continue; + } + + if(!cur) { + cur = cur_tmp; + head = cur_tmp; + } else { + cur->next = cur_tmp; + cur = cur->next; + } + + UA_free(subAlt); + } + + if((mbedtls_x509write_crt_set_subject_alt_name(&crt, head)) != 0) { + UA_LOG_ERROR(logger, UA_LOGCATEGORY_SECURECHANNEL, + "Setting subject alternative name failed."); + errRet = UA_STATUSCODE_BADINTERNALERROR; + while(head != NULL) { + cur_tmp = head->next; + mbedtls_free(head); + head = cur_tmp; + } + goto cleanup; + } + + while(head != NULL) { + cur_tmp = head->next; + mbedtls_free(head); + head = cur_tmp; + } + +#if MBEDTLS_VERSION_NUMBER >= 0x03040000 + unsigned char *serial = (unsigned char *)"1"; + size_t serial_len = 1; + mbedtls_x509write_crt_set_serial_raw(&crt, serial, serial_len); +#else + mbedtls_mpi serial_mpi; + mbedtls_mpi_init(&serial_mpi); + mbedtls_mpi_lset(&serial_mpi, 1); + mbedtls_x509write_crt_set_serial(&crt, &serial_mpi); + mbedtls_mpi_free(&serial_mpi); +#endif + + /* Get the current time */ + time_t rawTime; + struct tm *timeInfo; + time(&rawTime); + timeInfo = gmtime(&rawTime); + + /* Format the current timestamp */ + char current_timestamp[15]; // YYYYMMDDhhmmss + '\0' + strftime(current_timestamp, sizeof(current_timestamp), "%Y%m%d%H%M%S", timeInfo); + + /* Calculate the future timestamp */ + timeInfo->tm_mday += expiresInDays; + time_t future_time = mktime(timeInfo); + + /* Format the future timestamp */ + char future_timestamp[15]; // YYYYMMDDhhmmss + '\0' + strftime(future_timestamp, sizeof(future_timestamp), "%Y%m%d%H%M%S", gmtime(&future_time)); + + if(mbedtls_x509write_crt_set_validity(&crt, current_timestamp, future_timestamp) != 0) { + UA_LOG_ERROR(logger, UA_LOGCATEGORY_SECURECHANNEL, + "Setting 'not before' and 'not after' failed."); + errRet = UA_STATUSCODE_BADINTERNALERROR; + goto cleanup; + } + + if(mbedtls_x509write_crt_set_basic_constraints(&crt, 0, -1) != 0) { + UA_LOG_ERROR(logger, UA_LOGCATEGORY_SECURECHANNEL, + "Setting basic constraints failed."); + errRet = UA_STATUSCODE_BADINTERNALERROR; + goto cleanup; + } + + if(mbedtls_x509write_crt_set_key_usage(&crt, MBEDTLS_X509_KU_DIGITAL_SIGNATURE | MBEDTLS_X509_KU_NON_REPUDIATION + | MBEDTLS_X509_KU_KEY_ENCIPHERMENT | MBEDTLS_X509_KU_DATA_ENCIPHERMENT + | MBEDTLS_X509_KU_KEY_CERT_SIGN | MBEDTLS_X509_KU_CRL_SIGN) != 0) { + UA_LOG_ERROR(logger, UA_LOGCATEGORY_SECURECHANNEL, + "Setting key usage failed."); + errRet = UA_STATUSCODE_BADINTERNALERROR; + goto cleanup; + } + + mbedtls_asn1_sequence *ext_key_usage; + ext_key_usage = (mbedtls_asn1_sequence *)mbedtls_calloc(1, sizeof(mbedtls_asn1_sequence)); + ext_key_usage->buf.tag = MBEDTLS_ASN1_OID; + SET_OID(ext_key_usage->buf, MBEDTLS_OID_SERVER_AUTH); + ext_key_usage->next = (mbedtls_asn1_sequence *)mbedtls_calloc(1, sizeof(mbedtls_asn1_sequence)); + ext_key_usage->next->buf.tag = MBEDTLS_ASN1_OID; + SET_OID(ext_key_usage->next->buf, MBEDTLS_OID_CLIENT_AUTH); + + if(mbedtls_x509write_crt_set_ext_key_usage(&crt, ext_key_usage) != 0) { + UA_LOG_ERROR(logger, UA_LOGCATEGORY_SECURECHANNEL, + "Setting extended key usage failed."); + errRet = UA_STATUSCODE_BADINTERNALERROR; + mbedtls_free(ext_key_usage->next); + mbedtls_free(ext_key_usage); + goto cleanup; + } + + mbedtls_free(ext_key_usage->next); + mbedtls_free(ext_key_usage); + + mbedtls_x509write_crt_set_subject_key(&crt, &key); + mbedtls_x509write_crt_set_issuer_key(&crt, &key); + + + /* Write private key */ + if ((write_private_key(&key, certFormat, outPrivateKey)) != 0) { + UA_LOG_ERROR(logger, UA_LOGCATEGORY_SECURECHANNEL, + "Create Certificate: Writing private key failed."); + errRet = UA_STATUSCODE_BADINTERNALERROR; + goto cleanup; + } + + /* Write Certificate */ + if ((write_certificate(&crt, certFormat, outCertificate, + mbedtls_ctr_drbg_random, &ctr_drbg)) != 0) { + UA_LOG_ERROR(logger, UA_LOGCATEGORY_SECURECHANNEL, + "Create Certificate: Writing certificate failed."); + errRet = UA_STATUSCODE_BADINTERNALERROR; + goto cleanup; + } + + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + mbedtls_x509write_crt_free(&crt); + mbedtls_pk_free(&key); + +cleanup: + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + mbedtls_x509write_crt_free(&crt); + mbedtls_pk_free(&key); + return errRet; +} + +static int write_private_key(mbedtls_pk_context *key, UA_CertificateFormat keyFormat, UA_ByteString *outPrivateKey) { + int ret; + unsigned char output_buf[16000]; + unsigned char *c = output_buf; + size_t len = 0; + + memset(output_buf, 0, 16000); + switch(keyFormat) { + case UA_CERTIFICATEFORMAT_DER: { + if((ret = mbedtls_pk_write_key_pem(key, output_buf, 16000)) != 0) { + return ret; + } + + len = strlen((char *) output_buf); + break; + } + case UA_CERTIFICATEFORMAT_PEM: { + if((ret = mbedtls_pk_write_key_der(key, output_buf, 16000)) < 0) { + return ret; + } + + len = ret; + c = output_buf + sizeof(output_buf) - len; + break; + } + } + + outPrivateKey->length = len; + UA_ByteString_allocBuffer(outPrivateKey, outPrivateKey->length); + memcpy(outPrivateKey->data, c, outPrivateKey->length); + + return 0; +} + +static int write_certificate(mbedtls_x509write_cert *crt, UA_CertificateFormat certFormat, + UA_ByteString *outCertificate, int (*f_rng)(void *, unsigned char *, size_t), + void *p_rng) { + int ret; + unsigned char output_buf[4096]; + unsigned char *c = output_buf; + size_t len = 0; + + memset(output_buf, 0, 4096); + switch(certFormat) { + case UA_CERTIFICATEFORMAT_DER: { + if((ret = mbedtls_x509write_crt_der(crt, output_buf, 4096, f_rng, p_rng)) < 0) { + return ret; + } + + len = ret; + c = output_buf + 4096 - len; + break; + } + case UA_CERTIFICATEFORMAT_PEM: { + if((ret = mbedtls_x509write_crt_pem(crt, output_buf, 4096, f_rng, p_rng)) < 0) { + return ret; + } + + len = strlen((char *)output_buf); + break; + } + } + + outCertificate->length = len; + UA_ByteString_allocBuffer(outCertificate, outCertificate->length); + memcpy(outCertificate->data, c, outCertificate->length); + + return 0; +} + +#if MBEDTLS_VERSION_NUMBER < 0x03030000 +int mbedtls_x509write_crt_set_ext_key_usage(mbedtls_x509write_cert *ctx, + const mbedtls_asn1_sequence *exts) { + unsigned char buf[256]; + unsigned char *c = buf + sizeof(buf); + int ret; + size_t len = 0; + const mbedtls_asn1_sequence *last_ext = NULL; + const mbedtls_asn1_sequence *ext; + + memset(buf, 0, sizeof(buf)); + + /* We need at least one extension: SEQUENCE SIZE (1..MAX) OF KeyPurposeId */ + if(!exts) { + return MBEDTLS_ERR_X509_BAD_INPUT_DATA; + } + + /* Iterate over exts backwards, so we write them out in the requested order */ + while(last_ext != exts) { + for(ext = exts; ext->next != last_ext; ext = ext->next) { + } + if(ext->buf.tag != MBEDTLS_ASN1_OID) { + return MBEDTLS_ERR_X509_BAD_INPUT_DATA; + } + MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_raw_buffer(&c, buf, ext->buf.p, ext->buf.len)); + MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_len(&c, buf, ext->buf.len)); + MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_OID)); + last_ext = ext; + } + + MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_len(&c, buf, len)); + MBEDTLS_ASN1_CHK_ADD(len, mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)); + + return mbedtls_x509write_crt_set_extension(ctx, MBEDTLS_OID_EXTENDED_KEY_USAGE, + MBEDTLS_OID_SIZE(MBEDTLS_OID_EXTENDED_KEY_USAGE), 1, c, len); +} + +#endif + +static size_t mbedtls_get_san_list_deep(const mbedtls_write_san_list* sanlist) { + size_t ret = 0; + const mbedtls_write_san_list* cur = sanlist; + while (cur) { + ++ret; + cur = cur->next; + } + + return ret; +} + +int mbedtls_x509write_crt_set_subject_alt_name(mbedtls_x509write_cert *ctx, const mbedtls_write_san_list* sanlist) { + int ret = 0; + size_t sandeep = 0; + const mbedtls_write_san_list* cur = sanlist; + unsigned char* buf; + unsigned char* pc; + size_t len; + size_t buflen = 0; + + /* How many alt names to be written */ + sandeep = mbedtls_get_san_list_deep(sanlist); + if (sandeep == 0) + return ret; + + buflen = MBEDTLS_SAN_MAX_LEN * sandeep + sandeep; + buf = (unsigned char *)mbedtls_calloc(1, buflen); + if(!buf) + return MBEDTLS_ERR_ASN1_ALLOC_FAILED; + + memset(buf, 0, buflen); + pc = buf + buflen; + + len = 0; + while(cur) { + switch (cur->node.type) { + case MBEDTLS_X509_SAN_DNS_NAME: + case MBEDTLS_X509_SAN_RFC822_NAME: + case MBEDTLS_X509_SAN_UNIFORM_RESOURCE_IDENTIFIER: + case MBEDTLS_X509_SAN_IP_ADDRESS: + MBEDTLS_ASN1_CHK_CLEANUP_ADD(len, + mbedtls_asn1_write_raw_buffer(&pc, buf, (const unsigned char *)cur->node.host, + cur->node.hostlen)); + MBEDTLS_ASN1_CHK_CLEANUP_ADD(len, mbedtls_asn1_write_len(&pc, buf, cur->node.hostlen)); + MBEDTLS_ASN1_CHK_CLEANUP_ADD(len, mbedtls_asn1_write_tag(&pc, buf, + MBEDTLS_ASN1_CONTEXT_SPECIFIC | cur->node.type)); + break; + default: + /* Error out on an unsupported SAN */ + ret = MBEDTLS_ERR_X509_FEATURE_UNAVAILABLE; + goto cleanup; + } + + cur = cur->next; + } + + MBEDTLS_ASN1_CHK_CLEANUP_ADD(len, mbedtls_asn1_write_len(&pc, buf, len)); + MBEDTLS_ASN1_CHK_CLEANUP_ADD(len, mbedtls_asn1_write_tag(&pc, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)); + + ret = mbedtls_x509write_crt_set_extension(ctx, MBEDTLS_OID_SUBJECT_ALT_NAME, + MBEDTLS_OID_SIZE(MBEDTLS_OID_SUBJECT_ALT_NAME), 0, buf + buflen - len, len); + + mbedtls_free(buf); + return ret; + +cleanup: + mbedtls_free(buf); + return ret; +} + +#endif diff --git a/plugins/crypto/openssl/certificategroup.c b/plugins/crypto/openssl/certificategroup.c index 111b6151938..a374a53b426 100644 --- a/plugins/crypto/openssl/certificategroup.c +++ b/plugins/crypto/openssl/certificategroup.c @@ -53,8 +53,13 @@ struct MemoryCertStore { STACK_OF(X509) *trustedCertificates; STACK_OF(X509) *issuerCertificates; STACK_OF(X509_CRL) *crls; + + UA_CertificateGroup *cg; }; +static UA_Boolean +openSSLCheckCA(X509 *cert); + static UA_StatusCode MemoryCertStore_removeFromTrustList(UA_CertificateGroup *certGroup, const UA_TrustListDataType *trustList) { /* Check parameter */ @@ -155,7 +160,7 @@ openSSLFindCrls(UA_CertificateGroup *certGroup, const UA_ByteString *certificate /* Check if the certificate is a CA certificate. * Only a CA certificate can have a CRL. */ - if(!X509_check_ca(cert)) { + if(!openSSLCheckCA(cert)) { UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SERVER, "The certificate is not a CA certificate and therefore does not have a CRL."); X509_free(cert); @@ -449,6 +454,7 @@ static X509 * openSSLFindNextIssuer(MemoryCertStore *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. */ + X509_NAME *in = X509_get_issuer_name(x509); do { int size = sk_X509_num(stack); for(int i = 0; i < size; i++) { @@ -461,20 +467,56 @@ openSSLFindNextIssuer(MemoryCertStore *ctx, STACK_OF(X509) *stack, X509 *x509, X /* This checks subject/issuer name and the key usage of the issuer. * It does not verify the validity period and if the issuer key was * used for the signature. We check that afterwards. */ - if(X509_check_issued(candidate, x509) == 0) + if(X509_NAME_cmp(in, X509_get_subject_name(candidate)) == 0) return candidate; } - /* Switch to search in the ctx->skIssue list */ - stack = (stack != ctx->issuerCertificates) ? ctx->issuerCertificates : NULL; + /* Switch from the stack that came with the cert to the issuer list and + * then to the trust list. */ + if(stack == ctx->trustedCertificates) + stack = NULL; + else if(stack == ctx->issuerCertificates) + stack = ctx->trustedCertificates; + else + stack = ctx->issuerCertificates; } while(stack); return NULL; } +/* Is the certificate a CA? */ static UA_Boolean +openSSLCheckCA(X509 *cert) { + uint32_t flags = X509_get_extension_flags(cert); + /* The basic constraints must be set with the CA flag true */ + if(!(flags & EXFLAG_CA)) + return false; + + /* The Key Usage extension must be set */ + if(!(flags & EXFLAG_KUSAGE)) + return false; + + /* The Key Usage must include cert signing and CRL issuing */ + uint32_t usage = X509_get_key_usage(cert); + if(!(usage & KU_KEY_CERT_SIGN) || !(usage & KU_CRL_SIGN)) + return false; + + return true; +} + +static UA_StatusCode openSSLCheckRevoked(MemoryCertStore *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->crls); + + if(size == 0) { + UA_LOG_WARNING(ctx->cg->logging, UA_LOGCATEGORY_SECURITYPOLICY, + "Zero revocation lists have been loaded. " + "This seems intentional - omitting the check."); + return UA_STATUSCODE_GOOD; + } + + /* Loop over the crl and match the Issuer Name */ + UA_StatusCode res = UA_STATUSCODE_BADCERTIFICATEREVOCATIONUNKNOWN; 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->crls, i); @@ -485,10 +527,11 @@ openSSLCheckRevoked(MemoryCertStore *ctx, X509 *cert) { 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; + return UA_STATUSCODE_BADCERTIFICATEREVOKED; } + res = UA_STATUSCODE_GOOD; /* There was at least one crl that did not revoke (so far) */ } - return false; + return res; } #define UA_OPENSSL_MAX_CHAIN_LENGTH 10 @@ -507,11 +550,6 @@ openSSL_verifyChain(MemoryCertStore *ctx, STACK_OF(X509) *stack, X509 **old_issu 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; @@ -525,7 +563,7 @@ openSSL_verifyChain(MemoryCertStore *ctx, STACK_OF(X509) *stack, X509 **old_issu /* Verification Step: Certificate Usage * Can the issuer act as CA? Omit for self-signed leaf certificates. */ - if((depth > 0 || issuer != cert) && !X509_check_ca(issuer)) { + if((depth > 0 || issuer != cert) && !openSSLCheckCA(issuer)) { ret = UA_STATUSCODE_BADCERTIFICATEISSUERUSENOTALLOWED; continue; } @@ -543,11 +581,25 @@ openSSL_verifyChain(MemoryCertStore *ctx, STACK_OF(X509) *stack, X509 **old_issu * chain. We check whether the certificate is trusted below. This is the * only place where we return UA_STATUSCODE_BADCERTIFICATEUNTRUSTED. * This signals that the chain is complete (but can be still - * untrusted). */ + * untrusted). + * + * Break here as we have reached the end of the chain. Omit the + * Revocation Check for self-signed certificates. */ if(cert == issuer || X509_cmp(cert, issuer) == 0) { ret = UA_STATUSCODE_BADCERTIFICATEUNTRUSTED; - continue; + break; + } + + /* Verification Step: Revocation Check */ + ret = openSSLCheckRevoked(ctx, cert); + if(depth > 0) { + if(ret == UA_STATUSCODE_BADCERTIFICATEREVOKED) + ret = UA_STATUSCODE_BADCERTIFICATEISSUERREVOKED; + if(ret == UA_STATUSCODE_BADCERTIFICATEREVOCATIONUNKNOWN) + ret = UA_STATUSCODE_BADCERTIFICATEISSUERREVOCATIONUNKNOWN; } + if(ret != UA_STATUSCODE_GOOD) + continue; /* Detect (endless) loops of issuers. The last one can be skipped by the * check for self-signed just before. */ @@ -582,20 +634,19 @@ verifyCertificate(UA_CertificateGroup *certGroup, const UA_ByteString *certifica return UA_STATUSCODE_BADINTERNALERROR; } + UA_StatusCode ret = UA_STATUSCODE_GOOD; MemoryCertStore *context = (MemoryCertStore *)certGroup->context; if(context->reloadRequired) { - UA_StatusCode retval = reloadCertificates(certGroup); - if(retval != UA_STATUSCODE_GOOD) { - return retval; - } + ret = reloadCertificates(certGroup); + if(ret != UA_STATUSCODE_GOOD) + return ret; context->reloadRequired = false; } /* 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); + sk_X509_pop_free(stack, X509_free); return UA_STATUSCODE_BADCERTIFICATEINVALID; } @@ -604,7 +655,7 @@ verifyCertificate(UA_CertificateGroup *certGroup, const UA_ByteString *certifica * Refer the test case CTT/Security/Security Certificate Validation/029.js * for more details. */ X509 *leaf = sk_X509_value(stack, 0); - if(X509_check_ca(leaf)) { + if(openSSLCheckCA(leaf)) { sk_X509_pop_free(stack, X509_free); return UA_STATUSCODE_BADCERTIFICATEUSENOTALLOWED; } @@ -618,7 +669,7 @@ verifyCertificate(UA_CertificateGroup *certGroup, const UA_ByteString *certifica /* Verification Step: Build Certificate Chain * We perform the checks for each certificate inside. */ X509 *old_issuers[UA_OPENSSL_MAX_CHAIN_LENGTH]; - UA_StatusCode ret = openSSL_verifyChain(context, stack, old_issuers, leaf, 0); + ret = openSSL_verifyChain(context, stack, old_issuers, leaf, 0); sk_X509_pop_free(stack, X509_free); return ret; } @@ -676,6 +727,7 @@ UA_CertificateGroup_Memorystore(UA_CertificateGroup *certGroup, retval = UA_STATUSCODE_BADOUTOFMEMORY; goto cleanup; } + context->cg = certGroup; certGroup->context = context; /* Default values */ context->maxTrustListSize = 65535; @@ -1033,7 +1085,8 @@ UA_CertificateUtils_checkCA(const UA_ByteString *certificate) { if(!certificateX509) return UA_STATUSCODE_BADSECURITYCHECKSFAILED; - UA_StatusCode retval = X509_check_ca(certificateX509) ? UA_STATUSCODE_GOOD : UA_STATUSCODE_BADNOMATCH; + UA_StatusCode retval = openSSLCheckCA(certificateX509) ? + UA_STATUSCODE_GOOD : UA_STATUSCODE_BADNOMATCH; X509_free(certificateX509); return retval; } diff --git a/src/client/ua_client.c b/src/client/ua_client.c index 27a1771b443..fed71340a04 100644 --- a/src/client/ua_client.c +++ b/src/client/ua_client.c @@ -506,17 +506,19 @@ processMSGResponse(UA_Client *client, UA_UInt32 requestId, UA_clear(response, ac->responseType); UA_free(ac); } else { + /* Return a special status code after processing a synchronous message. + * This makes the client return control immediately. */ ac->syncResponse = NULL; /* Indicate that response was received */ + if(retval == UA_STATUSCODE_GOOD) + retval = UA_STATUSCODE_GOODCOMPLETESASYNCHRONOUSLY; } return retval; } UA_StatusCode -processServiceResponse(void *application, UA_SecureChannel *channel, +processServiceResponse(UA_Client *client, UA_SecureChannel *channel, UA_MessageType messageType, UA_UInt32 requestId, UA_ByteString *message) { - UA_Client *client = (UA_Client*)application; - if(!UA_SecureChannel_isConnected(channel)) { if(messageType == UA_MESSAGETYPE_MSG) { UA_LOG_DEBUG_CHANNEL(client->config.logging, channel, "Discard MSG message " diff --git a/src/client/ua_client_connect.c b/src/client/ua_client_connect.c index 03171b87b5a..6922c71ddc0 100644 --- a/src/client/ua_client_connect.c +++ b/src/client/ua_client_connect.c @@ -1498,6 +1498,9 @@ verifyClientApplicationURI(const UA_Client *client) { #endif } +static void +delayedNetworkCallback(void *application, void *context); + static void __Client_networkCallback(UA_ConnectionManager *cm, uintptr_t connectionId, void *application, void **connectionContext, @@ -1576,13 +1579,42 @@ __Client_networkCallback(UA_ConnectionManager *cm, uintptr_t connectionId, client->channel.state = UA_SECURECHANNELSTATE_CONNECTING; } - /* Received a message. Process the message with the SecureChannel. */ UA_EventLoop *el = client->config.eventLoop; UA_DateTime nowMonotonic = el->dateTime_nowMonotonic(el); - UA_StatusCode res = - UA_SecureChannel_processBuffer(&client->channel, client, - processServiceResponse, - &msg, nowMonotonic); + + /* Received a message. Process the message with the SecureChannel. */ + UA_StatusCode res = UA_SecureChannel_loadBuffer(&client->channel, msg); + while(UA_LIKELY(res == UA_STATUSCODE_GOOD)) { + UA_MessageType messageType; + UA_UInt32 requestId = 0; + UA_ByteString payload = UA_BYTESTRING_NULL; + UA_Boolean copied = false; + res = UA_SecureChannel_getCompleteMessage(&client->channel, &messageType, &requestId, + &payload, &copied, nowMonotonic); + if(res != UA_STATUSCODE_GOOD || payload.length == 0) + break; + res = processServiceResponse(client, &client->channel, + messageType, requestId, &payload); + if(copied) + UA_ByteString_clear(&payload); + + /* Abort after synchronous processing of a message. + * Add a delayed callback to process the remaining buffer ASAP. */ + if(res == UA_STATUSCODE_GOODCOMPLETESASYNCHRONOUSLY) { + if(client->channel.unprocessed.length > client->channel.unprocessedOffset && + client->channel.unprocessedDelayed.callback == NULL) { + client->channel.unprocessedDelayed.callback = delayedNetworkCallback; + client->channel.unprocessedDelayed.application = client; + client->channel.unprocessedDelayed.context = &client->channel; + UA_EventLoop *el = client->config.eventLoop; + el->addDelayedCallback(el, &client->channel.unprocessedDelayed); + } + res = UA_STATUSCODE_GOOD; + break; + } + } + res |= UA_SecureChannel_persistBuffer(&client->channel); + if(res != UA_STATUSCODE_GOOD) { UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT, "Processing the message returned the error code %s", @@ -1614,6 +1646,18 @@ __Client_networkCallback(UA_ConnectionManager *cm, uintptr_t connectionId, UA_UNLOCK(&client->clientMutex); } +static void +delayedNetworkCallback(void *application, void *context) { + UA_Client *client = (UA_Client*)application; + client->channel.unprocessedDelayed.callback = NULL; + if(client->channel.state == UA_SECURECHANNELSTATE_CONNECTED) + __Client_networkCallback(client->channel.connectionManager, + client->channel.connectionId, + client, &context, + UA_CONNECTIONSTATE_ESTABLISHED, + &UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL); +} + /* Initialize a TCP connection. Writes the result to client->connectStatus. */ static void initConnect(UA_Client *client) { @@ -1645,6 +1689,7 @@ initConnect(UA_Client *client) { client->channel.config = client->config.localConnectionConfig; client->channel.certificateVerification = &client->config.certificateVerification; client->channel.processOPNHeader = verifyClientSecureChannelHeader; + client->channel.processOPNHeaderApplication = client; /* Initialize the SecurityPolicy */ client->connectStatus = initSecurityPolicy(client); @@ -2052,6 +2097,7 @@ UA_Client_startListeningForReverseConnect(UA_Client *client, client->channel.config = client->config.localConnectionConfig; client->channel.certificateVerification = &client->config.certificateVerification; client->channel.processOPNHeader = verifyClientSecureChannelHeader; + client->channel.processOPNHeaderApplication = client; client->channel.connectionId = 0; client->connectStatus = initSecurityPolicy(client); diff --git a/src/client/ua_client_internal.h b/src/client/ua_client_internal.h index 0b64f2d9948..45272b67729 100644 --- a/src/client/ua_client_internal.h +++ b/src/client/ua_client_internal.h @@ -196,7 +196,7 @@ UA_StatusCode __Client_renewSecureChannel(UA_Client *client); UA_StatusCode -processServiceResponse(void *application, UA_SecureChannel *channel, +processServiceResponse(UA_Client *client, UA_SecureChannel *channel, UA_MessageType messageType, UA_UInt32 requestId, UA_ByteString *message); diff --git a/src/server/ua_server_binary.c b/src/server/ua_server_binary.c index 7efd2b0db53..9e2f54b5551 100644 --- a/src/server/ua_server_binary.c +++ b/src/server/ua_server_binary.c @@ -490,11 +490,9 @@ processMSG(UA_Server *server, UA_SecureChannel *channel, /* Takes decoded messages starting at the nodeid of the content type. */ static UA_StatusCode -processSecureChannelMessage(void *application, UA_SecureChannel *channel, +processSecureChannelMessage(UA_Server *server, UA_SecureChannel *channel, UA_MessageType messagetype, UA_UInt32 requestId, UA_ByteString *message) { - UA_Server *server = (UA_Server*)application; - UA_StatusCode retval = UA_STATUSCODE_GOOD; switch(messagetype) { case UA_MESSAGETYPE_HEL: @@ -572,9 +570,12 @@ purgeFirstChannelWithoutSession(UA_BinaryProtocolManager *bpm) { static UA_StatusCode configServerSecureChannel(void *application, UA_SecureChannel *channel, const UA_AsymmetricAlgorithmSecurityHeader *asymHeader) { + if(channel->securityPolicy) + return UA_STATUSCODE_GOOD; + /* Iterate over available endpoints and choose the correct one */ + UA_Server *server = (UA_Server *)application; UA_SecurityPolicy *securityPolicy = NULL; - UA_Server *const server = (UA_Server *const) application; for(size_t i = 0; i < server->config.securityPoliciesSize; ++i) { UA_SecurityPolicy *policy = &server->config.securityPolicies[i]; if(!UA_String_equal(&asymHeader->securityPolicyUri, &policy->policyUri)) @@ -644,6 +645,7 @@ createServerSecureChannel(UA_BinaryProtocolManager *bpm, UA_ConnectionManager *c channel->config = connConfig; channel->certificateVerification = &config->secureChannelPKI; channel->processOPNHeader = configServerSecureChannel; + channel->processOPNHeaderApplication = server; channel->connectionManager = cm; channel->connectionId = connectionId; @@ -795,17 +797,15 @@ serverNetworkCallback(UA_ConnectionManager *cm, uintptr_t connectionId, return; } - UA_LOG_INFO_CHANNEL(bpm->logging, channel, "SecureChannel created"); - /* Set the new channel as the new context for the connection */ *connectionContext = (void*)channel; - return; - } - /* The connection has fully opened */ - if(channel->state < UA_SECURECHANNELSTATE_CONNECTED) + /* Set the channel state to CONNECTED until the HEL message is received */ channel->state = UA_SECURECHANNELSTATE_CONNECTED; + UA_LOG_INFO_CHANNEL(bpm->logging, channel, "SecureChannel created"); + } + /* Received a message on a normal connection */ #ifdef UA_DEBUG_DUMP_PKGS UA_dump_hex_pkg(message->data, message->length); @@ -816,9 +816,25 @@ serverNetworkCallback(UA_ConnectionManager *cm, uintptr_t connectionId, UA_EventLoop *el = bpm->sc.server->config.eventLoop; UA_DateTime nowMonotonic = el->dateTime_nowMonotonic(el); - retval = UA_SecureChannel_processBuffer(channel, bpm->sc.server, - processSecureChannelMessage, - &msg, nowMonotonic); + + /* Process all complete messages */ + retval = UA_SecureChannel_loadBuffer(channel, msg); + while(UA_LIKELY(retval == UA_STATUSCODE_GOOD)) { + UA_MessageType messageType; + UA_UInt32 requestId = 0; + UA_ByteString payload = UA_BYTESTRING_NULL; + UA_Boolean copied = false; + retval = UA_SecureChannel_getCompleteMessage(channel, &messageType, &requestId, + &payload, &copied, nowMonotonic); + if(retval != UA_STATUSCODE_GOOD || payload.length == 0) + break; + retval = processSecureChannelMessage(bpm->sc.server, channel, + messageType, requestId, &payload); + if(copied) + UA_ByteString_clear(&payload); + } + retval |= UA_SecureChannel_persistBuffer(channel); + if(retval != UA_STATUSCODE_GOOD) { UA_LOG_WARNING_CHANNEL(bpm->logging, channel, "Processing the message failed with error %s", @@ -1256,13 +1272,28 @@ serverReverseConnectCallback(UA_ConnectionManager *cm, uintptr_t connectionId, return; } - /* The connection is fully opened and we have a SecureChannel. - * Process the received buffer */ UA_EventLoop *el = bpm->sc.server->config.eventLoop; UA_DateTime nowMonotonic = el->dateTime_nowMonotonic(el); - retval = UA_SecureChannel_processBuffer(context->channel, bpm->sc.server, - processSecureChannelMessage, - &msg, nowMonotonic); + + /* The connection is fully opened and we have a SecureChannel. + * Process the received buffer */ + retval = UA_SecureChannel_loadBuffer(context->channel, msg); + while(UA_LIKELY(retval == UA_STATUSCODE_GOOD)) { + UA_MessageType messageType; + UA_UInt32 requestId = 0; + UA_ByteString payload = UA_BYTESTRING_NULL; + UA_Boolean copied = false; + retval = UA_SecureChannel_getCompleteMessage(context->channel, &messageType, + &requestId, &payload, &copied, nowMonotonic); + if(retval != UA_STATUSCODE_GOOD || payload.length == 0) + break; + retval = processSecureChannelMessage(bpm->sc.server, context->channel, + messageType, requestId, &payload); + if(copied) + UA_ByteString_clear(&payload); + } + retval |= UA_SecureChannel_persistBuffer(context->channel); + if(retval != UA_STATUSCODE_GOOD) { UA_LOG_WARNING_CHANNEL(bpm->logging, context->channel, "Processing the message failed with error %s", @@ -1275,7 +1306,6 @@ serverReverseConnectCallback(UA_ConnectionManager *cm, uintptr_t connectionId, error.reason = UA_STRING_NULL; UA_SecureChannel_sendError(context->channel, &error); UA_SecureChannel_shutdown(context->channel, UA_SHUTDOWNREASON_ABORT); - setReverseConnectState(bpm->sc.server, context, UA_SECURECHANNELSTATE_CLOSING); return; } diff --git a/src/ua_securechannel.c b/src/ua_securechannel.c index 75733b6ba5c..eabb95ea982 100644 --- a/src/ua_securechannel.c +++ b/src/ua_securechannel.c @@ -28,8 +28,7 @@ void UA_SecureChannel_init(UA_SecureChannel *channel) { /* Normal linked lists are initialized by zeroing out */ memset(channel, 0, sizeof(UA_SecureChannel)); - SIMPLEQ_INIT(&channel->completeChunks); - SIMPLEQ_INIT(&channel->decryptedChunks); + TAILQ_INIT(&channel->chunks); } UA_StatusCode @@ -72,6 +71,8 @@ hideErrors(UA_TcpErrorMessage *const error) { case UA_STATUSCODE_BADCERTIFICATEUNTRUSTED: case UA_STATUSCODE_BADCERTIFICATEREVOKED: case UA_STATUSCODE_BADCERTIFICATEISSUERREVOKED: + case UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE: + case UA_STATUSCODE_BADCERTIFICATEISSUERUSENOTALLOWED: error->error = UA_STATUSCODE_BADSECURITYCHECKSFAILED; error->reason = UA_STRING_NULL; break; @@ -129,19 +130,21 @@ UA_Chunk_delete(UA_Chunk *chunk) { } static void -deleteChunks(UA_ChunkQueue *queue) { - UA_Chunk *chunk; - while((chunk = SIMPLEQ_FIRST(queue))) { - SIMPLEQ_REMOVE_HEAD(queue, pointers); +deleteChunks(UA_SecureChannel *channel) { + UA_Chunk *chunk, *chunk_tmp; + TAILQ_FOREACH_SAFE(chunk, &channel->chunks, pointers, chunk_tmp) { + TAILQ_REMOVE(&channel->chunks, chunk, pointers); UA_Chunk_delete(chunk); } + channel->chunksCount = 0; + channel->chunksLength = 0; } void UA_SecureChannel_deleteBuffered(UA_SecureChannel *channel) { - deleteChunks(&channel->completeChunks); - deleteChunks(&channel->decryptedChunks); - UA_ByteString_clear(&channel->incompleteChunk); + deleteChunks(channel); + if(channel->unprocessedCopied) + UA_ByteString_clear(&channel->unprocessed); } void @@ -172,6 +175,13 @@ UA_SecureChannel_clear(UA_SecureChannel *channel) { channel->channelContext = NULL; } + /* Remove remaining delayed callback */ + if(channel->connectionManager && + channel->connectionManager->eventSource.eventLoop) { + UA_EventLoop *el = channel->connectionManager->eventSource.eventLoop; + el->removeDelayedCallback(el, &channel->unprocessedDelayed); + } + /* The EventLoop connection is no longer valid */ channel->connectionId = 0; channel->connectionManager = NULL; @@ -568,7 +578,7 @@ processSequenceNumberSym(UA_SecureChannel *channel, UA_UInt32 sequenceNumber) { #endif static UA_StatusCode -unpackPayloadOPN(UA_SecureChannel *channel, UA_Chunk *chunk, void *application) { +unpackPayloadOPN(UA_SecureChannel *channel, UA_Chunk *chunk) { UA_assert(chunk->bytes.length >= UA_SECURECHANNEL_MESSAGE_MIN_LENGTH); size_t offset = UA_SECURECHANNEL_MESSAGEHEADER_LENGTH; /* Skip the message header */ UA_UInt32 secureChannelId; @@ -591,14 +601,10 @@ unpackPayloadOPN(UA_SecureChannel *channel, UA_Chunk *chunk, void *application) } /* New channel, create a security policy context and attach */ - if(!channel->securityPolicy) { - if(channel->processOPNHeader) - res = channel->processOPNHeader(application, channel, &asymHeader); - UA_CHECK_STATUS(res, goto error); - if(!channel->securityPolicy) - res = UA_STATUSCODE_BADINTERNALERROR; - UA_CHECK_STATUS(res, goto error); - } + UA_assert(channel->processOPNHeader); + res = channel->processOPNHeader(channel->processOPNHeaderApplication, + channel, &asymHeader); + UA_CHECK_STATUS(res, goto error); /* On the client side, take the SecureChannelId from the first response */ if(secureChannelId != 0 && channel->securityToken.channelId == 0) @@ -699,289 +705,290 @@ unpackPayloadMSG(UA_SecureChannel *channel, UA_Chunk *chunk, } static UA_StatusCode -assembleProcessMessage(UA_SecureChannel *channel, void *application, - UA_ProcessMessageCallback callback) { - UA_Chunk *chunk = SIMPLEQ_FIRST(&channel->decryptedChunks); - UA_assert(chunk != NULL); +extractCompleteChunk(UA_SecureChannel *channel, UA_Chunk *chunk, UA_DateTime nowMonotonic) { + /* At least 8 byte needed for the header */ + size_t offset = channel->unprocessedOffset; + size_t remaining = channel->unprocessed.length - offset; + if(remaining < UA_SECURECHANNEL_MESSAGEHEADER_LENGTH) + return UA_STATUSCODE_GOOD; - UA_StatusCode res = UA_STATUSCODE_GOOD; - if(chunk->chunkType == UA_CHUNKTYPE_FINAL) { - SIMPLEQ_REMOVE_HEAD(&channel->decryptedChunks, pointers); - UA_assert(chunk->chunkType == UA_CHUNKTYPE_FINAL); - res = callback(application, channel, chunk->messageType, - chunk->requestId, &chunk->bytes); - UA_Chunk_delete(chunk); - return res; - } + /* Decoding the header cannot fail */ + UA_TcpMessageHeader hdr; + UA_StatusCode res = + UA_decodeBinaryInternal(&channel->unprocessed, &offset, &hdr, + &UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER], NULL); + UA_assert(res == UA_STATUSCODE_GOOD); + (void)res; /* pacify compilers if assert is ignored */ + UA_MessageType msgType = (UA_MessageType) + (hdr.messageTypeAndChunkType & UA_BITMASK_MESSAGETYPE); + UA_ChunkType chunkType = (UA_ChunkType) + (hdr.messageTypeAndChunkType & UA_BITMASK_CHUNKTYPE); - UA_UInt32 requestId = chunk->requestId; - UA_MessageType messageType = chunk->messageType; - UA_ChunkType chunkType = chunk->chunkType; - UA_assert(chunkType == UA_CHUNKTYPE_INTERMEDIATE); - - size_t messageSize = 0; - SIMPLEQ_FOREACH(chunk, &channel->decryptedChunks, pointers) { - /* Consistency check */ - if(requestId != chunk->requestId) - return UA_STATUSCODE_BADINTERNALERROR; - if(chunkType != chunk->chunkType && chunk->chunkType != UA_CHUNKTYPE_FINAL) - return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; - if(chunk->messageType != messageType) + /* The message size is not allowed */ + if(hdr.messageSize < UA_SECURECHANNEL_MESSAGE_MIN_LENGTH) + return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; + if(hdr.messageSize > channel->config.recvBufferSize) + return UA_STATUSCODE_BADTCPMESSAGETOOLARGE; + + /* Incomplete chunk. Continue processing later. */ + if(hdr.messageSize > remaining) + return UA_STATUSCODE_GOOD; + + /* Set the chunk information */ + chunk->bytes.data = channel->unprocessed.data + channel->unprocessedOffset; + chunk->bytes.length = hdr.messageSize; + chunk->messageType = msgType; + chunk->chunkType = chunkType; + chunk->requestId = 0; + chunk->copied = false; + + /* Increase the unprocessed offset */ + channel->unprocessedOffset += hdr.messageSize; + + /* Validate, decrypt and unpack the chunk payload */ + switch(msgType) { + case UA_MESSAGETYPE_OPN: + if(chunkType != UA_CHUNKTYPE_FINAL) return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; + if(channel->state != UA_SECURECHANNELSTATE_OPEN && + channel->state != UA_SECURECHANNELSTATE_OPN_SENT && + channel->state != UA_SECURECHANNELSTATE_ACK_SENT) + return UA_STATUSCODE_BADINVALIDSTATE; + res = unpackPayloadOPN(channel, chunk); + break; - /* Sum up the lengths */ - messageSize += chunk->bytes.length; - if(chunk->chunkType == UA_CHUNKTYPE_FINAL) - break; - } + case UA_MESSAGETYPE_MSG: + case UA_MESSAGETYPE_CLO: + if(chunkType != UA_CHUNKTYPE_FINAL && + chunkType != UA_CHUNKTYPE_INTERMEDIATE && + chunkType != UA_CHUNKTYPE_ABORT) + return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; + if(channel->state != UA_SECURECHANNELSTATE_OPEN) + return UA_STATUSCODE_BADINVALIDSTATE; + res = unpackPayloadMSG(channel, chunk, nowMonotonic); + break; - /* Allocate memory for the full message */ - UA_ByteString payload; - res = UA_ByteString_allocBuffer(&payload, messageSize); - UA_CHECK_STATUS(res, return res); + case UA_MESSAGETYPE_RHE: + case UA_MESSAGETYPE_HEL: + case UA_MESSAGETYPE_ACK: + case UA_MESSAGETYPE_ERR: + if(chunkType != UA_CHUNKTYPE_FINAL) + return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; + /* Hide the message header */ + chunk->bytes.data += UA_SECURECHANNEL_MESSAGEHEADER_LENGTH; + chunk->bytes.length -= UA_SECURECHANNEL_MESSAGEHEADER_LENGTH; + break; - /* Assemble the full message */ - size_t offset = 0; - while(true) { - chunk = SIMPLEQ_FIRST(&channel->decryptedChunks); - memcpy(&payload.data[offset], chunk->bytes.data, chunk->bytes.length); - offset += chunk->bytes.length; - SIMPLEQ_REMOVE_HEAD(&channel->decryptedChunks, pointers); - UA_ChunkType ct = chunk->chunkType; - UA_Chunk_delete(chunk); - if(ct == UA_CHUNKTYPE_FINAL) - break; + default: + res = UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; + break; } - - /* Process the assembled message */ - res = callback(application, channel, messageType, requestId, &payload); - UA_ByteString_clear(&payload); return res; } -static UA_StatusCode -persistCompleteChunks(UA_ChunkQueue *queue) { - UA_Chunk *chunk; - SIMPLEQ_FOREACH(chunk, queue, pointers) { - if(chunk->copied) - continue; - UA_ByteString copy; - UA_StatusCode res = UA_ByteString_copy(&chunk->bytes, ©); - UA_CHECK_STATUS(res, return res); - chunk->bytes = copy; - chunk->copied = true; +UA_StatusCode +UA_SecureChannel_loadBuffer(UA_SecureChannel *channel, const UA_ByteString buffer) { + /* Append to the previous unprocessed buffer */ + if(channel->unprocessed.length > 0) { + UA_assert(channel->unprocessedCopied == true); + + UA_Byte *t = (UA_Byte*) + UA_realloc(channel->unprocessed.data, + channel->unprocessed.length + buffer.length); + if(!t) + return UA_STATUSCODE_BADOUTOFMEMORY; + + memcpy(t + channel->unprocessed.length, buffer.data, buffer.length); + channel->unprocessed.data = t; + channel->unprocessed.length += buffer.length; + return UA_STATUSCODE_GOOD; } - return UA_STATUSCODE_GOOD; -} -static UA_StatusCode -persistIncompleteChunk(UA_SecureChannel *channel, const UA_ByteString *buffer, - size_t offset) { - UA_assert(channel->incompleteChunk.length == 0); - UA_assert(offset < buffer->length); - size_t length = buffer->length - offset; - UA_StatusCode res = UA_ByteString_allocBuffer(&channel->incompleteChunk, length); - UA_CHECK_STATUS(res, return res); - memcpy(channel->incompleteChunk.data, &buffer->data[offset], length); + /* Use the new buffer directly */ + channel->unprocessed = buffer; + channel->unprocessedCopied = false; return UA_STATUSCODE_GOOD; } -/* Processes chunks and puts them into the payloads queue. Once a final chunk is - * put into the queue, the message is assembled and the callback is called. The - * queue will be cleared for the next message. */ -static UA_StatusCode -processChunks(UA_SecureChannel *channel, void *application, - UA_ProcessMessageCallback callback, - UA_DateTime nowMonotonic) { - UA_Chunk *chunk; +UA_StatusCode +UA_SecureChannel_getCompleteMessage(UA_SecureChannel *channel, + UA_MessageType *messageType, UA_UInt32 *requestId, + UA_ByteString *payload, UA_Boolean *copied, + UA_DateTime nowMonotonic) { + UA_Chunk chunk, *pchunk; UA_StatusCode res = UA_STATUSCODE_GOOD; - while((chunk = SIMPLEQ_FIRST(&channel->completeChunks))) { - /* Remove from the complete-chunk queue */ - SIMPLEQ_REMOVE_HEAD(&channel->completeChunks, pointers); - - /* Check, decrypt and unpack the payload */ - if(chunk->messageType == UA_MESSAGETYPE_OPN) { - if(channel->state != UA_SECURECHANNELSTATE_OPEN && - channel->state != UA_SECURECHANNELSTATE_OPN_SENT && - channel->state != UA_SECURECHANNELSTATE_ACK_SENT) - res = UA_STATUSCODE_BADINVALIDSTATE; - else - res = unpackPayloadOPN(channel, chunk, application); - } else if(chunk->messageType == UA_MESSAGETYPE_MSG || - chunk->messageType == UA_MESSAGETYPE_CLO) { - if(channel->state == UA_SECURECHANNELSTATE_CLOSED) - res = UA_STATUSCODE_BADSECURECHANNELCLOSED; - else - res = unpackPayloadMSG(channel, chunk, nowMonotonic); - } else { - chunk->bytes.data += UA_SECURECHANNEL_MESSAGEHEADER_LENGTH; - chunk->bytes.length -= UA_SECURECHANNEL_MESSAGEHEADER_LENGTH; - } - - if(res != UA_STATUSCODE_GOOD) { - UA_Chunk_delete(chunk); - return res; - } - - /* Add to the decrypted-chunk queue */ - SIMPLEQ_INSERT_TAIL(&channel->decryptedChunks, chunk, pointers); - /* Check the resource limits */ - channel->decryptedChunksCount++; - channel->decryptedChunksLength += chunk->bytes.length; + extract_chunk: + /* Extract+decode the next chunk from the buffer */ + memset(&chunk, 0, sizeof(UA_Chunk)); + res = extractCompleteChunk(channel, &chunk, nowMonotonic); + if(chunk.bytes.length == 0 || res != UA_STATUSCODE_GOOD) + return res; /* Error or no complete chunk could be extracted */ + + /* Process the chunk */ + switch(chunk.chunkType) { + case UA_CHUNKTYPE_ABORT: + /* Remove all chunks received so far. Then continue extracting chunks. */ + deleteChunks(channel); + if(chunk.copied) + UA_ByteString_clear(&chunk.bytes); + goto extract_chunk; + + case UA_CHUNKTYPE_INTERMEDIATE: + /* Validate the resource limits */ if((channel->config.localMaxChunkCount != 0 && - channel->decryptedChunksCount > channel->config.localMaxChunkCount) || + channel->chunksCount >= channel->config.localMaxChunkCount) || (channel->config.localMaxMessageSize != 0 && - channel->decryptedChunksLength > channel->config.localMaxMessageSize)) { + channel->chunksLength + chunk.bytes.length > channel->config.localMaxMessageSize)) { + if(chunk.copied) + UA_ByteString_clear(&chunk.bytes); return UA_STATUSCODE_BADTCPMESSAGETOOLARGE; } - /* Waiting for additional chunks */ - if(chunk->chunkType == UA_CHUNKTYPE_INTERMEDIATE) - continue; - - /* Final chunk or abort. Reset the counters. */ - channel->decryptedChunksCount = 0; - channel->decryptedChunksLength = 0; - - /* Abort the message, remove all decrypted chunks - * TODO: Log a warning with the error code */ - if(chunk->chunkType == UA_CHUNKTYPE_ABORT) { - while((chunk = SIMPLEQ_FIRST(&channel->decryptedChunks))) { - SIMPLEQ_REMOVE_HEAD(&channel->decryptedChunks, pointers); - UA_Chunk_delete(chunk); - } - continue; + /* Add the chunk to the queue. Then continue extracting more chunks. */ + pchunk = (UA_Chunk*)UA_malloc(sizeof(UA_Chunk)); + if(!pchunk) { + if(chunk.copied) + UA_ByteString_clear(&chunk.bytes); + return UA_STATUSCODE_BADOUTOFMEMORY; } + *pchunk = chunk; + TAILQ_INSERT_TAIL(&channel->chunks, pchunk, pointers); + channel->chunksCount++; + channel->chunksLength += pchunk->bytes.length; + goto extract_chunk; - /* The decrypted queue contains a full message. Process it. */ - UA_assert(chunk->chunkType == UA_CHUNKTYPE_FINAL); - res = assembleProcessMessage(channel, application, callback); - UA_CHECK_STATUS(res, return res); + case UA_CHUNKTYPE_FINAL: + default: + UA_assert(chunk.chunkType == UA_CHUNKTYPE_FINAL); /* Was checked before */ + break; /* A final chunk was received -- assemble the message */ } - return UA_STATUSCODE_GOOD; -} - -static UA_StatusCode -extractCompleteChunk(UA_SecureChannel *channel, const UA_ByteString *buffer, - size_t *offset, UA_Boolean *done) { - /* At least 8 byte needed for the header. Wait for the next chunk. */ - size_t initial_offset = *offset; - size_t remaining = buffer->length - initial_offset; - if(remaining < UA_SECURECHANNEL_MESSAGEHEADER_LENGTH) { - *done = true; - return UA_STATUSCODE_GOOD; + /* Compute the message size */ + size_t messageSize = chunk.bytes.length; + UA_Chunk *first = NULL; + TAILQ_FOREACH(pchunk, &channel->chunks, pointers) { + if(chunk.requestId != pchunk->requestId) + continue; + if(chunk.messageType != pchunk->messageType) { + if(chunk.copied) + UA_ByteString_clear(&chunk.bytes); + return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; + } + if(!first) + first = pchunk; + messageSize += pchunk->bytes.length; } - /* Decoding cannot fail */ - UA_TcpMessageHeader hdr; - UA_StatusCode res = - UA_decodeBinaryInternal(buffer, &initial_offset, &hdr, - &UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER], NULL); - UA_assert(res == UA_STATUSCODE_GOOD); - (void)res; /* pacify compilers if assert is ignored */ - UA_MessageType msgType = (UA_MessageType) - (hdr.messageTypeAndChunkType & UA_BITMASK_MESSAGETYPE); - UA_ChunkType chunkType = (UA_ChunkType) - (hdr.messageTypeAndChunkType & UA_BITMASK_CHUNKTYPE); - - /* The message size is not allowed */ - if(hdr.messageSize < UA_SECURECHANNEL_MESSAGE_MIN_LENGTH) - return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; - if(hdr.messageSize > channel->config.recvBufferSize) + /* Validate the assembled message size */ + if(channel->config.localMaxMessageSize != 0 && + channel->chunksLength > channel->config.localMaxMessageSize) { + if(chunk.copied) + UA_ByteString_clear(&chunk.bytes); return UA_STATUSCODE_BADTCPMESSAGETOOLARGE; - - /* Incomplete chunk */ - if(hdr.messageSize > remaining) { - *done = true; - return UA_STATUSCODE_GOOD; } - /* ByteString with only this chunk. */ - UA_ByteString chunkPayload; - chunkPayload.data = &buffer->data[*offset]; - chunkPayload.length = hdr.messageSize; - - if(msgType == UA_MESSAGETYPE_RHE || msgType == UA_MESSAGETYPE_HEL || msgType == UA_MESSAGETYPE_ACK || - msgType == UA_MESSAGETYPE_ERR || msgType == UA_MESSAGETYPE_OPN) { - if(chunkType != UA_CHUNKTYPE_FINAL) - return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; - } else { - /* Only messages on SecureChannel-level with symmetric encryption afterwards */ - if(msgType != UA_MESSAGETYPE_MSG && - msgType != UA_MESSAGETYPE_CLO) - return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; + /* Assemble the full payload and store it in chunk.bytes */ + if(messageSize > chunk.bytes.length) { + UA_assert(first != NULL); - /* Check the chunk type before decrypting */ - if(chunkType != UA_CHUNKTYPE_FINAL && - chunkType != UA_CHUNKTYPE_INTERMEDIATE && - chunkType != UA_CHUNKTYPE_ABORT) - return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID; - } + /* Allocate the full memory and initialize with the first chunk content. + * Use realloc to speed up. */ + UA_ByteString message; + if(first->copied) { + message.data = (UA_Byte*)UA_realloc(first->bytes.data, messageSize); + } else { + message.data = (UA_Byte*)UA_malloc(messageSize); + if(message.data) + memcpy(message.data, first->bytes.data, first->bytes.length); + } + if(!message.data) { + if(chunk.copied) + UA_ByteString_clear(&chunk.bytes); + return UA_STATUSCODE_BADOUTOFMEMORY; + } + message.length = first->bytes.length; + + /* Remove the the first chunk */ + pchunk = TAILQ_NEXT(first, pointers); + first->copied = false; + channel->chunksCount--; + channel->chunksLength -= first->bytes.length; + TAILQ_REMOVE(&channel->chunks, first, pointers); + UA_Chunk_delete(first); + + /* Copy over the content from the remaining intermediate chunks. + * And remove them right away. */ + UA_Chunk *next; + for(; pchunk; pchunk = next) { + next = TAILQ_NEXT(pchunk, pointers); + if(chunk.requestId != pchunk->requestId) + continue; + memcpy(message.data + message.length, pchunk->bytes.data, pchunk->bytes.length); + message.length += pchunk->bytes.length; + channel->chunksCount--; + channel->chunksLength -= pchunk->bytes.length; + TAILQ_REMOVE(&channel->chunks, pchunk, pointers); + UA_Chunk_delete(pchunk); + } - /* Add the chunk; forward the offset */ - *offset += hdr.messageSize; - UA_Chunk *chunk = (UA_Chunk*)UA_malloc(sizeof(UA_Chunk)); - UA_CHECK_MEM(chunk, return UA_STATUSCODE_BADOUTOFMEMORY); + /* Copy over the content from the final chunk */ + memcpy(message.data + message.length, chunk.bytes.data, chunk.bytes.length); + message.length += chunk.bytes.length; + UA_assert(message.length == messageSize); - chunk->bytes = chunkPayload; - chunk->messageType = msgType; - chunk->chunkType = chunkType; - chunk->requestId = 0; - chunk->copied = false; + /* Set assembled message as the content of the final chunk */ + if(chunk.copied) + UA_ByteString_clear(&chunk.bytes); + chunk.bytes = message; + chunk.copied = true; + } - SIMPLEQ_INSERT_TAIL(&channel->completeChunks, chunk, pointers); + /* Return the assembled message */ + *requestId = chunk.requestId; + *messageType = chunk.messageType; + *payload = chunk.bytes; + *copied = chunk.copied; return UA_STATUSCODE_GOOD; } UA_StatusCode -UA_SecureChannel_processBuffer(UA_SecureChannel *channel, void *application, - UA_ProcessMessageCallback callback, - const UA_ByteString *buffer, - UA_DateTime nowMonotonic) { - /* Prepend the incomplete last chunk. This is usually done in the - * networklayer. But we test for a buffered incomplete chunk here again to - * work around "lazy" network layers. */ - UA_ByteString appended = channel->incompleteChunk; - if(appended.length > 0) { - channel->incompleteChunk = UA_BYTESTRING_NULL; - UA_Byte *t = (UA_Byte*)UA_realloc(appended.data, appended.length + buffer->length); - UA_CHECK_MEM(t, UA_ByteString_clear(&appended); - return UA_STATUSCODE_BADOUTOFMEMORY); - memcpy(&t[appended.length], buffer->data, buffer->length); - appended.data = t; - appended.length += buffer->length; - buffer = &appended; - } +UA_SecureChannel_persistBuffer(UA_SecureChannel *channel) { + UA_StatusCode res = UA_STATUSCODE_GOOD; - /* Loop over the received chunks */ - size_t offset = 0; - UA_Boolean done = false; - UA_StatusCode res; - while(!done) { - res = extractCompleteChunk(channel, buffer, &offset, &done); - UA_CHECK_STATUS(res, goto cleanup); + /* Persist the chunks */ + UA_Chunk *chunk; + TAILQ_FOREACH(chunk, &channel->chunks, pointers) { + if(chunk->copied) + continue; + UA_ByteString tmp = UA_BYTESTRING_NULL; + res |= UA_ByteString_copy(&chunk->bytes, &tmp); + chunk->bytes = tmp; + chunk->copied = true; } - /* Buffer half-received chunk. Before processing the messages so that - * processing is reentrant. */ - if(offset < buffer->length) { - res = persistIncompleteChunk(channel, buffer, offset); - UA_CHECK_STATUS(res, goto cleanup); + /* No unprocessed bytes remaining */ + UA_assert(channel->unprocessed.length >= channel->unprocessedOffset); + if(channel->unprocessed.length == channel->unprocessedOffset) { + if(channel->unprocessedCopied) + UA_ByteString_clear(&channel->unprocessed); + else + UA_ByteString_init(&channel->unprocessed); + channel->unprocessedOffset = 0; + return res; } - /* Process whatever we can. Chunks of completed and processed messages are - * removed. */ - res = processChunks(channel, application, callback, nowMonotonic); - UA_CHECK_STATUS(res, goto cleanup); - - /* Persist full chunks that still point to the buffer. Can only return - * UA_STATUSCODE_BADOUTOFMEMORY as an error code. So merging res works. */ - res |= persistCompleteChunks(&channel->completeChunks); - res |= persistCompleteChunks(&channel->decryptedChunks); - - cleanup: - UA_ByteString_clear(&appended); + /* Allocate a new unprocessed ByteString. + * tmp is the empty string if malloc fails. */ + UA_ByteString tmp = UA_BYTESTRING_NULL; + UA_ByteString remaining = channel->unprocessed; + remaining.data += channel->unprocessedOffset; + remaining.length -= channel->unprocessedOffset; + res |= UA_ByteString_copy(&remaining, &tmp); + if(channel->unprocessedCopied) + UA_ByteString_clear(&channel->unprocessed); + channel->unprocessed = tmp; + channel->unprocessedOffset = 0; + channel->unprocessedCopied = true; return res; } diff --git a/src/ua_securechannel.h b/src/ua_securechannel.h index f88ffa12741..90be0885b18 100644 --- a/src/ua_securechannel.h +++ b/src/ua_securechannel.h @@ -62,7 +62,7 @@ typedef struct UA_Session UA_Session; /* For chunked requests */ typedef struct UA_Chunk { - SIMPLEQ_ENTRY(UA_Chunk) pointers; + TAILQ_ENTRY(UA_Chunk) pointers; UA_ByteString bytes; UA_MessageType messageType; UA_ChunkType chunkType; @@ -71,7 +71,7 @@ typedef struct UA_Chunk { * memory allocated for the chunk separately */ } UA_Chunk; -typedef SIMPLEQ_HEAD(UA_ChunkQueue, UA_Chunk) UA_ChunkQueue; +typedef TAILQ_HEAD(UA_ChunkQueue, UA_Chunk) UA_ChunkQueue; typedef enum { UA_SECURECHANNELRENEWSTATE_NORMAL, @@ -143,20 +143,19 @@ struct UA_SecureChannel { * used in the server) */ UA_Session *sessions; - /* If a buffer is received, first all chunks are put into the completeChunks - * queue. Then they are processed in order. This ensures that processing - * buffers is reentrant with the correct processing order. (This has lead to - * problems in the client in the past.) */ - UA_ChunkQueue completeChunks; /* Received full chunks that have not been - * decrypted so far */ - UA_ChunkQueue decryptedChunks; /* Received chunks that were decrypted but - * not processed */ - size_t decryptedChunksCount; - size_t decryptedChunksLength; - UA_ByteString incompleteChunk; /* A half-received chunk (TCP is a - * streaming protocol) is stored here */ + /* (Decrypted) chunks waiting to be processed */ + UA_ChunkQueue chunks; + size_t chunksCount; + size_t chunksLength; + + /* Received buffer from which no chunks have been extracted so far */ + UA_ByteString unprocessed; + size_t unprocessedOffset; + UA_Boolean unprocessedCopied; + UA_DelayedCallback unprocessedDelayed; UA_CertificateGroup *certificateVerification; + void *processOPNHeaderApplication; UA_StatusCode (*processOPNHeader)(void *application, UA_SecureChannel *channel, const UA_AsymmetricAlgorithmSecurityHeader *asymHeader); }; @@ -273,22 +272,28 @@ UA_MessageContext_abort(UA_MessageContext *mc); * Receive Message * --------------- */ -typedef UA_StatusCode -(UA_ProcessMessageCallback)(void *application, UA_SecureChannel *channel, - UA_MessageType messageType, UA_UInt32 requestId, - UA_ByteString *message); - -/* Process a received buffer. The callback function is called with the message - * body if the message is complete. The message is removed afterwards. Returns - * if an irrecoverable error occured. +/* Process a received buffer. This always has these three steps: + * + * 1. loadBuffer: The chunks in the SecureChannel are cut into chunks. + * The chunks can still point to the buffer. + * 2. getCompleteMessage: Assemble chunks into a complete message. This is + * repeated until an error occours or an empty message is returned. + * 3. persistBuffer: Make a copy of the remaining unpprocessed bytestring. So + * that the NetworkManager can reuse or free the packet memory. * * Note that only MSG and CLO messages are decrypted. HEL/ACK/OPN/... are * forwarded verbatim to the application. */ UA_StatusCode -UA_SecureChannel_processBuffer(UA_SecureChannel *channel, void *application, - UA_ProcessMessageCallback callback, - const UA_ByteString *buffer, - UA_DateTime nowMonotonic); +UA_SecureChannel_loadBuffer(UA_SecureChannel *channel, const UA_ByteString buffer); + +UA_StatusCode +UA_SecureChannel_getCompleteMessage(UA_SecureChannel *channel, + UA_MessageType *messageType, UA_UInt32 *requestId, + UA_ByteString *payload, UA_Boolean *copied, + UA_DateTime nowMonotonic); + +UA_StatusCode +UA_SecureChannel_persistBuffer(UA_SecureChannel *channel); /* Internal methods in ua_securechannel_crypto.h */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index df748479278..2b4c282adcb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -382,6 +382,7 @@ if(UA_ENABLE_ENCRYPTION_MBEDTLS) ua_add_test(encryption/check_update_certificate.c) ua_add_test(encryption/check_update_trustlist.c) ua_add_test(encryption/check_certificategroup.c) + ua_add_test(encryption/check_save_rejected_cert.c) endif() if(UA_ENABLE_ENCRYPTION_OPENSSL OR UA_ENABLE_ENCRYPTION_LIBRESSL) diff --git a/tests/check_securechannel.c b/tests/check_securechannel.c index 273dc6488d4..687c138c566 100644 --- a/tests/check_securechannel.c +++ b/tests/check_securechannel.c @@ -453,18 +453,26 @@ START_TEST(SecureChannel_sendSymmetricMessage_invalidParameters) { } END_TEST static UA_StatusCode -process_callback(void *application, UA_SecureChannel *channel, - UA_MessageType messageType, UA_UInt32 requestId, - UA_ByteString *message) { - ck_assert_ptr_ne(message, NULL); - ck_assert_ptr_ne(application, NULL); - if(message == NULL || application == NULL) - return UA_STATUSCODE_BADINTERNALERROR; - ck_assert_uint_ne(message->length, 0); - ck_assert_ptr_ne(message->data, NULL); - int *chunks_processed = (int *)application; - ++*chunks_processed; - return UA_STATUSCODE_GOOD; +UA_SecureChannel_processBuffer(UA_SecureChannel *channel, int *chunks_processed, + const UA_ByteString buffer) { + UA_StatusCode res = UA_SecureChannel_loadBuffer(channel, buffer); + while(UA_LIKELY(res == UA_STATUSCODE_GOOD)) { + UA_MessageType messageType; + UA_UInt32 requestId = 0; + UA_ByteString payload = UA_BYTESTRING_NULL; + UA_Boolean copied = false; + res = UA_SecureChannel_getCompleteMessage(channel, &messageType, &requestId, + &payload, &copied, UA_DateTime_nowMonotonic()); + if(res != UA_STATUSCODE_GOOD || payload.length == 0) + break; + ck_assert_uint_ne(payload.length, 0); + ck_assert_ptr_ne(payload.data, NULL); + ++*chunks_processed; + if(copied) + UA_ByteString_clear(&payload); + } + res |= UA_SecureChannel_persistBuffer(channel); + return res; } START_TEST(SecureChannel_assemblePartialChunks) { @@ -476,21 +484,18 @@ START_TEST(SecureChannel_assemblePartialChunks) { buffer.length = 32; UA_StatusCode retval = - UA_SecureChannel_processBuffer(&testChannel, &chunks_processed, - process_callback, &buffer, UA_DateTime_nowMonotonic()); + UA_SecureChannel_processBuffer(&testChannel, &chunks_processed, buffer); ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected success"); ck_assert_int_eq(chunks_processed, 1); buffer.length = 16; - UA_SecureChannel_processBuffer(&testChannel, &chunks_processed, - process_callback, &buffer, UA_DateTime_nowMonotonic()); + UA_SecureChannel_processBuffer(&testChannel, &chunks_processed, buffer); ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected success"); ck_assert_int_eq(chunks_processed, 1); buffer.data = &buffer.data[16]; - UA_SecureChannel_processBuffer(&testChannel, &chunks_processed, - process_callback, &buffer, UA_DateTime_nowMonotonic()); + UA_SecureChannel_processBuffer(&testChannel, &chunks_processed, buffer); ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected success"); ck_assert_int_eq(chunks_processed, 2); @@ -502,23 +507,20 @@ START_TEST(SecureChannel_assemblePartialChunks) { "\x10\x00\x00\x00@\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff"; buffer.length = 48; - UA_SecureChannel_processBuffer(&testChannel, &chunks_processed, - process_callback, &buffer, UA_DateTime_nowMonotonic()); + UA_SecureChannel_processBuffer(&testChannel, &chunks_processed, buffer); ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected success"); ck_assert_int_eq(chunks_processed, 3); buffer.data = &buffer.data[48]; buffer.length = 32; - UA_SecureChannel_processBuffer(&testChannel, &chunks_processed, - process_callback, &buffer, UA_DateTime_nowMonotonic()); + UA_SecureChannel_processBuffer(&testChannel, &chunks_processed, buffer); ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected success"); ck_assert_int_eq(chunks_processed, 4); buffer.data = &buffer.data[32]; buffer.length = 16; - UA_SecureChannel_processBuffer(&testChannel, &chunks_processed, - process_callback, &buffer, UA_DateTime_nowMonotonic()); + UA_SecureChannel_processBuffer(&testChannel, &chunks_processed, buffer); ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected success"); ck_assert_int_eq(chunks_processed, 5); } END_TEST diff --git a/tests/encryption/check_certificategroup.c b/tests/encryption/check_certificategroup.c index 0f0011b0a74..2216c51abd5 100644 --- a/tests/encryption/check_certificategroup.c +++ b/tests/encryption/check_certificategroup.c @@ -81,8 +81,9 @@ static void setup2(void) { ck_assert(server != NULL); UA_ServerConfig *config = UA_Server_getConfig(server); - char storePathDir[4096]; - getcwd(storePathDir, 4096); + char storePathDir[32]; + strcpy(storePathDir, "open62541-pki-XXXXXX"); + mkdtemp(storePathDir); const UA_String storePath = UA_STRING(storePathDir); @@ -303,7 +304,7 @@ START_TEST(get_rejectedlist) { /* Secure client connect */ retval = UA_Client_connect(client, "opc.tcp://localhost:4840"); - ck_assert_uint_eq(retval, UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE); + ck_assert_uint_eq(retval, UA_STATUSCODE_BADSECURITYCHECKSFAILED); UA_ByteString *rejectedList = NULL; size_t rejectedListSize = 0; diff --git a/tests/pubsub/check_pubsub_publisherid.c b/tests/pubsub/check_pubsub_publisherid.c index e3a2df72806..79d096c3983 100644 --- a/tests/pubsub/check_pubsub_publisherid.c +++ b/tests/pubsub/check_pubsub_publisherid.c @@ -30,11 +30,6 @@ static void setup(void) { UA_ServerConfig *config = UA_Server_getConfig(server); ck_assert(config != 0); - /* Silence the log, because this test might produce an enormous amount of noise */ - logger = UA_Log_Stdout_withLevel(UA_LOGLEVEL_ERROR); - config->logging->clear(config->logging); - *config->logging = logger; - ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_run_startup(server)); } diff --git a/tools/cmake/open62541Macros.cmake b/tools/cmake/open62541Macros.cmake index f0d89ab234e..eb22450f014 100644 --- a/tools/cmake/open62541Macros.cmake +++ b/tools/cmake/open62541Macros.cmake @@ -66,7 +66,6 @@ function(ua_generate_nodeid_header) # Header containing defines for all NodeIds add_custom_command(OUTPUT ${UA_GEN_ID_OUTPUT_DIR}/${UA_GEN_ID_NAME}.h - PRE_BUILD COMMAND ${Python3_EXECUTABLE} ${open62541_TOOLS_DIR}/generate_nodeid_header.py ${UA_GEN_ID_FILE_CSV} ${UA_GEN_ID_OUTPUT_DIR}/${UA_GEN_ID_NAME} ${UA_GEN_ID_ID_PREFIX} DEPENDS ${open62541_TOOLS_DIR}/generate_nodeid_header.py @@ -218,7 +217,7 @@ function(ua_generate_datatypes) add_custom_command(OUTPUT ${UA_GEN_DT_OUTPUT_DIR}/${UA_GEN_DT_NAME}_generated.c ${UA_GEN_DT_OUTPUT_DIR}/${UA_GEN_DT_NAME}_generated.h - PRE_BUILD + ${UA_GEN_DT_OUTPUT_DIR}/${UA_GEN_DT_NAME}_generated_handling.h COMMAND ${ARG_CONV_EXCL_ENV} ${Python3_EXECUTABLE} ${open62541_TOOLS_DIR}/generate_datatypes.py ${NAMESPACE_MAP_TMP} ${SELECTED_TYPES_TMP} @@ -395,7 +394,6 @@ function(ua_generate_nodeset) add_custom_command(OUTPUT ${UA_GEN_NS_OUTPUT_DIR}/namespace${FILE_SUFFIX}.c ${UA_GEN_NS_OUTPUT_DIR}/namespace${FILE_SUFFIX}.h - PRE_BUILD COMMAND ${Python3_EXECUTABLE} ${open62541_TOOLS_DIR}/nodeset_compiler/nodeset_compiler.py ${GEN_INTERNAL_HEADERS} ${GEN_NS0}