Merge remote-tracking branch 'origin/1.4' into merge_14_master_24

This commit is contained in:
Julius Pfrommer 2024-11-28 18:32:55 +01:00
commit 3785ed5c4d
22 changed files with 1271 additions and 544 deletions

2
.gitignore vendored
View File

@ -90,6 +90,8 @@ Pipfile.lock
.vscode
/.vs
**/libxml2
debian/*
/CMakeSettings.json
# clangd cache
.cache/
# project local vim settings

View File

@ -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 #
#####################
@ -738,13 +743,13 @@ set(exported_headers ${PROJECT_BINARY_DIR}/src_generated/open62541/config.h
${PROJECT_SOURCE_DIR}/include/open62541/plugin/eventloop.h
${PROJECT_SOURCE_DIR}/include/open62541/plugin/nodestore.h
${PROJECT_SOURCE_DIR}/include/open62541/plugin/historydatabase.h
${PROJECT_SOURCE_DIR}/include/open62541/server_pubsub.h
${PROJECT_SOURCE_DIR}/include/open62541/pubsub.h
${PROJECT_SOURCE_DIR}/include/open62541/client.h
${PROJECT_SOURCE_DIR}/include/open62541/server.h
${PROJECT_SOURCE_DIR}/include/open62541/client_highlevel.h
${PROJECT_SOURCE_DIR}/include/open62541/client_subscriptions.h
${PROJECT_SOURCE_DIR}/include/open62541/client_highlevel_async.h)
${PROJECT_SOURCE_DIR}/include/open62541/client_highlevel_async.h
${PROJECT_SOURCE_DIR}/include/open62541/server_pubsub.h
${PROJECT_SOURCE_DIR}/include/open62541/server.h
${PROJECT_SOURCE_DIR}/include/open62541/pubsub.h)
# Main Library
@ -979,13 +984,16 @@ endif()
# Use guards in the files to ensure that UA_ENABLE_ENCRYPTON_MBEDTLS and UA_ENABLE_ENCRYPTION_OPENSSL are honored.
if((UNIX AND UA_ENABLE_ENCRYPTION) OR UA_ENABLE_AMALGAMATION)
list(APPEND plugin_sources ${PROJECT_SOURCE_DIR}/plugins/crypto/ua_certificategroup_filestore.c
list(APPEND plugin_sources ${PROJECT_SOURCE_DIR}/plugins/crypto/ua_filestore_common.h
${PROJECT_SOURCE_DIR}/plugins/crypto/ua_filestore_common.c
${PROJECT_SOURCE_DIR}/plugins/crypto/ua_certificategroup_filestore.c
${PROJECT_SOURCE_DIR}/plugins/crypto/ua_securitypolicy_filestore.c)
endif()
if(UA_ENABLE_ENCRYPTION_MBEDTLS OR UA_ENABLE_AMALGAMATION)
list(INSERT plugin_sources 0
${PROJECT_SOURCE_DIR}/plugins/crypto/mbedtls/securitypolicy_common.h)
list(APPEND plugin_sources
${PROJECT_SOURCE_DIR}/plugins/crypto/mbedtls/securitypolicy_common.h
${PROJECT_SOURCE_DIR}/plugins/crypto/mbedtls/securitypolicy_common.c
${PROJECT_SOURCE_DIR}/plugins/crypto/mbedtls/securitypolicy_basic128rsa15.c
${PROJECT_SOURCE_DIR}/plugins/crypto/mbedtls/securitypolicy_basic256.c
@ -999,8 +1007,9 @@ if(UA_ENABLE_ENCRYPTION_MBEDTLS OR UA_ENABLE_AMALGAMATION)
endif()
if(UA_ENABLE_ENCRYPTION_OPENSSL OR UA_ENABLE_ENCRYPTION_LIBRESSL OR UA_ENABLE_AMALGAMATION)
list(INSERT plugin_sources 0
${PROJECT_SOURCE_DIR}/plugins/crypto/openssl/securitypolicy_common.h)
list(APPEND plugin_sources
${PROJECT_SOURCE_DIR}/plugins/crypto/openssl/securitypolicy_common.h
${PROJECT_SOURCE_DIR}/plugins/crypto/openssl/securitypolicy_common.c
${PROJECT_SOURCE_DIR}/plugins/crypto/openssl/securitypolicy_basic128rsa15.c
${PROJECT_SOURCE_DIR}/plugins/crypto/openssl/securitypolicy_basic256.c
@ -1045,20 +1054,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 +1082,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 +1143,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 +1169,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 +1188,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 +1195,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 +1221,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

View File

@ -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()

View File

@ -4,17 +4,15 @@
* Copyright (c) 2022 Fraunhofer IOSB (Author: Noel Graf)
*/
#include <open62541/plugin/log_stdout.h>
#include <open62541/server.h>
#include <open62541/server_pubsub.h>
#include <open62541/plugin/log_stdout.h>
#if defined(UA_ENABLE_PUBSUB_ENCRYPTION)
#include <open62541/plugin/securitypolicy_default.h>
#endif
#include <stdio.h>
#include <stdio.h>
#define CONNECTION_NAME "MQTT Subscriber Connection"
#define TRANSPORT_PROFILE_URI_UADP "http://opcfoundation.org/UA-Profile/Transport/pubsub-mqtt-uadp"
#define TRANSPORT_PROFILE_URI_JSON "http://opcfoundation.org/UA-Profile/Transport/pubsub-mqtt-json"

