mirror of
https://github.com/open62541/open62541.git
synced 2025-06-03 04:00:21 +00:00
Merge remote-tracking branch 'origin/1.4' into merge_14_master_24
This commit is contained in:
commit
3785ed5c4d
2
.gitignore
vendored
2
.gitignore
vendored
@ -90,6 +90,8 @@ Pipfile.lock
|
|||||||
.vscode
|
.vscode
|
||||||
/.vs
|
/.vs
|
||||||
**/libxml2
|
**/libxml2
|
||||||
|
debian/*
|
||||||
|
/CMakeSettings.json
|
||||||
# clangd cache
|
# clangd cache
|
||||||
.cache/
|
.cache/
|
||||||
# project local vim settings
|
# project local vim settings
|
||||||
|
@ -44,7 +44,7 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
|||||||
# overwritten with more detailed information if git is available.
|
# overwritten with more detailed information if git is available.
|
||||||
set(OPEN62541_VER_MAJOR 1)
|
set(OPEN62541_VER_MAJOR 1)
|
||||||
set(OPEN62541_VER_MINOR 4)
|
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_LABEL "-undefined") # like "-rc1" or "-g4538abcd" or "-g4538abcd-dirty"
|
||||||
set(OPEN62541_VER_COMMIT "unknown-commit")
|
set(OPEN62541_VER_COMMIT "unknown-commit")
|
||||||
|
|
||||||
@ -496,6 +496,11 @@ if(UA_ENABLE_TPM2_SECURITY)
|
|||||||
list(APPEND open62541_LIBRARIES ${TPM2_LIB})
|
list(APPEND open62541_LIBRARIES ${TPM2_LIB})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(MINGW)
|
||||||
|
# GCC stack protector support
|
||||||
|
list(APPEND open62541_LIBRARIES ws2_32 ssp)
|
||||||
|
endif()
|
||||||
|
|
||||||
#####################
|
#####################
|
||||||
# Compiler Settings #
|
# 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/eventloop.h
|
||||||
${PROJECT_SOURCE_DIR}/include/open62541/plugin/nodestore.h
|
${PROJECT_SOURCE_DIR}/include/open62541/plugin/nodestore.h
|
||||||
${PROJECT_SOURCE_DIR}/include/open62541/plugin/historydatabase.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/client.h
|
||||||
${PROJECT_SOURCE_DIR}/include/open62541/server.h
|
|
||||||
${PROJECT_SOURCE_DIR}/include/open62541/client_highlevel.h
|
${PROJECT_SOURCE_DIR}/include/open62541/client_highlevel.h
|
||||||
${PROJECT_SOURCE_DIR}/include/open62541/client_subscriptions.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
|
# 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.
|
# 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)
|
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)
|
${PROJECT_SOURCE_DIR}/plugins/crypto/ua_securitypolicy_filestore.c)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(UA_ENABLE_ENCRYPTION_MBEDTLS OR UA_ENABLE_AMALGAMATION)
|
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
|
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_common.c
|
||||||
${PROJECT_SOURCE_DIR}/plugins/crypto/mbedtls/securitypolicy_basic128rsa15.c
|
${PROJECT_SOURCE_DIR}/plugins/crypto/mbedtls/securitypolicy_basic128rsa15.c
|
||||||
${PROJECT_SOURCE_DIR}/plugins/crypto/mbedtls/securitypolicy_basic256.c
|
${PROJECT_SOURCE_DIR}/plugins/crypto/mbedtls/securitypolicy_basic256.c
|
||||||
@ -999,8 +1007,9 @@ if(UA_ENABLE_ENCRYPTION_MBEDTLS OR UA_ENABLE_AMALGAMATION)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(UA_ENABLE_ENCRYPTION_OPENSSL OR UA_ENABLE_ENCRYPTION_LIBRESSL OR UA_ENABLE_AMALGAMATION)
|
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
|
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_common.c
|
||||||
${PROJECT_SOURCE_DIR}/plugins/crypto/openssl/securitypolicy_basic128rsa15.c
|
${PROJECT_SOURCE_DIR}/plugins/crypto/openssl/securitypolicy_basic128rsa15.c
|
||||||
${PROJECT_SOURCE_DIR}/plugins/crypto/openssl/securitypolicy_basic256.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_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)")
|
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")
|
if(UA_NAMESPACE_ZERO STREQUAL "FULL")
|
||||||
# Use the "full" schema files also for datatypes and statuscodes
|
# Use the "full" schema files also for datatypes and statuscodes
|
||||||
set(UA_SCHEMA_DIR ${UA_NODESET_DIR}/Schema CACHE INTERNAL "")
|
set(UA_SCHEMA_DIR ${UA_NODESET_DIR}/Schema CACHE INTERNAL "")
|
||||||
|
|
||||||
# Set the full Nodeset for NS0.
|
# Set the full Nodeset for NS0
|
||||||
# Use a new variable UA_FILE_NS0_INTERNAL so we don't pollute the options.
|
if(NOT UA_FILE_NS0_PRIVATE)
|
||||||
if(UA_FILE_NS0)
|
set(UA_FILE_NS0_PRIVATE ${UA_SCHEMA_DIR}/Opc.Ua.NodeSet2.xml)
|
||||||
set(UA_FILE_NS0_INTERNAL ${UA_FILE_NS0})
|
|
||||||
else()
|
|
||||||
set(UA_FILE_NS0_INTERNAL ${UA_SCHEMA_DIR}/Opc.Ua.NodeSet2.xml CACHE INTERNAL "")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Check that the submodule was checked out or manually downloaded into the folder
|
# 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")
|
message(STATUS "Submodule update")
|
||||||
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
|
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
|
||||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||||
@ -1071,12 +1082,9 @@ else()
|
|||||||
# Directory with the schema files for installation
|
# Directory with the schema files for installation
|
||||||
set(UA_SCHEMA_DIR ${PROJECT_SOURCE_DIR}/tools/schema CACHE INTERNAL "")
|
set(UA_SCHEMA_DIR ${PROJECT_SOURCE_DIR}/tools/schema CACHE INTERNAL "")
|
||||||
|
|
||||||
# Set the reduced Nodeset for NS0.
|
# Set the reduced Nodeset for NS0
|
||||||
# Use a new variable UA_FILE_NS0_INTERNAL so we don't pollute the options.
|
if(NOT UA_FILE_NS0_PRIVATE)
|
||||||
if(UA_FILE_NS0)
|
set(UA_FILE_NS0_PRIVATE ${UA_SCHEMA_DIR}/Opc.Ua.NodeSet2.Reduced.xml CACHE INTERNAL "")
|
||||||
set(UA_FILE_NS0_INTERNAL ${UA_FILE_NS0})
|
|
||||||
else()
|
|
||||||
set(UA_FILE_NS0_INTERNAL ${UA_SCHEMA_DIR}/Opc.Ua.NodeSet2.Reduced.xml CACHE INTERNAL "")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Set feature-specific datatypes definitions and nodesets
|
# Set feature-specific datatypes definitions and nodesets
|
||||||
@ -1135,7 +1143,7 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
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_NODEIDS ${UA_SCHEMA_DIR}/NodeIds.csv)
|
||||||
set(UA_FILE_STATUSCODES ${UA_SCHEMA_DIR}/StatusCode.csv)
|
set(UA_FILE_STATUSCODES ${UA_SCHEMA_DIR}/StatusCode.csv)
|
||||||
set(UA_FILE_TYPES_BSD ${UA_SCHEMA_DIR}/Opc.Ua.Types.bsd)
|
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
|
# statuscode explanation
|
||||||
add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/open62541/statuscodes.h
|
add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/open62541/statuscodes.h
|
||||||
${PROJECT_BINARY_DIR}/src_generated/open62541/statuscodes.c
|
${PROJECT_BINARY_DIR}/src_generated/open62541/statuscodes.c
|
||||||
PRE_BUILD
|
|
||||||
COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/generate_statuscode_descriptions.py
|
COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/generate_statuscode_descriptions.py
|
||||||
${UA_FILE_STATUSCODES} ${PROJECT_BINARY_DIR}/src_generated/open62541/statuscodes
|
${UA_FILE_STATUSCODES} ${PROJECT_BINARY_DIR}/src_generated/open62541/statuscodes
|
||||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/tools/generate_statuscode_descriptions.py
|
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)
|
if(UA_ENABLE_AMALGAMATION)
|
||||||
# single-file release
|
# single-file release
|
||||||
add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/open62541.h
|
add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/open62541.h
|
||||||
PRE_BUILD
|
|
||||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/amalgamate.py
|
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/amalgamate.py
|
||||||
${OPEN62541_VERSION} ${CMAKE_CURRENT_BINARY_DIR}/open62541.h
|
${OPEN62541_VERSION} ${CMAKE_CURRENT_BINARY_DIR}/open62541.h
|
||||||
${exported_headers} ${NODESETLOADER_PUBLIC_HEADERS} ${plugin_headers}
|
${exported_headers} ${NODESETLOADER_PUBLIC_HEADERS} ${plugin_headers}
|
||||||
@ -1189,7 +1195,6 @@ if(UA_ENABLE_AMALGAMATION)
|
|||||||
${exported_headers} ${plugin_headers})
|
${exported_headers} ${plugin_headers})
|
||||||
|
|
||||||
add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/open62541.c
|
add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/open62541.c
|
||||||
PRE_BUILD
|
|
||||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/amalgamate.py
|
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/amalgamate.py
|
||||||
${OPEN62541_VERSION} ${CMAKE_CURRENT_BINARY_DIR}/open62541.c
|
${OPEN62541_VERSION} ${CMAKE_CURRENT_BINARY_DIR}/open62541.c
|
||||||
${lib_headers} ${NODESETLOADER_PRIVATE_HEADERS} ${lib_sources} ${plugin_sources}
|
${lib_headers} ${NODESETLOADER_PRIVATE_HEADERS} ${lib_sources} ${plugin_sources}
|
||||||
@ -1216,7 +1221,7 @@ if(UA_ENABLE_NODESET_INJECTOR)
|
|||||||
message(STATUS "Nodesetinjector feature enabled")
|
message(STATUS "Nodesetinjector feature enabled")
|
||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/open62541/nodesetinjector.h
|
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
|
COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/nodeset_injector/generate_nodesetinjector.py
|
||||||
${PROJECT_BINARY_DIR}/src_generated/open62541/nodesetinjector)
|
${PROJECT_BINARY_DIR}/src_generated/open62541/nodesetinjector)
|
||||||
add_custom_target(open62541-generator-nodesetinjector DEPENDS
|
add_custom_target(open62541-generator-nodesetinjector DEPENDS
|
||||||
|
@ -22,14 +22,12 @@ list(APPEND GENERATED_RST "")
|
|||||||
# Generated type definitions
|
# Generated type definitions
|
||||||
add_custom_command(OUTPUT ${DOC_SRC_DIR}/types_generated.rst
|
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}
|
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)
|
DEPENDS ${PROJECT_BINARY_DIR}/src_generated/open62541/types_generated.rst)
|
||||||
list(APPEND GENERATED_RST ${DOC_SRC_DIR}/types_generated.rst)
|
list(APPEND GENERATED_RST ${DOC_SRC_DIR}/types_generated.rst)
|
||||||
|
|
||||||
macro(generate_rst in out)
|
macro(generate_rst in out)
|
||||||
add_custom_command(OUTPUT ${out}
|
add_custom_command(OUTPUT ${out}
|
||||||
DEPENDS ${PROJECT_SOURCE_DIR}/tools/c2rst.py ${in}
|
DEPENDS ${PROJECT_SOURCE_DIR}/tools/c2rst.py ${in}
|
||||||
PRE_BUILD
|
|
||||||
COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/c2rst.py ${in} ${out})
|
COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/c2rst.py ${in} ${out})
|
||||||
list(APPEND GENERATED_RST "${out}")
|
list(APPEND GENERATED_RST "${out}")
|
||||||
endmacro()
|
endmacro()
|
||||||
|
@ -4,17 +4,15 @@
|
|||||||
* Copyright (c) 2022 Fraunhofer IOSB (Author: Noel Graf)
|
* Copyright (c) 2022 Fraunhofer IOSB (Author: Noel Graf)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <open62541/plugin/log_stdout.h>
|
|
||||||
#include <open62541/server.h>
|
#include <open62541/server.h>
|
||||||
#include <open62541/server_pubsub.h>
|
#include <open62541/server_pubsub.h>
|
||||||
|
#include <open62541/plugin/log_stdout.h>
|
||||||
#if defined(UA_ENABLE_PUBSUB_ENCRYPTION)
|
#if defined(UA_ENABLE_PUBSUB_ENCRYPTION)
|
||||||
#include <open62541/plugin/securitypolicy_default.h>
|
#include <open62541/plugin/securitypolicy_default.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#define CONNECTION_NAME "MQTT Subscriber Connection"
|
#define CONNECTION_NAME "MQTT Subscriber Connection"
|
||||||
#define TRANSPORT_PROFILE_URI_UADP "http://opcfoundation.org/UA-Profile/Transport/pubsub-mqtt-uadp"
|
#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"
|
#define TRANSPORT_PROFILE_URI_JSON "http://opcfoundation.org/UA-Profile/Transport/pubsub-mqtt-json"
|
||||||
|
@ -42,10 +42,7 @@ static const struct {
|
|||||||
{{0, UA_STRING_STATIC("max-rejected-listsize")}, &UA_TYPES[UA_TYPES_STRING], false}
|
{{0, UA_STRING_STATIC("max-rejected-listsize")}, &UA_TYPES[UA_TYPES_STRING], false}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MemoryCertStore;
|
typedef struct {
|
||||||
typedef struct MemoryCertStore MemoryCertStore;
|
|
||||||
|
|
||||||
struct MemoryCertStore {
|
|
||||||
UA_TrustListDataType trustList;
|
UA_TrustListDataType trustList;
|
||||||
size_t rejectedCertificatesSize;
|
size_t rejectedCertificatesSize;
|
||||||
UA_ByteString *rejectedCertificates;
|
UA_ByteString *rejectedCertificates;
|
||||||
@ -59,7 +56,9 @@ struct MemoryCertStore {
|
|||||||
mbedtls_x509_crt issuerCertificates;
|
mbedtls_x509_crt issuerCertificates;
|
||||||
mbedtls_x509_crl trustedCrls;
|
mbedtls_x509_crl trustedCrls;
|
||||||
mbedtls_x509_crl issuerCrls;
|
mbedtls_x509_crl issuerCrls;
|
||||||
};
|
|
||||||
|
UA_CertificateGroup *cg;
|
||||||
|
} MemoryCertStore;
|
||||||
|
|
||||||
static UA_Boolean mbedtlsCheckCA(mbedtls_x509_crt *cert);
|
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);
|
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.
|
/* Return the first matching issuer candidate AFTER prev.
|
||||||
* This can return the cert itself if self-signed. */
|
* This can return the cert itself if self-signed. */
|
||||||
static mbedtls_x509_crt *
|
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) {
|
mbedtls_x509_crt *cert, mbedtls_x509_crt *prev) {
|
||||||
char inbuf[UA_MBEDTLS_MAX_DN_LENGTH];
|
char inbuf[UA_MBEDTLS_MAX_DN_LENGTH];
|
||||||
int nameLen = mbedtls_x509_dn_gets(inbuf, UA_MBEDTLS_MAX_DN_LENGTH, &cert->issuer);
|
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)))
|
mbedtls_pk_can_do(&i->pk, cert->MBEDTLS_PRIVATE(sig_pk)))
|
||||||
return i;
|
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);
|
} while(stack);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static UA_Boolean
|
static UA_StatusCode
|
||||||
mbedtlsCheckRevoked(MemoryCertStore *context, mbedtls_x509_crt *cert) {
|
mbedtlsCheckRevoked(MemoryCertStore *ctx, mbedtls_x509_crt *cert) {
|
||||||
|
/* Parse the Issuer Name */
|
||||||
char inbuf[UA_MBEDTLS_MAX_DN_LENGTH];
|
char inbuf[UA_MBEDTLS_MAX_DN_LENGTH];
|
||||||
int nameLen = mbedtls_x509_dn_gets(inbuf, UA_MBEDTLS_MAX_DN_LENGTH, &cert->issuer);
|
int nameLen = mbedtls_x509_dn_gets(inbuf, UA_MBEDTLS_MAX_DN_LENGTH, &cert->issuer);
|
||||||
if(nameLen < 0)
|
if(nameLen < 0)
|
||||||
return true;
|
return UA_STATUSCODE_BADINTERNALERROR;
|
||||||
UA_String issuerName = {(size_t)nameLen, (UA_Byte*)inbuf};
|
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) &&
|
if(ctx->trustedCrls.raw.len == 0 && ctx->issuerCrls.raw.len == 0) {
|
||||||
mbedtls_x509_crt_is_revoked(cert, crl) != 0)
|
UA_LOG_WARNING(ctx->cg->logging, UA_LOGCATEGORY_SECURITYPOLICY,
|
||||||
return true;
|
"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) &&
|
/* Loop over the crl and match the Issuer Name */
|
||||||
mbedtls_x509_crt_is_revoked(cert, crl) != 0)
|
UA_StatusCode res = UA_STATUSCODE_BADCERTIFICATEREVOCATIONUNKNOWN;
|
||||||
return true;
|
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 */
|
/* 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
|
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) {
|
mbedtls_x509_crt *cert, int depth) {
|
||||||
/* Maxiumum chain length */
|
/* Maxiumum chain length */
|
||||||
if(depth == UA_MBEDTLS_MAX_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 :
|
return (depth == 0) ? UA_STATUSCODE_BADCERTIFICATETIMEINVALID :
|
||||||
UA_STATUSCODE_BADCERTIFICATEISSUERTIMEINVALID;
|
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
|
/* Return the most specific error code. BADCERTIFICATECHAININCOMPLETE is
|
||||||
* returned only if all possible chains are incomplete. */
|
* returned only if all possible chains are incomplete. */
|
||||||
mbedtls_x509_crt *issuer = NULL;
|
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
|
/* Find the issuer. This can return the same certificate if it is
|
||||||
* self-signed (subject == issuer). We come back here to try a different
|
* self-signed (subject == issuer). We come back here to try a different
|
||||||
* "path" if a subsequent verification fails. */
|
* "path" if a subsequent verification fails. */
|
||||||
issuer = mbedtlsFindNextIssuer(context, stack, cert, issuer);
|
issuer = mbedtlsFindNextIssuer(ctx, stack, cert, issuer);
|
||||||
if(!issuer)
|
if(!issuer)
|
||||||
break;
|
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
|
* chain. We check whether the certificate is trusted below. This is the
|
||||||
* only place where we return UA_STATUSCODE_BADCERTIFICATEUNTRUSTED.
|
* only place where we return UA_STATUSCODE_BADCERTIFICATEUNTRUSTED.
|
||||||
* This signals that the chain is complete (but can be still
|
* This signals that the chain is complete (but can be still
|
||||||
* untrusted). */
|
* untrusted).
|
||||||
if(issuer == cert || (cert->tbs.len == issuer->tbs.len &&
|
*
|
||||||
memcmp(cert->tbs.p, issuer->tbs.p, cert->tbs.len) == 0)) {
|
* 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;
|
ret = UA_STATUSCODE_BADCERTIFICATEUNTRUSTED;
|
||||||
continue;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Detect (endless) loops of issuers. The last one can be skipped by the
|
/* Verification Step: Revocation Check */
|
||||||
* check for self-signed just before. */
|
ret = mbedtlsCheckRevoked(ctx, cert);
|
||||||
for(int i = 0; i < depth - 1; i++) {
|
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)
|
if(old_issuers[i] == issuer)
|
||||||
return UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE;
|
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
|
/* We have found the issuer certificate used for the signature. Recurse
|
||||||
* to the next certificate in the chain (verify the current issuer). */
|
* 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
|
/* The chain is complete, but we haven't yet identified a trusted
|
||||||
* certificate "on the way down". Can we trust this certificate? */
|
* certificate "on the way down". Can we trust this certificate? */
|
||||||
if(ret == UA_STATUSCODE_BADCERTIFICATEUNTRUSTED) {
|
if(ret == UA_STATUSCODE_BADCERTIFICATEUNTRUSTED) {
|
||||||
for(mbedtls_x509_crt *t = &context->trustedCertificates; t; t = t->next) {
|
for(mbedtls_x509_crt *t = &ctx->trustedCertificates; t; t = t->next) {
|
||||||
if(cert->tbs.len == t->tbs.len &&
|
if(mbedtlsSameBuf(&cert->tbs, &t->tbs))
|
||||||
memcmp(cert->tbs.p, t->tbs.p, cert->tbs.len) == 0)
|
|
||||||
return UA_STATUSCODE_GOOD;
|
return UA_STATUSCODE_GOOD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -627,6 +667,7 @@ UA_CertificateGroup_Memorystore(UA_CertificateGroup *certGroup,
|
|||||||
retval = UA_STATUSCODE_BADOUTOFMEMORY;
|
retval = UA_STATUSCODE_BADOUTOFMEMORY;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
context->cg = certGroup;
|
||||||
certGroup->context = context;
|
certGroup->context = context;
|
||||||
/* Default values */
|
/* Default values */
|
||||||
context->maxTrustListSize = 65535;
|
context->maxTrustListSize = 65535;
|
||||||
|
550
plugins/crypto/mbedtls/ua_mbedtls_create_certificate.c
Normal file
550
plugins/crypto/mbedtls/ua_mbedtls_create_certificate.c
Normal 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
|
@ -53,8 +53,13 @@ struct MemoryCertStore {
|
|||||||
STACK_OF(X509) *trustedCertificates;
|
STACK_OF(X509) *trustedCertificates;
|
||||||
STACK_OF(X509) *issuerCertificates;
|
STACK_OF(X509) *issuerCertificates;
|
||||||
STACK_OF(X509_CRL) *crls;
|
STACK_OF(X509_CRL) *crls;
|
||||||
|
|
||||||
|
UA_CertificateGroup *cg;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static UA_Boolean
|
||||||
|
openSSLCheckCA(X509 *cert);
|
||||||
|
|
||||||
static UA_StatusCode
|
static UA_StatusCode
|
||||||
MemoryCertStore_removeFromTrustList(UA_CertificateGroup *certGroup, const UA_TrustListDataType *trustList) {
|
MemoryCertStore_removeFromTrustList(UA_CertificateGroup *certGroup, const UA_TrustListDataType *trustList) {
|
||||||
/* Check parameter */
|
/* Check parameter */
|
||||||
@ -155,7 +160,7 @@ openSSLFindCrls(UA_CertificateGroup *certGroup, const UA_ByteString *certificate
|
|||||||
|
|
||||||
/* Check if the certificate is a CA certificate.
|
/* Check if the certificate is a CA certificate.
|
||||||
* Only a CA certificate can have a CRL. */
|
* Only a CA certificate can have a CRL. */
|
||||||
if(!X509_check_ca(cert)) {
|
if(!openSSLCheckCA(cert)) {
|
||||||
UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SERVER,
|
UA_LOG_WARNING(certGroup->logging, UA_LOGCATEGORY_SERVER,
|
||||||
"The certificate is not a CA certificate and therefore does not have a CRL.");
|
"The certificate is not a CA certificate and therefore does not have a CRL.");
|
||||||
X509_free(cert);
|
X509_free(cert);
|
||||||
@ -449,6 +454,7 @@ static X509 *
|
|||||||
openSSLFindNextIssuer(MemoryCertStore *ctx, STACK_OF(X509) *stack, X509 *x509, X509 *prev) {
|
openSSLFindNextIssuer(MemoryCertStore *ctx, STACK_OF(X509) *stack, X509 *x509, X509 *prev) {
|
||||||
/* First check issuers from the stack - provided in the same bytestring as
|
/* First check issuers from the stack - provided in the same bytestring as
|
||||||
* the certificate. This can also return x509 itself. */
|
* the certificate. This can also return x509 itself. */
|
||||||
|
X509_NAME *in = X509_get_issuer_name(x509);
|
||||||
do {
|
do {
|
||||||
int size = sk_X509_num(stack);
|
int size = sk_X509_num(stack);
|
||||||
for(int i = 0; i < size; i++) {
|
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.
|
/* 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
|
* It does not verify the validity period and if the issuer key was
|
||||||
* used for the signature. We check that afterwards. */
|
* 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;
|
return candidate;
|
||||||
}
|
}
|
||||||
/* Switch to search in the ctx->skIssue list */
|
/* Switch from the stack that came with the cert to the issuer list and
|
||||||
stack = (stack != ctx->issuerCertificates) ? ctx->issuerCertificates : NULL;
|
* 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);
|
} while(stack);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Is the certificate a CA? */
|
||||||
static UA_Boolean
|
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) {
|
openSSLCheckRevoked(MemoryCertStore *ctx, X509 *cert) {
|
||||||
const ASN1_INTEGER *sn = X509_get0_serialNumber(cert);
|
const ASN1_INTEGER *sn = X509_get0_serialNumber(cert);
|
||||||
const X509_NAME *in = X509_get_issuer_name(cert);
|
const X509_NAME *in = X509_get_issuer_name(cert);
|
||||||
int size = sk_X509_CRL_num(ctx->crls);
|
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++) {
|
for(int i = 0; i < size; i++) {
|
||||||
/* The crl contains a list of serial numbers from the same issuer */
|
/* The crl contains a list of serial numbers from the same issuer */
|
||||||
X509_CRL *crl = sk_X509_CRL_value(ctx->crls, i);
|
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++) {
|
for(int j = 0; j < rsize; j++) {
|
||||||
X509_REVOKED *r = sk_X509_REVOKED_value(rs, j);
|
X509_REVOKED *r = sk_X509_REVOKED_value(rs, j);
|
||||||
if(ASN1_INTEGER_cmp(sn, X509_REVOKED_get0_serialNumber(r)) == 0)
|
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
|
#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 :
|
return (depth == 0) ? UA_STATUSCODE_BADCERTIFICATETIMEINVALID :
|
||||||
UA_STATUSCODE_BADCERTIFICATEISSUERTIMEINVALID;
|
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
|
/* Return the most specific error code. BADCERTIFICATECHAININCOMPLETE is
|
||||||
* returned only if all possible chains are incomplete. */
|
* returned only if all possible chains are incomplete. */
|
||||||
X509 *issuer = NULL;
|
X509 *issuer = NULL;
|
||||||
@ -525,7 +563,7 @@ openSSL_verifyChain(MemoryCertStore *ctx, STACK_OF(X509) *stack, X509 **old_issu
|
|||||||
|
|
||||||
/* Verification Step: Certificate Usage
|
/* Verification Step: Certificate Usage
|
||||||
* Can the issuer act as CA? Omit for self-signed leaf certificates. */
|
* 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;
|
ret = UA_STATUSCODE_BADCERTIFICATEISSUERUSENOTALLOWED;
|
||||||
continue;
|
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
|
* chain. We check whether the certificate is trusted below. This is the
|
||||||
* only place where we return UA_STATUSCODE_BADCERTIFICATEUNTRUSTED.
|
* only place where we return UA_STATUSCODE_BADCERTIFICATEUNTRUSTED.
|
||||||
* This signals that the chain is complete (but can be still
|
* 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) {
|
if(cert == issuer || X509_cmp(cert, issuer) == 0) {
|
||||||
ret = UA_STATUSCODE_BADCERTIFICATEUNTRUSTED;
|
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
|
/* Detect (endless) loops of issuers. The last one can be skipped by the
|
||||||
* check for self-signed just before. */
|
* check for self-signed just before. */
|
||||||
for(int i = 0; i < depth; i++) {
|
for(int i = 0; i < depth; i++) {
|
||||||
@ -582,20 +634,19 @@ verifyCertificate(UA_CertificateGroup *certGroup, const UA_ByteString *certifica
|
|||||||
return UA_STATUSCODE_BADINTERNALERROR;
|
return UA_STATUSCODE_BADINTERNALERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UA_StatusCode ret = UA_STATUSCODE_GOOD;
|
||||||
MemoryCertStore *context = (MemoryCertStore *)certGroup->context;
|
MemoryCertStore *context = (MemoryCertStore *)certGroup->context;
|
||||||
if(context->reloadRequired) {
|
if(context->reloadRequired) {
|
||||||
UA_StatusCode retval = reloadCertificates(certGroup);
|
ret = reloadCertificates(certGroup);
|
||||||
if(retval != UA_STATUSCODE_GOOD) {
|
if(ret != UA_STATUSCODE_GOOD)
|
||||||
return retval;
|
return ret;
|
||||||
}
|
|
||||||
context->reloadRequired = false;
|
context->reloadRequired = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Verification Step: Certificate Structure */
|
/* Verification Step: Certificate Structure */
|
||||||
STACK_OF(X509) *stack = openSSLLoadCertificateStack(*certificate);
|
STACK_OF(X509) *stack = openSSLLoadCertificateStack(*certificate);
|
||||||
if(!stack || sk_X509_num(stack) < 1) {
|
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;
|
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
|
* Refer the test case CTT/Security/Security Certificate Validation/029.js
|
||||||
* for more details. */
|
* for more details. */
|
||||||
X509 *leaf = sk_X509_value(stack, 0);
|
X509 *leaf = sk_X509_value(stack, 0);
|
||||||
if(X509_check_ca(leaf)) {
|
if(openSSLCheckCA(leaf)) {
|
||||||
sk_X509_pop_free(stack, X509_free);
|
sk_X509_pop_free(stack, X509_free);
|
||||||
return UA_STATUSCODE_BADCERTIFICATEUSENOTALLOWED;
|
return UA_STATUSCODE_BADCERTIFICATEUSENOTALLOWED;
|
||||||
}
|
}
|
||||||
@ -618,7 +669,7 @@ verifyCertificate(UA_CertificateGroup *certGroup, const UA_ByteString *certifica
|
|||||||
/* Verification Step: Build Certificate Chain
|
/* Verification Step: Build Certificate Chain
|
||||||
* We perform the checks for each certificate inside. */
|
* We perform the checks for each certificate inside. */
|
||||||
X509 *old_issuers[UA_OPENSSL_MAX_CHAIN_LENGTH];
|
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);
|
sk_X509_pop_free(stack, X509_free);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -676,6 +727,7 @@ UA_CertificateGroup_Memorystore(UA_CertificateGroup *certGroup,
|
|||||||
retval = UA_STATUSCODE_BADOUTOFMEMORY;
|
retval = UA_STATUSCODE_BADOUTOFMEMORY;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
context->cg = certGroup;
|
||||||
certGroup->context = context;
|
certGroup->context = context;
|
||||||
/* Default values */
|
/* Default values */
|
||||||
context->maxTrustListSize = 65535;
|
context->maxTrustListSize = 65535;
|
||||||
@ -1033,7 +1085,8 @@ UA_CertificateUtils_checkCA(const UA_ByteString *certificate) {
|
|||||||
if(!certificateX509)
|
if(!certificateX509)
|
||||||
return UA_STATUSCODE_BADSECURITYCHECKSFAILED;
|
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);
|
X509_free(certificateX509);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include <open62541/plugin/certificategroup_default.h>
|
#include <open62541/plugin/certificategroup_default.h>
|
||||||
#include <open62541/plugin/log_stdout.h>
|
#include <open62541/plugin/log_stdout.h>
|
||||||
|
|
||||||
|
#include "ua_filestore_common.h"
|
||||||
#include "mp_printf.h"
|
#include "mp_printf.h"
|
||||||
|
|
||||||
#ifdef UA_ENABLE_ENCRYPTION
|
#ifdef UA_ENABLE_ENCRYPTION
|
||||||
@ -166,53 +167,6 @@ getCertFileName(const char *path, const UA_ByteString *certificate,
|
|||||||
return retval;
|
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
|
static UA_StatusCode
|
||||||
readCertificates(UA_ByteString **list, size_t *listSize, const UA_String path) {
|
readCertificates(UA_ByteString **list, size_t *listSize, const UA_String path) {
|
||||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||||
|
64
plugins/crypto/ua_filestore_common.c
Normal file
64
plugins/crypto/ua_filestore_common.c
Normal 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
|
24
plugins/crypto/ua_filestore_common.h
Normal file
24
plugins/crypto/ua_filestore_common.h
Normal 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 */
|
@ -7,11 +7,10 @@
|
|||||||
* Copyright 2024 (c) Fraunhofer IOSB (Author: Noel Graf)
|
* Copyright 2024 (c) Fraunhofer IOSB (Author: Noel Graf)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <open62541/util.h>
|
|
||||||
#include <open62541/plugin/securitypolicy_default.h>
|
#include <open62541/plugin/securitypolicy_default.h>
|
||||||
#include <open62541/plugin/log_stdout.h>
|
|
||||||
|
|
||||||
#include "mp_printf.h"
|
#include "mp_printf.h"
|
||||||
|
#include "ua_filestore_common.h"
|
||||||
|
|
||||||
#ifdef UA_ENABLE_ENCRYPTION
|
#ifdef UA_ENABLE_ENCRYPTION
|
||||||
|
|
||||||
@ -24,59 +23,12 @@
|
|||||||
#include <bits/stdio_lim.h>
|
#include <bits/stdio_lim.h>
|
||||||
#endif // !__ANDROID__
|
#endif // !__ANDROID__
|
||||||
|
|
||||||
typedef struct FileCertStore {
|
typedef struct {
|
||||||
/* In-Memory security policy as a base */
|
/* In-Memory security policy as a base */
|
||||||
UA_SecurityPolicy *innerPolicy;
|
UA_SecurityPolicy *innerPolicy;
|
||||||
UA_String storePath;
|
UA_String storePath;
|
||||||
} SecurityPolicy_FilestoreContext;
|
} 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
|
static bool
|
||||||
checkCertificateInFilestore(char *path, const UA_ByteString newCertificate) {
|
checkCertificateInFilestore(char *path, const UA_ByteString newCertificate) {
|
||||||
/* Check if the file is already stored in CertStore */
|
/* Check if the file is already stored in CertStore */
|
||||||
|
@ -506,17 +506,19 @@ processMSGResponse(UA_Client *client, UA_UInt32 requestId,
|
|||||||
UA_clear(response, ac->responseType);
|
UA_clear(response, ac->responseType);
|
||||||
UA_free(ac);
|
UA_free(ac);
|
||||||
} else {
|
} 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 */
|
ac->syncResponse = NULL; /* Indicate that response was received */
|
||||||
|
if(retval == UA_STATUSCODE_GOOD)
|
||||||
|
retval = UA_STATUSCODE_GOODCOMPLETESASYNCHRONOUSLY;
|
||||||
}
|
}
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
UA_StatusCode
|
UA_StatusCode
|
||||||
processServiceResponse(void *application, UA_SecureChannel *channel,
|
processServiceResponse(UA_Client *client, UA_SecureChannel *channel,
|
||||||
UA_MessageType messageType, UA_UInt32 requestId,
|
UA_MessageType messageType, UA_UInt32 requestId,
|
||||||
UA_ByteString *message) {
|
UA_ByteString *message) {
|
||||||
UA_Client *client = (UA_Client*)application;
|
|
||||||
|
|
||||||
if(!UA_SecureChannel_isConnected(channel)) {
|
if(!UA_SecureChannel_isConnected(channel)) {
|
||||||
if(messageType == UA_MESSAGETYPE_MSG) {
|
if(messageType == UA_MESSAGETYPE_MSG) {
|
||||||
UA_LOG_DEBUG_CHANNEL(client->config.logging, channel, "Discard MSG message "
|
UA_LOG_DEBUG_CHANNEL(client->config.logging, channel, "Discard MSG message "
|
||||||
|
@ -1498,6 +1498,9 @@ verifyClientApplicationURI(const UA_Client *client) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
delayedNetworkCallback(void *application, void *context);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
__Client_networkCallback(UA_ConnectionManager *cm, uintptr_t connectionId,
|
__Client_networkCallback(UA_ConnectionManager *cm, uintptr_t connectionId,
|
||||||
void *application, void **connectionContext,
|
void *application, void **connectionContext,
|
||||||
@ -1576,13 +1579,42 @@ __Client_networkCallback(UA_ConnectionManager *cm, uintptr_t connectionId,
|
|||||||
client->channel.state = UA_SECURECHANNELSTATE_CONNECTING;
|
client->channel.state = UA_SECURECHANNELSTATE_CONNECTING;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Received a message. Process the message with the SecureChannel. */
|
|
||||||
UA_EventLoop *el = client->config.eventLoop;
|
UA_EventLoop *el = client->config.eventLoop;
|
||||||
UA_DateTime nowMonotonic = el->dateTime_nowMonotonic(el);
|
UA_DateTime nowMonotonic = el->dateTime_nowMonotonic(el);
|
||||||
UA_StatusCode res =
|
|
||||||
UA_SecureChannel_processBuffer(&client->channel, client,
|
/* Received a message. Process the message with the SecureChannel. */
|
||||||
processServiceResponse,
|
UA_StatusCode res = UA_SecureChannel_loadBuffer(&client->channel, msg);
|
||||||
&msg, nowMonotonic);
|
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) {
|
if(res != UA_STATUSCODE_GOOD) {
|
||||||
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
UA_LOG_ERROR(client->config.logging, UA_LOGCATEGORY_CLIENT,
|
||||||
"Processing the message returned the error code %s",
|
"Processing the message returned the error code %s",
|
||||||
@ -1614,6 +1646,18 @@ __Client_networkCallback(UA_ConnectionManager *cm, uintptr_t connectionId,
|
|||||||
UA_UNLOCK(&client->clientMutex);
|
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. */
|
/* Initialize a TCP connection. Writes the result to client->connectStatus. */
|
||||||
static void
|
static void
|
||||||
initConnect(UA_Client *client) {
|
initConnect(UA_Client *client) {
|
||||||
@ -1645,6 +1689,7 @@ initConnect(UA_Client *client) {
|
|||||||
client->channel.config = client->config.localConnectionConfig;
|
client->channel.config = client->config.localConnectionConfig;
|
||||||
client->channel.certificateVerification = &client->config.certificateVerification;
|
client->channel.certificateVerification = &client->config.certificateVerification;
|
||||||
client->channel.processOPNHeader = verifyClientSecureChannelHeader;
|
client->channel.processOPNHeader = verifyClientSecureChannelHeader;
|
||||||
|
client->channel.processOPNHeaderApplication = client;
|
||||||
|
|
||||||
/* Initialize the SecurityPolicy */
|
/* Initialize the SecurityPolicy */
|
||||||
client->connectStatus = initSecurityPolicy(client);
|
client->connectStatus = initSecurityPolicy(client);
|
||||||
@ -2052,6 +2097,7 @@ UA_Client_startListeningForReverseConnect(UA_Client *client,
|
|||||||
client->channel.config = client->config.localConnectionConfig;
|
client->channel.config = client->config.localConnectionConfig;
|
||||||
client->channel.certificateVerification = &client->config.certificateVerification;
|
client->channel.certificateVerification = &client->config.certificateVerification;
|
||||||
client->channel.processOPNHeader = verifyClientSecureChannelHeader;
|
client->channel.processOPNHeader = verifyClientSecureChannelHeader;
|
||||||
|
client->channel.processOPNHeaderApplication = client;
|
||||||
client->channel.connectionId = 0;
|
client->channel.connectionId = 0;
|
||||||
|
|
||||||
client->connectStatus = initSecurityPolicy(client);
|
client->connectStatus = initSecurityPolicy(client);
|
||||||
|
@ -196,7 +196,7 @@ UA_StatusCode
|
|||||||
__Client_renewSecureChannel(UA_Client *client);
|
__Client_renewSecureChannel(UA_Client *client);
|
||||||
|
|
||||||
UA_StatusCode
|
UA_StatusCode
|
||||||
processServiceResponse(void *application, UA_SecureChannel *channel,
|
processServiceResponse(UA_Client *client, UA_SecureChannel *channel,
|
||||||
UA_MessageType messageType, UA_UInt32 requestId,
|
UA_MessageType messageType, UA_UInt32 requestId,
|
||||||
UA_ByteString *message);
|
UA_ByteString *message);
|
||||||
|
|
||||||
|
@ -490,11 +490,9 @@ processMSG(UA_Server *server, UA_SecureChannel *channel,
|
|||||||
|
|
||||||
/* Takes decoded messages starting at the nodeid of the content type. */
|
/* Takes decoded messages starting at the nodeid of the content type. */
|
||||||
static UA_StatusCode
|
static UA_StatusCode
|
||||||
processSecureChannelMessage(void *application, UA_SecureChannel *channel,
|
processSecureChannelMessage(UA_Server *server, UA_SecureChannel *channel,
|
||||||
UA_MessageType messagetype, UA_UInt32 requestId,
|
UA_MessageType messagetype, UA_UInt32 requestId,
|
||||||
UA_ByteString *message) {
|
UA_ByteString *message) {
|
||||||
UA_Server *server = (UA_Server*)application;
|
|
||||||
|
|
||||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||||
switch(messagetype) {
|
switch(messagetype) {
|
||||||
case UA_MESSAGETYPE_HEL:
|
case UA_MESSAGETYPE_HEL:
|
||||||
@ -572,9 +570,12 @@ purgeFirstChannelWithoutSession(UA_BinaryProtocolManager *bpm) {
|
|||||||
static UA_StatusCode
|
static UA_StatusCode
|
||||||
configServerSecureChannel(void *application, UA_SecureChannel *channel,
|
configServerSecureChannel(void *application, UA_SecureChannel *channel,
|
||||||
const UA_AsymmetricAlgorithmSecurityHeader *asymHeader) {
|
const UA_AsymmetricAlgorithmSecurityHeader *asymHeader) {
|
||||||
|
if(channel->securityPolicy)
|
||||||
|
return UA_STATUSCODE_GOOD;
|
||||||
|
|
||||||
/* Iterate over available endpoints and choose the correct one */
|
/* Iterate over available endpoints and choose the correct one */
|
||||||
|
UA_Server *server = (UA_Server *)application;
|
||||||
UA_SecurityPolicy *securityPolicy = NULL;
|
UA_SecurityPolicy *securityPolicy = NULL;
|
||||||
UA_Server *const server = (UA_Server *const) application;
|
|
||||||
for(size_t i = 0; i < server->config.securityPoliciesSize; ++i) {
|
for(size_t i = 0; i < server->config.securityPoliciesSize; ++i) {
|
||||||
UA_SecurityPolicy *policy = &server->config.securityPolicies[i];
|
UA_SecurityPolicy *policy = &server->config.securityPolicies[i];
|
||||||
if(!UA_String_equal(&asymHeader->securityPolicyUri, &policy->policyUri))
|
if(!UA_String_equal(&asymHeader->securityPolicyUri, &policy->policyUri))
|
||||||
@ -644,6 +645,7 @@ createServerSecureChannel(UA_BinaryProtocolManager *bpm, UA_ConnectionManager *c
|
|||||||
channel->config = connConfig;
|
channel->config = connConfig;
|
||||||
channel->certificateVerification = &config->secureChannelPKI;
|
channel->certificateVerification = &config->secureChannelPKI;
|
||||||
channel->processOPNHeader = configServerSecureChannel;
|
channel->processOPNHeader = configServerSecureChannel;
|
||||||
|
channel->processOPNHeaderApplication = server;
|
||||||
channel->connectionManager = cm;
|
channel->connectionManager = cm;
|
||||||
channel->connectionId = connectionId;
|
channel->connectionId = connectionId;
|
||||||
|
|
||||||
@ -795,17 +797,15 @@ serverNetworkCallback(UA_ConnectionManager *cm, uintptr_t connectionId,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UA_LOG_INFO_CHANNEL(bpm->logging, channel, "SecureChannel created");
|
|
||||||
|
|
||||||
/* Set the new channel as the new context for the connection */
|
/* Set the new channel as the new context for the connection */
|
||||||
*connectionContext = (void*)channel;
|
*connectionContext = (void*)channel;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The connection has fully opened */
|
/* Set the channel state to CONNECTED until the HEL message is received */
|
||||||
if(channel->state < UA_SECURECHANNELSTATE_CONNECTED)
|
|
||||||
channel->state = UA_SECURECHANNELSTATE_CONNECTED;
|
channel->state = UA_SECURECHANNELSTATE_CONNECTED;
|
||||||
|
|
||||||
|
UA_LOG_INFO_CHANNEL(bpm->logging, channel, "SecureChannel created");
|
||||||
|
}
|
||||||
|
|
||||||
/* Received a message on a normal connection */
|
/* Received a message on a normal connection */
|
||||||
#ifdef UA_DEBUG_DUMP_PKGS
|
#ifdef UA_DEBUG_DUMP_PKGS
|
||||||
UA_dump_hex_pkg(message->data, message->length);
|
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_EventLoop *el = bpm->sc.server->config.eventLoop;
|
||||||
UA_DateTime nowMonotonic = el->dateTime_nowMonotonic(el);
|
UA_DateTime nowMonotonic = el->dateTime_nowMonotonic(el);
|
||||||
retval = UA_SecureChannel_processBuffer(channel, bpm->sc.server,
|
|
||||||
processSecureChannelMessage,
|
/* Process all complete messages */
|
||||||
&msg, nowMonotonic);
|
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) {
|
if(retval != UA_STATUSCODE_GOOD) {
|
||||||
UA_LOG_WARNING_CHANNEL(bpm->logging, channel,
|
UA_LOG_WARNING_CHANNEL(bpm->logging, channel,
|
||||||
"Processing the message failed with error %s",
|
"Processing the message failed with error %s",
|
||||||
@ -1256,13 +1272,28 @@ serverReverseConnectCallback(UA_ConnectionManager *cm, uintptr_t connectionId,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The connection is fully opened and we have a SecureChannel.
|
|
||||||
* Process the received buffer */
|
|
||||||
UA_EventLoop *el = bpm->sc.server->config.eventLoop;
|
UA_EventLoop *el = bpm->sc.server->config.eventLoop;
|
||||||
UA_DateTime nowMonotonic = el->dateTime_nowMonotonic(el);
|
UA_DateTime nowMonotonic = el->dateTime_nowMonotonic(el);
|
||||||
retval = UA_SecureChannel_processBuffer(context->channel, bpm->sc.server,
|
|
||||||
processSecureChannelMessage,
|
/* The connection is fully opened and we have a SecureChannel.
|
||||||
&msg, nowMonotonic);
|
* 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) {
|
if(retval != UA_STATUSCODE_GOOD) {
|
||||||
UA_LOG_WARNING_CHANNEL(bpm->logging, context->channel,
|
UA_LOG_WARNING_CHANNEL(bpm->logging, context->channel,
|
||||||
"Processing the message failed with error %s",
|
"Processing the message failed with error %s",
|
||||||
@ -1275,7 +1306,6 @@ serverReverseConnectCallback(UA_ConnectionManager *cm, uintptr_t connectionId,
|
|||||||
error.reason = UA_STRING_NULL;
|
error.reason = UA_STRING_NULL;
|
||||||
UA_SecureChannel_sendError(context->channel, &error);
|
UA_SecureChannel_sendError(context->channel, &error);
|
||||||
UA_SecureChannel_shutdown(context->channel, UA_SHUTDOWNREASON_ABORT);
|
UA_SecureChannel_shutdown(context->channel, UA_SHUTDOWNREASON_ABORT);
|
||||||
|
|
||||||
setReverseConnectState(bpm->sc.server, context, UA_SECURECHANNELSTATE_CLOSING);
|
setReverseConnectState(bpm->sc.server, context, UA_SECURECHANNELSTATE_CLOSING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -28,8 +28,7 @@ void
|
|||||||
UA_SecureChannel_init(UA_SecureChannel *channel) {
|
UA_SecureChannel_init(UA_SecureChannel *channel) {
|
||||||
/* Normal linked lists are initialized by zeroing out */
|
/* Normal linked lists are initialized by zeroing out */
|
||||||
memset(channel, 0, sizeof(UA_SecureChannel));
|
memset(channel, 0, sizeof(UA_SecureChannel));
|
||||||
SIMPLEQ_INIT(&channel->completeChunks);
|
TAILQ_INIT(&channel->chunks);
|
||||||
SIMPLEQ_INIT(&channel->decryptedChunks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UA_StatusCode
|
UA_StatusCode
|
||||||
@ -72,6 +71,8 @@ hideErrors(UA_TcpErrorMessage *const error) {
|
|||||||
case UA_STATUSCODE_BADCERTIFICATEUNTRUSTED:
|
case UA_STATUSCODE_BADCERTIFICATEUNTRUSTED:
|
||||||
case UA_STATUSCODE_BADCERTIFICATEREVOKED:
|
case UA_STATUSCODE_BADCERTIFICATEREVOKED:
|
||||||
case UA_STATUSCODE_BADCERTIFICATEISSUERREVOKED:
|
case UA_STATUSCODE_BADCERTIFICATEISSUERREVOKED:
|
||||||
|
case UA_STATUSCODE_BADCERTIFICATECHAININCOMPLETE:
|
||||||
|
case UA_STATUSCODE_BADCERTIFICATEISSUERUSENOTALLOWED:
|
||||||
error->error = UA_STATUSCODE_BADSECURITYCHECKSFAILED;
|
error->error = UA_STATUSCODE_BADSECURITYCHECKSFAILED;
|
||||||
error->reason = UA_STRING_NULL;
|
error->reason = UA_STRING_NULL;
|
||||||
break;
|
break;
|
||||||
@ -129,19 +130,21 @@ UA_Chunk_delete(UA_Chunk *chunk) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
deleteChunks(UA_ChunkQueue *queue) {
|
deleteChunks(UA_SecureChannel *channel) {
|
||||||
UA_Chunk *chunk;
|
UA_Chunk *chunk, *chunk_tmp;
|
||||||
while((chunk = SIMPLEQ_FIRST(queue))) {
|
TAILQ_FOREACH_SAFE(chunk, &channel->chunks, pointers, chunk_tmp) {
|
||||||
SIMPLEQ_REMOVE_HEAD(queue, pointers);
|
TAILQ_REMOVE(&channel->chunks, chunk, pointers);
|
||||||
UA_Chunk_delete(chunk);
|
UA_Chunk_delete(chunk);
|
||||||
}
|
}
|
||||||
|
channel->chunksCount = 0;
|
||||||
|
channel->chunksLength = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
UA_SecureChannel_deleteBuffered(UA_SecureChannel *channel) {
|
UA_SecureChannel_deleteBuffered(UA_SecureChannel *channel) {
|
||||||
deleteChunks(&channel->completeChunks);
|
deleteChunks(channel);
|
||||||
deleteChunks(&channel->decryptedChunks);
|
if(channel->unprocessedCopied)
|
||||||
UA_ByteString_clear(&channel->incompleteChunk);
|
UA_ByteString_clear(&channel->unprocessed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -172,6 +175,13 @@ UA_SecureChannel_clear(UA_SecureChannel *channel) {
|
|||||||
channel->channelContext = NULL;
|
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 */
|
/* The EventLoop connection is no longer valid */
|
||||||
channel->connectionId = 0;
|
channel->connectionId = 0;
|
||||||
channel->connectionManager = NULL;
|
channel->connectionManager = NULL;
|
||||||
@ -568,7 +578,7 @@ processSequenceNumberSym(UA_SecureChannel *channel, UA_UInt32 sequenceNumber) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static UA_StatusCode
|
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);
|
UA_assert(chunk->bytes.length >= UA_SECURECHANNEL_MESSAGE_MIN_LENGTH);
|
||||||
size_t offset = UA_SECURECHANNEL_MESSAGEHEADER_LENGTH; /* Skip the message header */
|
size_t offset = UA_SECURECHANNEL_MESSAGEHEADER_LENGTH; /* Skip the message header */
|
||||||
UA_UInt32 secureChannelId;
|
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 */
|
/* New channel, create a security policy context and attach */
|
||||||
if(!channel->securityPolicy) {
|
UA_assert(channel->processOPNHeader);
|
||||||
if(channel->processOPNHeader)
|
res = channel->processOPNHeader(channel->processOPNHeaderApplication,
|
||||||
res = channel->processOPNHeader(application, channel, &asymHeader);
|
channel, &asymHeader);
|
||||||
UA_CHECK_STATUS(res, goto error);
|
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 */
|
/* On the client side, take the SecureChannelId from the first response */
|
||||||
if(secureChannelId != 0 && channel->securityToken.channelId == 0)
|
if(secureChannelId != 0 && channel->securityToken.channelId == 0)
|
||||||
@ -699,185 +705,17 @@ unpackPayloadMSG(UA_SecureChannel *channel, UA_Chunk *chunk,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static UA_StatusCode
|
static UA_StatusCode
|
||||||
assembleProcessMessage(UA_SecureChannel *channel, void *application,
|
extractCompleteChunk(UA_SecureChannel *channel, UA_Chunk *chunk, UA_DateTime nowMonotonic) {
|
||||||
UA_ProcessMessageCallback callback) {
|
/* At least 8 byte needed for the header */
|
||||||
UA_Chunk *chunk = SIMPLEQ_FIRST(&channel->decryptedChunks);
|
size_t offset = channel->unprocessedOffset;
|
||||||
UA_assert(chunk != NULL);
|
size_t remaining = channel->unprocessed.length - offset;
|
||||||
|
if(remaining < UA_SECURECHANNEL_MESSAGEHEADER_LENGTH)
|
||||||
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, ©);
|
|
||||||
UA_CHECK_STATUS(res, return res);
|
|
||||||
chunk->bytes = copy;
|
|
||||||
chunk->copied = true;
|
|
||||||
}
|
|
||||||
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;
|
return UA_STATUSCODE_GOOD;
|
||||||
}
|
|
||||||
|
|
||||||
/* Decoding cannot fail */
|
/* Decoding the header cannot fail */
|
||||||
UA_TcpMessageHeader hdr;
|
UA_TcpMessageHeader hdr;
|
||||||
UA_StatusCode res =
|
UA_StatusCode res =
|
||||||
UA_decodeBinaryInternal(buffer, &initial_offset, &hdr,
|
UA_decodeBinaryInternal(&channel->unprocessed, &offset, &hdr,
|
||||||
&UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER], NULL);
|
&UA_TRANSPORT[UA_TRANSPORT_TCPMESSAGEHEADER], NULL);
|
||||||
UA_assert(res == UA_STATUSCODE_GOOD);
|
UA_assert(res == UA_STATUSCODE_GOOD);
|
||||||
(void)res; /* pacify compilers if assert is ignored */
|
(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)
|
if(hdr.messageSize > channel->config.recvBufferSize)
|
||||||
return UA_STATUSCODE_BADTCPMESSAGETOOLARGE;
|
return UA_STATUSCODE_BADTCPMESSAGETOOLARGE;
|
||||||
|
|
||||||
/* Incomplete chunk */
|
/* Incomplete chunk. Continue processing later. */
|
||||||
if(hdr.messageSize > remaining) {
|
if(hdr.messageSize > remaining)
|
||||||
*done = true;
|
|
||||||
return UA_STATUSCODE_GOOD;
|
return UA_STATUSCODE_GOOD;
|
||||||
}
|
|
||||||
|
|
||||||
/* ByteString with only this chunk. */
|
/* Set the chunk information */
|
||||||
UA_ByteString chunkPayload;
|
chunk->bytes.data = channel->unprocessed.data + channel->unprocessedOffset;
|
||||||
chunkPayload.data = &buffer->data[*offset];
|
chunk->bytes.length = hdr.messageSize;
|
||||||
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;
|
|
||||||
chunk->messageType = msgType;
|
chunk->messageType = msgType;
|
||||||
chunk->chunkType = chunkType;
|
chunk->chunkType = chunkType;
|
||||||
chunk->requestId = 0;
|
chunk->requestId = 0;
|
||||||
chunk->copied = false;
|
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;
|
return UA_STATUSCODE_GOOD;
|
||||||
}
|
}
|
||||||
|
|
||||||
UA_StatusCode
|
UA_StatusCode
|
||||||
UA_SecureChannel_processBuffer(UA_SecureChannel *channel, void *application,
|
UA_SecureChannel_getCompleteMessage(UA_SecureChannel *channel,
|
||||||
UA_ProcessMessageCallback callback,
|
UA_MessageType *messageType, UA_UInt32 *requestId,
|
||||||
const UA_ByteString *buffer,
|
UA_ByteString *payload, UA_Boolean *copied,
|
||||||
UA_DateTime nowMonotonic) {
|
UA_DateTime nowMonotonic) {
|
||||||
/* Prepend the incomplete last chunk. This is usually done in the
|
UA_Chunk chunk, *pchunk;
|
||||||
* networklayer. But we test for a buffered incomplete chunk here again to
|
UA_StatusCode res = UA_STATUSCODE_GOOD;
|
||||||
* work around "lazy" network layers. */
|
|
||||||
UA_ByteString appended = channel->incompleteChunk;
|
extract_chunk:
|
||||||
if(appended.length > 0) {
|
/* Extract+decode the next chunk from the buffer */
|
||||||
channel->incompleteChunk = UA_BYTESTRING_NULL;
|
memset(&chunk, 0, sizeof(UA_Chunk));
|
||||||
UA_Byte *t = (UA_Byte*)UA_realloc(appended.data, appended.length + buffer->length);
|
res = extractCompleteChunk(channel, &chunk, nowMonotonic);
|
||||||
UA_CHECK_MEM(t, UA_ByteString_clear(&appended);
|
if(chunk.bytes.length == 0 || res != UA_STATUSCODE_GOOD)
|
||||||
return UA_STATUSCODE_BADOUTOFMEMORY);
|
return res; /* Error or no complete chunk could be extracted */
|
||||||
memcpy(&t[appended.length], buffer->data, buffer->length);
|
|
||||||
appended.data = t;
|
/* Process the chunk */
|
||||||
appended.length += buffer->length;
|
switch(chunk.chunkType) {
|
||||||
buffer = &appended;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Loop over the received chunks */
|
/* Compute the message size */
|
||||||
size_t offset = 0;
|
size_t messageSize = chunk.bytes.length;
|
||||||
UA_Boolean done = false;
|
UA_Chunk *first = NULL;
|
||||||
UA_StatusCode res;
|
TAILQ_FOREACH(pchunk, &channel->chunks, pointers) {
|
||||||
while(!done) {
|
if(chunk.requestId != pchunk->requestId)
|
||||||
res = extractCompleteChunk(channel, buffer, &offset, &done);
|
continue;
|
||||||
UA_CHECK_STATUS(res, goto cleanup);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Buffer half-received chunk. Before processing the messages so that
|
/* Validate the assembled message size */
|
||||||
* processing is reentrant. */
|
if(channel->config.localMaxMessageSize != 0 &&
|
||||||
if(offset < buffer->length) {
|
channel->chunksLength > channel->config.localMaxMessageSize) {
|
||||||
res = persistIncompleteChunk(channel, buffer, offset);
|
if(chunk.copied)
|
||||||
UA_CHECK_STATUS(res, goto cleanup);
|
UA_ByteString_clear(&chunk.bytes);
|
||||||
|
return UA_STATUSCODE_BADTCPMESSAGETOOLARGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Process whatever we can. Chunks of completed and processed messages are
|
/* Assemble the full payload and store it in chunk.bytes */
|
||||||
* removed. */
|
if(messageSize > chunk.bytes.length) {
|
||||||
res = processChunks(channel, application, callback, nowMonotonic);
|
UA_assert(first != NULL);
|
||||||
UA_CHECK_STATUS(res, goto cleanup);
|
|
||||||
|
|
||||||
/* Persist full chunks that still point to the buffer. Can only return
|
/* Allocate the full memory and initialize with the first chunk content.
|
||||||
* UA_STATUSCODE_BADOUTOFMEMORY as an error code. So merging res works. */
|
* Use realloc to speed up. */
|
||||||
res |= persistCompleteChunks(&channel->completeChunks);
|
UA_ByteString message;
|
||||||
res |= persistCompleteChunks(&channel->decryptedChunks);
|
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;
|
||||||
|
|
||||||
cleanup:
|
/* Remove the the first chunk */
|
||||||
UA_ByteString_clear(&appended);
|
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;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ typedef struct UA_Session UA_Session;
|
|||||||
|
|
||||||
/* For chunked requests */
|
/* For chunked requests */
|
||||||
typedef struct UA_Chunk {
|
typedef struct UA_Chunk {
|
||||||
SIMPLEQ_ENTRY(UA_Chunk) pointers;
|
TAILQ_ENTRY(UA_Chunk) pointers;
|
||||||
UA_ByteString bytes;
|
UA_ByteString bytes;
|
||||||
UA_MessageType messageType;
|
UA_MessageType messageType;
|
||||||
UA_ChunkType chunkType;
|
UA_ChunkType chunkType;
|
||||||
@ -71,7 +71,7 @@ typedef struct UA_Chunk {
|
|||||||
* memory allocated for the chunk separately */
|
* memory allocated for the chunk separately */
|
||||||
} UA_Chunk;
|
} UA_Chunk;
|
||||||
|
|
||||||
typedef SIMPLEQ_HEAD(UA_ChunkQueue, UA_Chunk) UA_ChunkQueue;
|
typedef TAILQ_HEAD(UA_ChunkQueue, UA_Chunk) UA_ChunkQueue;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
UA_SECURECHANNELRENEWSTATE_NORMAL,
|
UA_SECURECHANNELRENEWSTATE_NORMAL,
|
||||||
@ -143,20 +143,19 @@ struct UA_SecureChannel {
|
|||||||
* used in the server) */
|
* used in the server) */
|
||||||
UA_Session *sessions;
|
UA_Session *sessions;
|
||||||
|
|
||||||
/* If a buffer is received, first all chunks are put into the completeChunks
|
/* (Decrypted) chunks waiting to be processed */
|
||||||
* queue. Then they are processed in order. This ensures that processing
|
UA_ChunkQueue chunks;
|
||||||
* buffers is reentrant with the correct processing order. (This has lead to
|
size_t chunksCount;
|
||||||
* problems in the client in the past.) */
|
size_t chunksLength;
|
||||||
UA_ChunkQueue completeChunks; /* Received full chunks that have not been
|
|
||||||
* decrypted so far */
|
/* Received buffer from which no chunks have been extracted so far */
|
||||||
UA_ChunkQueue decryptedChunks; /* Received chunks that were decrypted but
|
UA_ByteString unprocessed;
|
||||||
* not processed */
|
size_t unprocessedOffset;
|
||||||
size_t decryptedChunksCount;
|
UA_Boolean unprocessedCopied;
|
||||||
size_t decryptedChunksLength;
|
UA_DelayedCallback unprocessedDelayed;
|
||||||
UA_ByteString incompleteChunk; /* A half-received chunk (TCP is a
|
|
||||||
* streaming protocol) is stored here */
|
|
||||||
|
|
||||||
UA_CertificateGroup *certificateVerification;
|
UA_CertificateGroup *certificateVerification;
|
||||||
|
void *processOPNHeaderApplication;
|
||||||
UA_StatusCode (*processOPNHeader)(void *application, UA_SecureChannel *channel,
|
UA_StatusCode (*processOPNHeader)(void *application, UA_SecureChannel *channel,
|
||||||
const UA_AsymmetricAlgorithmSecurityHeader *asymHeader);
|
const UA_AsymmetricAlgorithmSecurityHeader *asymHeader);
|
||||||
};
|
};
|
||||||
@ -273,22 +272,28 @@ UA_MessageContext_abort(UA_MessageContext *mc);
|
|||||||
* Receive Message
|
* Receive Message
|
||||||
* --------------- */
|
* --------------- */
|
||||||
|
|
||||||
typedef UA_StatusCode
|
/* Process a received buffer. This always has these three steps:
|
||||||
(UA_ProcessMessageCallback)(void *application, UA_SecureChannel *channel,
|
*
|
||||||
UA_MessageType messageType, UA_UInt32 requestId,
|
* 1. loadBuffer: The chunks in the SecureChannel are cut into chunks.
|
||||||
UA_ByteString *message);
|
* The chunks can still point to the buffer.
|
||||||
|
* 2. getCompleteMessage: Assemble chunks into a complete message. This is
|
||||||
/* Process a received buffer. The callback function is called with the message
|
* repeated until an error occours or an empty message is returned.
|
||||||
* body if the message is complete. The message is removed afterwards. Returns
|
* 3. persistBuffer: Make a copy of the remaining unpprocessed bytestring. So
|
||||||
* if an irrecoverable error occured.
|
* that the NetworkManager can reuse or free the packet memory.
|
||||||
*
|
*
|
||||||
* Note that only MSG and CLO messages are decrypted. HEL/ACK/OPN/... are
|
* Note that only MSG and CLO messages are decrypted. HEL/ACK/OPN/... are
|
||||||
* forwarded verbatim to the application. */
|
* forwarded verbatim to the application. */
|
||||||
UA_StatusCode
|
UA_StatusCode
|
||||||
UA_SecureChannel_processBuffer(UA_SecureChannel *channel, void *application,
|
UA_SecureChannel_loadBuffer(UA_SecureChannel *channel, const UA_ByteString buffer);
|
||||||
UA_ProcessMessageCallback callback,
|
|
||||||
const UA_ByteString *buffer,
|
UA_StatusCode
|
||||||
UA_DateTime nowMonotonic);
|
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 */
|
/* Internal methods in ua_securechannel_crypto.h */
|
||||||
|
|
||||||
|
@ -453,18 +453,26 @@ START_TEST(SecureChannel_sendSymmetricMessage_invalidParameters) {
|
|||||||
} END_TEST
|
} END_TEST
|
||||||
|
|
||||||
static UA_StatusCode
|
static UA_StatusCode
|
||||||
process_callback(void *application, UA_SecureChannel *channel,
|
UA_SecureChannel_processBuffer(UA_SecureChannel *channel, int *chunks_processed,
|
||||||
UA_MessageType messageType, UA_UInt32 requestId,
|
const UA_ByteString buffer) {
|
||||||
UA_ByteString *message) {
|
UA_StatusCode res = UA_SecureChannel_loadBuffer(channel, buffer);
|
||||||
ck_assert_ptr_ne(message, NULL);
|
while(UA_LIKELY(res == UA_STATUSCODE_GOOD)) {
|
||||||
ck_assert_ptr_ne(application, NULL);
|
UA_MessageType messageType;
|
||||||
if(message == NULL || application == NULL)
|
UA_UInt32 requestId = 0;
|
||||||
return UA_STATUSCODE_BADINTERNALERROR;
|
UA_ByteString payload = UA_BYTESTRING_NULL;
|
||||||
ck_assert_uint_ne(message->length, 0);
|
UA_Boolean copied = false;
|
||||||
ck_assert_ptr_ne(message->data, NULL);
|
res = UA_SecureChannel_getCompleteMessage(channel, &messageType, &requestId,
|
||||||
int *chunks_processed = (int *)application;
|
&payload, &copied, UA_DateTime_nowMonotonic());
|
||||||
++*chunks_processed;
|
if(res != UA_STATUSCODE_GOOD || payload.length == 0)
|
||||||
return UA_STATUSCODE_GOOD;
|
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) {
|
START_TEST(SecureChannel_assemblePartialChunks) {
|
||||||
@ -476,21 +484,18 @@ START_TEST(SecureChannel_assemblePartialChunks) {
|
|||||||
buffer.length = 32;
|
buffer.length = 32;
|
||||||
|
|
||||||
UA_StatusCode retval =
|
UA_StatusCode retval =
|
||||||
UA_SecureChannel_processBuffer(&testChannel, &chunks_processed,
|
UA_SecureChannel_processBuffer(&testChannel, &chunks_processed, buffer);
|
||||||
process_callback, &buffer, UA_DateTime_nowMonotonic());
|
|
||||||
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected success");
|
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected success");
|
||||||
ck_assert_int_eq(chunks_processed, 1);
|
ck_assert_int_eq(chunks_processed, 1);
|
||||||
|
|
||||||
buffer.length = 16;
|
buffer.length = 16;
|
||||||
|
|
||||||
UA_SecureChannel_processBuffer(&testChannel, &chunks_processed,
|
UA_SecureChannel_processBuffer(&testChannel, &chunks_processed, buffer);
|
||||||
process_callback, &buffer, UA_DateTime_nowMonotonic());
|
|
||||||
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected success");
|
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected success");
|
||||||
ck_assert_int_eq(chunks_processed, 1);
|
ck_assert_int_eq(chunks_processed, 1);
|
||||||
|
|
||||||
buffer.data = &buffer.data[16];
|
buffer.data = &buffer.data[16];
|
||||||
UA_SecureChannel_processBuffer(&testChannel, &chunks_processed,
|
UA_SecureChannel_processBuffer(&testChannel, &chunks_processed, buffer);
|
||||||
process_callback, &buffer, UA_DateTime_nowMonotonic());
|
|
||||||
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected success");
|
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected success");
|
||||||
ck_assert_int_eq(chunks_processed, 2);
|
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";
|
"\x10\x00\x00\x00@\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff";
|
||||||
buffer.length = 48;
|
buffer.length = 48;
|
||||||
|
|
||||||
UA_SecureChannel_processBuffer(&testChannel, &chunks_processed,
|
UA_SecureChannel_processBuffer(&testChannel, &chunks_processed, buffer);
|
||||||
process_callback, &buffer, UA_DateTime_nowMonotonic());
|
|
||||||
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected success");
|
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected success");
|
||||||
ck_assert_int_eq(chunks_processed, 3);
|
ck_assert_int_eq(chunks_processed, 3);
|
||||||
|
|
||||||
buffer.data = &buffer.data[48];
|
buffer.data = &buffer.data[48];
|
||||||
buffer.length = 32;
|
buffer.length = 32;
|
||||||
|
|
||||||
UA_SecureChannel_processBuffer(&testChannel, &chunks_processed,
|
UA_SecureChannel_processBuffer(&testChannel, &chunks_processed, buffer);
|
||||||
process_callback, &buffer, UA_DateTime_nowMonotonic());
|
|
||||||
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected success");
|
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected success");
|
||||||
ck_assert_int_eq(chunks_processed, 4);
|
ck_assert_int_eq(chunks_processed, 4);
|
||||||
|
|
||||||
buffer.data = &buffer.data[32];
|
buffer.data = &buffer.data[32];
|
||||||
buffer.length = 16;
|
buffer.length = 16;
|
||||||
UA_SecureChannel_processBuffer(&testChannel, &chunks_processed,
|
UA_SecureChannel_processBuffer(&testChannel, &chunks_processed, buffer);
|
||||||
process_callback, &buffer, UA_DateTime_nowMonotonic());
|
|
||||||
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected success");
|
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected success");
|
||||||
ck_assert_int_eq(chunks_processed, 5);
|
ck_assert_int_eq(chunks_processed, 5);
|
||||||
} END_TEST
|
} END_TEST
|
||||||
|
@ -81,8 +81,9 @@ static void setup2(void) {
|
|||||||
ck_assert(server != NULL);
|
ck_assert(server != NULL);
|
||||||
UA_ServerConfig *config = UA_Server_getConfig(server);
|
UA_ServerConfig *config = UA_Server_getConfig(server);
|
||||||
|
|
||||||
char storePathDir[4096];
|
char storePathDir[32];
|
||||||
getcwd(storePathDir, 4096);
|
strcpy(storePathDir, "open62541-pki-XXXXXX");
|
||||||
|
mkdtemp(storePathDir);
|
||||||
|
|
||||||
const UA_String storePath = UA_STRING(storePathDir);
|
const UA_String storePath = UA_STRING(storePathDir);
|
||||||
|
|
||||||
@ -303,7 +304,7 @@ START_TEST(get_rejectedlist) {
|
|||||||
|
|
||||||
/* Secure client connect */
|
/* Secure client connect */
|
||||||
retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
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;
|
UA_ByteString *rejectedList = NULL;
|
||||||
size_t rejectedListSize = 0;
|
size_t rejectedListSize = 0;
|
||||||
|
@ -360,7 +360,8 @@ START_TEST(CreateReaderGroup) {
|
|||||||
memset(&transportSettingsData, 0,
|
memset(&transportSettingsData, 0,
|
||||||
sizeof(UA_BrokerDataSetReaderTransportDataType));
|
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);
|
ck_assert(rg != 0);
|
||||||
UA_ExtensionObject *ts = &rg->config.transportSettings;
|
UA_ExtensionObject *ts = &rg->config.transportSettings;
|
||||||
|
|
||||||
|
@ -30,11 +30,6 @@ static void setup(void) {
|
|||||||
UA_ServerConfig *config = UA_Server_getConfig(server);
|
UA_ServerConfig *config = UA_Server_getConfig(server);
|
||||||
ck_assert(config != 0);
|
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));
|
ck_assert_int_eq(UA_STATUSCODE_GOOD, UA_Server_run_startup(server));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +66,6 @@ function(ua_generate_nodeid_header)
|
|||||||
|
|
||||||
# Header containing defines for all NodeIds
|
# Header containing defines for all NodeIds
|
||||||
add_custom_command(OUTPUT ${UA_GEN_ID_OUTPUT_DIR}/${UA_GEN_ID_NAME}.h
|
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
|
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}
|
${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
|
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
|
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
|
${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
|
COMMAND ${ARG_CONV_EXCL_ENV} ${Python3_EXECUTABLE} ${open62541_TOOLS_DIR}/generate_datatypes.py
|
||||||
${NAMESPACE_MAP_TMP}
|
${NAMESPACE_MAP_TMP}
|
||||||
${SELECTED_TYPES_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
|
add_custom_command(OUTPUT ${UA_GEN_NS_OUTPUT_DIR}/namespace${FILE_SUFFIX}.c
|
||||||
${UA_GEN_NS_OUTPUT_DIR}/namespace${FILE_SUFFIX}.h
|
${UA_GEN_NS_OUTPUT_DIR}/namespace${FILE_SUFFIX}.h
|
||||||
PRE_BUILD
|
|
||||||
COMMAND ${Python3_EXECUTABLE} ${open62541_TOOLS_DIR}/nodeset_compiler/nodeset_compiler.py
|
COMMAND ${Python3_EXECUTABLE} ${open62541_TOOLS_DIR}/nodeset_compiler/nodeset_compiler.py
|
||||||
${GEN_INTERNAL_HEADERS}
|
${GEN_INTERNAL_HEADERS}
|
||||||
${GEN_NS0}
|
${GEN_NS0}
|
||||||
|
Loading…
Reference in New Issue
Block a user