View File

@ -42,10 +42,7 @@ static const struct {
{{0, UA_STRING_STATIC("max-rejected-listsize")}, &UA_TYPES[UA_TYPES_STRING], false}
};
struct MemoryCertStore;
typedef struct MemoryCertStore MemoryCertStore;
struct MemoryCertStore {
typedef struct {
UA_TrustListDataType trustList;
size_t rejectedCertificatesSize;
UA_ByteString *rejectedCertificates;
@ -59,7 +56,9 @@ struct MemoryCertStore {
mbedtls_x509_crt issuerCertificates;
mbedtls_x509_crl trustedCrls;
mbedtls_x509_crl issuerCrls;
};
UA_CertificateGroup *cg;
} MemoryCertStore;
static UA_Boolean mbedtlsCheckCA(mbedtls_x509_crt *cert);
@ -372,10 +371,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(MemoryCertStore *ctx, 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 +401,57 @@ 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 == &ctx->trustedCertificates)
stack = NULL;
else if(stack == &ctx->issuerCertificates)
stack = &ctx->trustedCertificates;
else
stack = &ctx->issuerCertificates;
} while(stack);
return NULL;
}
static UA_Boolean
mbedtlsCheckRevoked(MemoryCertStore *context, mbedtls_x509_crt *cert) {
static UA_StatusCode
mbedtlsCheckRevoked(MemoryCertStore *ctx, 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(ctx->trustedCrls.raw.len == 0 && ctx->issuerCrls.raw.len == 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;
}
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 = &ctx->trustedCrls; 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;
}
/* Loop over the issuer crls separately */
for(mbedtls_x509_crl *crl = &ctx->issuerCrls; crl; crl = crl->next) {
if(mbedtlsSameName(issuerName, &crl->issuer)) {
if(mbedtls_x509_crt_is_revoked(cert, crl) != 0)
return UA_STATUSCODE_BADCERTIFICATEREVOKED;
res = UA_STATUSCODE_GOOD;
}
}
return res;
}
/* Verify that the public key of the issuer was used to sign the certificate */
@ -445,7 +478,8 @@ 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(MemoryCertStore *ctx, 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 +491,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 +499,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(ctx, stack, cert, issuer);
if(!issuer)
break;
@ -491,16 +520,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;
}
/* 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++) {
/* Verification Step: Revocation Check */
ret = mbedtlsCheckRevoked(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 */
for(int i = 0; i < depth; i++) {
if(old_issuers[i] == issuer)
return UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE;
}
@ -508,15 +549,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(ctx, 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 = &ctx->trustedCertificates; t; t = t->next) {
if(mbedtlsSameBuf(&cert->tbs, &t->tbs))
return UA_STATUSCODE_GOOD;
}
}
@ -627,6 +667,7 @@ UA_CertificateGroup_Memorystore(UA_CertificateGroup *certGroup,
retval = UA_STATUSCODE_BADOUTOFMEMORY;
goto cleanup;
}
context->cg = certGroup;
certGroup->context = context;
/* Default values */
context->maxTrustListSize = 65535;

View File

@ -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 <open62541/plugin/create_certificate.h>
#include <time.h>
#include "securitypolicy_mbedtls_common.h"
#include "../../arch/eventloop_posix.h"
#if defined(UA_ENABLE_ENCRYPTION_MBEDTLS)
#include <mbedtls/x509_crt.h>
#include <mbedtls/oid.h>
#include <mbedtls/asn1write.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/platform.h>
#include <mbedtls/version.h>
#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

View File

@ -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,12 +581,26 @@ 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. */
for(int i = 0; i < depth; i++) {
@ -582,19 +634,18 @@ 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);
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;
}

View File

@ -11,6 +11,7 @@
#include <open62541/plugin/certificategroup_default.h>
#include <open62541/plugin/log_stdout.h>
#include "ua_filestore_common.h"
#include "mp_printf.h"
#ifdef UA_ENABLE_ENCRYPTION
@ -166,53 +167,6 @@ getCertFileName(const char *path, const UA_ByteString *certificate,
return retval;
}
static UA_StatusCode
readFileToByteString(const char *const path, UA_ByteString *data) {
if(path == NULL || data == NULL)
return UA_STATUSCODE_BADINTERNALERROR;
/* Open the file */
FILE *fp = fopen(path, "rb");
if(!fp)
return UA_STATUSCODE_BADNOTFOUND;
/* Get the file length, allocate the data and read */
fseek(fp, 0, SEEK_END);
UA_StatusCode retval = UA_ByteString_allocBuffer(data, (size_t)ftell(fp));
if(retval == UA_STATUSCODE_GOOD) {
fseek(fp, 0, SEEK_SET);
size_t read = fread(data->data, sizeof(UA_Byte), data->length * sizeof(UA_Byte), fp);
if(read != data->length) {
UA_ByteString_clear(data);
}
} else {
data->length = 0;
}
fclose(fp);
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
writeByteStringToFile(const char *const path, const UA_ByteString *data) {
UA_StatusCode retval = UA_STATUSCODE_GOOD;
/* Open the file */
FILE *fp = fopen(path, "wb");
if(!fp)
return UA_STATUSCODE_BADINTERNALERROR;
/* Write byte string to file */
size_t len = fwrite(data->data, sizeof(UA_Byte), data->length * sizeof(UA_Byte), fp);
if(len != data->length) {
fclose(fp);
retval = UA_STATUSCODE_BADINTERNALERROR;
}
fclose(fp);
return retval;
}
static UA_StatusCode
readCertificates(UA_ByteString **list, size_t *listSize, const UA_String path) {
UA_StatusCode retval = UA_STATUSCODE_GOOD;

View File

@ -0,0 +1,64 @@
/* 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 2024 (c) Fraunhofer IOSB (Author: Noel Graf)
*/
#include "ua_filestore_common.h"
#include <stdio.h>
#ifdef UA_ENABLE_ENCRYPTION
#ifdef __linux__ /* Linux only so far */
UA_StatusCode
readFileToByteString(const char *const path, UA_ByteString *data) {
if(path == NULL || data == NULL)
return UA_STATUSCODE_BADINTERNALERROR;
/* Open the file */
FILE *fp = fopen(path, "rb");
if(!fp)
return UA_STATUSCODE_BADNOTFOUND;
/* Get the file length, allocate the data and read */
fseek(fp, 0, SEEK_END);
UA_StatusCode retval = UA_ByteString_allocBuffer(data, (size_t)ftell(fp));
if(retval == UA_STATUSCODE_GOOD) {
fseek(fp, 0, SEEK_SET);
size_t read = fread(data->data, sizeof(UA_Byte), data->length * sizeof(UA_Byte), fp);
if(read != data->length) {
UA_ByteString_clear(data);
}
} else {
data->length = 0;
}
fclose(fp);
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
writeByteStringToFile(const char *const path, const UA_ByteString *data) {
UA_StatusCode retval = UA_STATUSCODE_GOOD;
/* Open the file */
FILE *fp = fopen(path, "wb");
if(!fp)
return UA_STATUSCODE_BADINTERNALERROR;
/* Write byte string to file */
size_t len = fwrite(data->data, sizeof(UA_Byte), data->length * sizeof(UA_Byte), fp);
if(len != data->length) {
fclose(fp);
retval = UA_STATUSCODE_BADINTERNALERROR;
}
fclose(fp);
return retval;
}
#endif
#endif

View File

@ -0,0 +1,24 @@
/* 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 2024 (c) Fraunhofer IOSB (Author: Noel Graf)
*/
#include <open62541/util.h>
#ifdef UA_ENABLE_ENCRYPTION
#ifdef __linux__ /* Linux only so far */
UA_StatusCode
readFileToByteString(const char *const path,
UA_ByteString *data);
UA_StatusCode
writeByteStringToFile(const char *const path,
const UA_ByteString *data);
#endif /* __linux__ */
#endif /* UA_ENABLE_ENCRYPTION */

View File

@ -7,11 +7,10 @@
* Copyright 2024 (c) Fraunhofer IOSB (Author: Noel Graf)
*/
#include <open62541/util.h>
#include <open62541/plugin/securitypolicy_default.h>
#include <open62541/plugin/log_stdout.h>
#include "mp_printf.h"
#include "ua_filestore_common.h"
#ifdef UA_ENABLE_ENCRYPTION
@ -24,59 +23,12 @@
#include <bits/stdio_lim.h>
#endif // !__ANDROID__
typedef struct FileCertStore {
typedef struct {
/* In-Memory security policy as a base */
UA_SecurityPolicy *innerPolicy;
UA_String storePath;
} SecurityPolicy_FilestoreContext;
static UA_StatusCode
readFileToByteString(const char *const path, UA_ByteString *data) {
if(path == NULL || data == NULL)
return UA_STATUSCODE_BADINTERNALERROR;
/* Open the file */
FILE *fp = fopen(path, "rb");
if(!fp)
return UA_STATUSCODE_BADNOTFOUND;
/* Get the file length, allocate the data and read */
fseek(fp, 0, SEEK_END);
UA_StatusCode retval = UA_ByteString_allocBuffer(data, (size_t)ftell(fp));
if(retval == UA_STATUSCODE_GOOD) {
fseek(fp, 0, SEEK_SET);
size_t read = fread(data->data, sizeof(UA_Byte), data->length * sizeof(UA_Byte), fp);
if(read != data->length) {
UA_ByteString_clear(data);
}
} else {
data->length = 0;
}
fclose(fp);
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
writeByteStringToFile(const char *const path, const UA_ByteString *data) {
UA_StatusCode retval = UA_STATUSCODE_GOOD;
/* Open the file */
FILE *fp = fopen(path, "wb");
if(!fp)
return UA_STATUSCODE_BADINTERNALERROR;
/* Write byte string to file */
size_t len = fwrite(data->data, sizeof(UA_Byte), data->length * sizeof(UA_Byte), fp);
if(len != data->length) {
fclose(fp);
retval = UA_STATUSCODE_BADINTERNALERROR;
}
fclose(fp);
return retval;
}
static bool
checkCertificateInFilestore(char *path, const UA_ByteString newCertificate) {
/* Check if the file is already stored in CertStore */

View File

@ -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 "

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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_assert(channel->processOPNHeader);
res = channel->processOPNHeader(channel->processOPNHeaderApplication,
channel, &asymHeader);
UA_CHECK_STATUS(res, goto error);
if(!channel->securityPolicy)
res = UA_STATUSCODE_BADINTERNALERROR;
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,185 +705,17 @@ 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);
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;
}
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)
return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID;
/* Sum up the lengths */
messageSize += chunk->bytes.length;
if(chunk->chunkType == UA_CHUNKTYPE_FINAL)
break;
}
/* Allocate memory for the full message */
UA_ByteString payload;
res = UA_ByteString_allocBuffer(&payload, messageSize);
UA_CHECK_STATUS(res, return res);
/* 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;
}
/* 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, &copy);
UA_CHECK_STATUS(res, return res);
chunk->bytes = copy;
chunk->copied = true;
}
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;
}
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);
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 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;
if((channel->config.localMaxChunkCount != 0 &&
channel->decryptedChunksCount > channel->config.localMaxChunkCount) ||
(channel->config.localMaxMessageSize != 0 &&
channel->decryptedChunksLength > channel->config.localMaxMessageSize)) {
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;
}
/* 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);
}
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;
}
/* Decoding cannot fail */
/* Decoding the header cannot fail */
UA_TcpMessageHeader hdr;
UA_StatusCode res =
UA_decodeBinaryInternal(buffer, &initial_offset, &hdr,
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 */
@ -892,96 +730,265 @@ extractCompleteChunk(UA_SecureChannel *channel, const UA_ByteString *buffer,
if(hdr.messageSize > channel->config.recvBufferSize)
return UA_STATUSCODE_BADTCPMESSAGETOOLARGE;
/* Incomplete chunk */
if(hdr.messageSize > remaining) {
*done = true;
/* Incomplete chunk. Continue processing later. */
if(hdr.messageSize > remaining)
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;
/* Check the chunk type before decrypting */
if(chunkType != UA_CHUNKTYPE_FINAL &&
chunkType != UA_CHUNKTYPE_INTERMEDIATE &&
chunkType != UA_CHUNKTYPE_ABORT)
return UA_STATUSCODE_BADTCPMESSAGETYPEINVALID;
}
/* 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);
chunk->bytes = chunkPayload;
/* 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;
SIMPLEQ_INSERT_TAIL(&channel->completeChunks, chunk, pointers);
/* 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;
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;
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;
default:
res = UA_STATUSCODE_BADTCPMESSAGETYPEINVALID;
break;
}
return res;
}
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;
}
/* Use the new buffer directly */
channel->unprocessed = buffer;
channel->unprocessedCopied = false;
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_SecureChannel_processBuffer(UA_SecureChannel *channel, void *application,
UA_ProcessMessageCallback callback,
const UA_ByteString *buffer,
UA_SecureChannel_getCompleteMessage(UA_SecureChannel *channel,
UA_MessageType *messageType, UA_UInt32 *requestId,
UA_ByteString *payload, UA_Boolean *copied,
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_Chunk chunk, *pchunk;
UA_StatusCode res = UA_STATUSCODE_GOOD;
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->chunksCount >= channel->config.localMaxChunkCount) ||
(channel->config.localMaxMessageSize != 0 &&
channel->chunksLength + chunk.bytes.length > channel->config.localMaxMessageSize)) {
if(chunk.copied)
UA_ByteString_clear(&chunk.bytes);
return UA_STATUSCODE_BADTCPMESSAGETOOLARGE;
}
/* 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);
/* 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;
case UA_CHUNKTYPE_FINAL:
default:
UA_assert(chunk.chunkType == UA_CHUNKTYPE_FINAL); /* Was checked before */
break; /* A final chunk was received -- assemble the message */
}
/* 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);
/* 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;
}
/* Process whatever we can. Chunks of completed and processed messages are
* removed. */
res = processChunks(channel, application, callback, nowMonotonic);
UA_CHECK_STATUS(res, goto cleanup);
/* 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;
}
/* 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);
/* Assemble the full payload and store it in chunk.bytes */
if(messageSize > chunk.bytes.length) {
UA_assert(first != NULL);
cleanup:
UA_ByteString_clear(&appended);
/* 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);
}
/* 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);
/* Set assembled message as the content of the final chunk */
if(chunk.copied)
UA_ByteString_clear(&chunk.bytes);
chunk.bytes = message;
chunk.copied = true;
}
/* Return the assembled message */
*requestId = chunk.requestId;
*messageType = chunk.messageType;
*payload = chunk.bytes;
*copied = chunk.copied;
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_SecureChannel_persistBuffer(UA_SecureChannel *channel) {
UA_StatusCode res = UA_STATUSCODE_GOOD;
/* 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;
}
/* 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;
}
/* 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;
}

View File

@ -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,23 +272,29 @@ 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_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 */
void

View File

@ -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;
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;
return UA_STATUSCODE_GOOD;
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

View File

@ -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;

View File

@ -360,7 +360,8 @@ START_TEST(CreateReaderGroup) {
memset(&transportSettingsData, 0,
sizeof(UA_BrokerDataSetReaderTransportDataType));
UA_ReaderGroup *rg = UA_ReaderGroup_findRGbyId(server, readerGroupIdent);
UA_PubSubManager *psm = getPSM(server);
UA_ReaderGroup *rg = UA_ReaderGroup_find(psm, readerGroupIdent);
ck_assert(rg != 0);
UA_ExtensionObject *ts = &rg->config.transportSettings;

View File

@ -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));
}

View File

@ -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,6 @@ 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
COMMAND ${ARG_CONV_EXCL_ENV} ${Python3_EXECUTABLE} ${open62541_TOOLS_DIR}/generate_datatypes.py
${NAMESPACE_MAP_TMP}
${SELECTED_TYPES_TMP}
@ -395,7 +393,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}