mirror of
https://github.com/open62541/open62541.git
synced 2025-06-03 04:00:21 +00:00
feat(pubsub): KeyStorage and SetSecurityKeys (#5243)
* Add UA_ENABLE_PUBSUB_SKS Cmake option The SKS related features are enabled in open62541. The UA_ENABLE_PUBSUB_SKS cmake option adds the support to enable/disable SKS related features. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Add securityGroupId parameter to PubSub Group Config The PubSub groups (Reader/Writer) are associated to a security group by securityGroupId. The securityGroupId parameter is added to UA_WriterGroupConfig and UA_ReaderGroupConfig structs. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Add UA_PubSubKeyListItem struct The Keystorage contains a linked list of KeyItems. The KeyItems holds the information related to a key. The UA_PubSubKeyListItem struct forms the keyItems of the keystorage KeyList. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Add UA_PubSubKeyStorage struct A structured storage is required to store all the keys and related information, such as which security group, what security policy, how many PubSub Groups, and when to move to next keys. The UA_PubSubKeyStorage struct holds the list of keys used to secure the pubsub message for a security group and related information. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Add PubSubKeyStorageList to the pubSubManager The KeyStorage is managed by the pubSubManager object. There can be multiple key storages managed by a pubSubManager for different Security Groups. An UA_PubSubKeyStorage List is added to the pubSubManager, which contains the pointers to all the key Storages managed by the pubSubManager for different Security Groups. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Add KeyStorage pointer to ReaderGroup and WriterGroup struct A reference to a Keystorage is created when a ReaderGroup or WriterGroup group is created. This reference is used for the life cycle management of the KeyStorage. A non-owning pointer is added to WriterGroup and ReaderGroup structs to point to their associated keystorage in the server. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Add function to initialize a keyStorage An empty keystorage list is created when a ReaderGroup or a WriterGroup is created in server. The new KeyStorage is added to the server KeyList and initialized for a securityGroup. If a KeyStorage already exists in the server for a securityGroupId, then the referenceCount is incremented and the keyStorage is added to the initializing ReaderGroup or WriterGroup. In case of failure, the keystorage is cleaned and deleted. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Add storeSecurityKey function After the keystorage is initialized and added to the server keyList, the keys should be stored in the keystorage keylist. the storeSecurityKeys function takes current key and future key list and add it a KeyStorage in the server. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Add Mechanism for key Rollover after KeyLifeTime expires After the KeyLifeTime expires the Publisher and Subscriber are required to move to the next key in the existing list. The addMoveToNextKeyCallback function calculates the time to trigger the callback function and adds a timed server callback. The importKeyToChannelContext function takes the key material, divides the key into singing, encrypting and keyNonce part according to security Policy assigned to KeyStorage and adds them to the channelcontext of the PubSub Group. The moveToKeyCallback is the callback function to set the keys and add next callback for key Rollover. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Add API function update the existing keyStorage The existing KeyList is updated/replaced with the fetched list. If the currentTokenId is unknown in the existing list, then the existing keylist is replaced by the fetched KeyList. In othercases keystorage is updated and keylist is extended with new future keys. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Add removeKeyStorage function A keyStorage can be referenced by multiple PubSub Groups in a server. When a keyStorage is removed with removeKeyStorage function, it checks the number of of referenceCount parameter and decrements it by 1. If referenceCount was 1, then the keyStorage pointer is freed and removed from the server. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Add keyStorage in Server with ReaderGroup Each ReaderGroup is assocaited with a keyStorage. The keys are used in its channel context to secure the messages. A keyStorage is added/updated when a ReaderGroup is created in the server. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Add KeyStorage in server with WriterGroup Each WriterGroup is assocaited with a keyStorage. The keys are used in its channel context to secure the messages. A keyStorage is added/updated when a WriterGroup is created in the server. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Remove KeyStorage from server with ReaderGroup Each ReaderGroup is assocaited with a keyStorage. The keys are used in its channel context to secure the messages. A keyStorage is removed/updated when a ReaderGroup is removed from the server. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Remove KeyStorage from server with a WriterGroup Each WriterGroup is assocaited with a keyStorage. The keys are used in its channel context to secure the messages. A keyStorage is removed/updated when a WriterGroup is removed in the server. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Delete All KeyStorages from Server When PubSub Configuration is deleted, all the nested configuration and members are deleted from the server. The KeyStorages are also deleted when a PubSubManager is deleted, because KeyStorages does not serve the purpose anymore. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Add KeyStorage Test setup and unit tests A test suite is added for the keystorage and the following using tests are added to it - Initialization of empty keystorage - Initialization of keystorage with invalid PubSub security policy - storing keys into empty keystorage - move to next key after keylifetime expire - Setting keys to the channel context - Adding keyStorage with Writer and ReaderGroup - Adding a Reader/WriterGroup to existing Keystorage - Removing keyStorage with a Writer/ReaderGroup Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Add setSecurityKeys Method Node Callback The keys are pushed to the Publishers and Subscribers who do not have client functionality using setSecurityKeys Method Node. The setSecurityKeys callback implements the backend of the setSecurityKeys Method node exposed by the server. Checking for user credentials against PubSub Security Group Object nodes is unresolved becuase creation of Security Group Object is not implemented. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Add sks push test setup and unit tests Setups the PubSub SKS Push test suite and helper methods. The test suite includes a server with encryption and a client to connect with the server with encrypted channel. The following unit tests are added - insufficient Security Mode - MissingSecurityGroup - Setting Security Keys - Update CurrentKey from exisiting list - Update CurrentKey and add new future keys - Replace existing keys with new keys Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Add PubSubKeyStorage description The PubSubKeyStorage description is added to the header file. It also includes the implemented workflow of the KeyStorage API to interact with KeyStorage. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> * Integrate PubSub SKS build and unit test in CI The unit tests should be intergerated in the CI of open62541 pipeline to provide proper testing and integration support of the new feature. The build and unit tests of PubSub SKS are integrated in the CI pipeline. Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de> Signed-off-by: Muddasir Shakil <muddasir.shakil@linutronix.de>
This commit is contained in:
parent
737f0562fc
commit
b457206d24
3
.github/workflows/build_linux.yml
vendored
3
.github/workflows/build_linux.yml
vendored
@ -41,6 +41,9 @@ jobs:
|
||||
- build_name: "PubSub Encryption (MbedTLS) Build & Unit Tests (gcc)"
|
||||
cmd_deps: sudo apt-get install -y -qq libmbedtls-dev
|
||||
cmd_action: unit_tests_encryption_mbedtls_pubsub
|
||||
- build_name: "PubSub SKS Build & Unit Tests (gcc)"
|
||||
cmd_deps: sudo apt-get install -y -qq libmbedtls-dev
|
||||
cmd_action: unit_tests_pubsub_sks
|
||||
- build_name: "Encryption (OpenSSL) Build & Unit Tests (gcc)"
|
||||
cmd_deps: sudo apt-get install -y -qq openssl
|
||||
cmd_action: unit_tests_encryption OPENSSL
|
||||
|
@ -388,6 +388,16 @@ mark_as_advanced(UA_ENABLE_PUBSUB_MONITORING)
|
||||
option(UA_ENABLE_PUBSUB_BUFMALLOC "Enable allocation with static memory buffer for time critical PubSub parts" OFF)
|
||||
mark_as_advanced(UA_ENABLE_PUBSUB_BUFMALLOC)
|
||||
|
||||
option(UA_ENABLE_PUBSUB_SKS "Enable Security Key Service support for publisher and subscriber" OFF)
|
||||
mark_as_advanced(UA_ENABLE_PUBSUB_SKS)
|
||||
|
||||
if(UA_ENABLE_PUBSUB_SKS)
|
||||
message(WARNING "The PubSub SKS feature is under development and not yet ready.")
|
||||
if(NOT UA_ENABLE_PUBSUB_ENCRYPTION)
|
||||
message(FATAL_ERROR "PubSub SKS cannot be used with disabled UA_ENABLE_PUBSUB_ENCRYPTION")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
#RT and Transport PubSub settings
|
||||
option(UA_ENABLE_PUBSUB_ETH_UADP "Enable publish/subscribe UADP over Ethernet" OFF)
|
||||
|
||||
@ -957,6 +967,7 @@ set(internal_headers ${PROJECT_SOURCE_DIR}/deps/open62541_queue.h
|
||||
${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub.h
|
||||
${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_manager.h
|
||||
${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_ns0.h
|
||||
${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_keystorage.h
|
||||
${PROJECT_SOURCE_DIR}/src/server/ua_server_async.h
|
||||
${PROJECT_SOURCE_DIR}/src/server/ua_server_internal.h
|
||||
${PROJECT_SOURCE_DIR}/src/server/ua_services.h
|
||||
@ -990,6 +1001,7 @@ set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c
|
||||
${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_readergroup.c
|
||||
${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_manager.c
|
||||
${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_ns0.c
|
||||
${PROJECT_SOURCE_DIR}/src/pubsub/ua_pubsub_keystorage.c
|
||||
# services
|
||||
${PROJECT_SOURCE_DIR}/src/server/ua_services_view.c
|
||||
${PROJECT_SOURCE_DIR}/src/server/ua_services_method.c
|
||||
|
@ -76,6 +76,7 @@
|
||||
#cmakedefine UA_GENERATED_NAMESPACE_ZERO_FULL
|
||||
#cmakedefine UA_ENABLE_PUBSUB_MONITORING
|
||||
#cmakedefine UA_ENABLE_PUBSUB_BUFMALLOC
|
||||
#cmakedefine UA_ENABLE_PUBSUB_SKS
|
||||
|
||||
#cmakedefine UA_PACK_DEBIAN
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
* Copyright (c) 2019 Kalycito Infotech Private Limited
|
||||
* Copyright (c) 2021 Fraunhofer IOSB (Author: Jan Hermes)
|
||||
* Copyright (c) 2022 Siemens AG (Author: Thomas Fischer)
|
||||
* Copyright (c) 2022 Linutronix GmbH (Author: Muddasir Shakil)
|
||||
*/
|
||||
|
||||
#ifndef UA_SERVER_PUBSUB_H
|
||||
@ -519,6 +520,7 @@ typedef struct {
|
||||
UA_MessageSecurityMode securityMode; /* via the UA_WriterGroupDataType */
|
||||
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
|
||||
UA_PubSubSecurityPolicy *securityPolicy;
|
||||
UA_String securityGroupId;
|
||||
#endif
|
||||
} UA_WriterGroupConfig;
|
||||
|
||||
@ -810,6 +812,7 @@ typedef struct {
|
||||
UA_MessageSecurityMode securityMode;
|
||||
#ifdef UA_ENABLE_PUBSUB_ENCRYPTION
|
||||
UA_PubSubSecurityPolicy *securityPolicy;
|
||||
UA_String securityGroupId;
|
||||
#endif
|
||||
} UA_ReaderGroupConfig;
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
* Copyright (c) 2021 Fraunhofer IOSB (Author: Jan Hermes)
|
||||
* Copyright (c) 2022 Siemens AG (Author: Thomas Fischer)
|
||||
* Copyright (c) 2022 Fraunhofer IOSB (Author: Noel Graf)
|
||||
* Copyright (c) 2022 Linutronix GmbH (Author: Muddasir Shakil)
|
||||
*/
|
||||
|
||||
#ifndef UA_PUBSUB_H_
|
||||
@ -21,6 +22,10 @@
|
||||
#include "open62541_queue.h"
|
||||
#include "ua_pubsub_networkmessage.h"
|
||||
|
||||
#ifdef UA_ENABLE_PUBSUB_SKS
|
||||
#include <ua_pubsub_keystorage.h>
|
||||
#endif
|
||||
|
||||
/* The public configuration structs are defined in include/ua_plugin_pubsub.h */
|
||||
|
||||
_UA_BEGIN_DECLS
|
||||
@ -77,7 +82,7 @@ UA_StandaloneSubscribedDataSet_findSDSbyId(UA_Server *server, UA_NodeId identifi
|
||||
UA_StandaloneSubscribedDataSet *
|
||||
UA_StandaloneSubscribedDataSet_findSDSbyName(UA_Server *server, UA_String identifier);
|
||||
void
|
||||
UA_StandaloneSubscribedDataSet_clear(UA_Server *server, UA_StandaloneSubscribedDataSet *subscribedDataSet);
|
||||
UA_StandaloneSubscribedDataSet_clear(UA_Server *server, UA_StandaloneSubscribedDataSet *subscribedDataSet);
|
||||
|
||||
#define UA_LOG_PDS_INTERNAL(LOGGER, LEVEL, PDS, MSG, ...) \
|
||||
if(UA_LOGLEVEL <= UA_LOGLEVEL_##LEVEL) { \
|
||||
@ -274,6 +279,9 @@ struct UA_WriterGroup {
|
||||
UA_UInt32 securityTokenId;
|
||||
UA_UInt32 nonceSequenceNumber; /* To be part of the MessageNonce */
|
||||
void *securityPolicyContext;
|
||||
#ifdef UA_ENABLE_PUBSUB_SKS
|
||||
UA_PubSubKeyStorage *keyStorage; /* non-owning pointer to keyStorage*/
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
@ -465,6 +473,9 @@ struct UA_ReaderGroup {
|
||||
UA_UInt32 securityTokenId;
|
||||
UA_UInt32 nonceSequenceNumber; /* To be part of the MessageNonce */
|
||||
void *securityPolicyContext;
|
||||
#ifdef UA_ENABLE_PUBSUB_SKS
|
||||
UA_PubSubKeyStorage *keyStorage;
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
|
471
src/pubsub/ua_pubsub_keystorage.c
Normal file
471
src/pubsub/ua_pubsub_keystorage.c
Normal file
@ -0,0 +1,471 @@
|
||||
/* 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) 2019 ifak e.V. Magdeburg (Holger Zipper)
|
||||
* Copyright (c) 2022 Linutronix GmbH (Author: Muddasir Shakil)
|
||||
*/
|
||||
|
||||
#include "ua_pubsub_keystorage.h"
|
||||
|
||||
#ifdef UA_ENABLE_PUBSUB_SKS /* conditional compilation */
|
||||
|
||||
#include "server/ua_server_internal.h"
|
||||
|
||||
UA_PubSubKeyStorage *
|
||||
UA_Server_findKeyStorage(UA_Server *server, UA_String securityGroupId) {
|
||||
|
||||
if(!server || UA_String_isEmpty(&securityGroupId))
|
||||
return NULL;
|
||||
|
||||
UA_PubSubKeyStorage *outKeyStorage, *keyStorageTemp;
|
||||
LIST_FOREACH_SAFE(outKeyStorage, &server->pubSubManager.pubSubKeyList, keyStorageList,
|
||||
keyStorageTemp) {
|
||||
if(UA_String_equal(&outKeyStorage->securityGroupID, &securityGroupId)) {
|
||||
return outKeyStorage;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
UA_StatusCode
|
||||
UA_Server_findPubSubSecurityPolicy(UA_Server *server, const UA_String *securityPolicyUri,
|
||||
UA_PubSubSecurityPolicy **policy) {
|
||||
if(!server || !securityPolicyUri)
|
||||
return UA_STATUSCODE_BADINVALIDARGUMENT;
|
||||
|
||||
UA_StatusCode retval = UA_STATUSCODE_BADNOTFOUND;
|
||||
|
||||
UA_ServerConfig *config = UA_Server_getConfig(server);
|
||||
|
||||
for(size_t i = 0; i < config->pubSubConfig.securityPoliciesSize; i++) {
|
||||
if(UA_String_equal(securityPolicyUri,
|
||||
&config->pubSubConfig.securityPolicies[i].policyUri)) {
|
||||
*policy = &config->pubSubConfig.securityPolicies[i];
|
||||
retval = UA_STATUSCODE_GOOD;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void
|
||||
UA_PubSubKeyStorage_clearKeyList(UA_PubSubKeyStorage *keyStorage) {
|
||||
if(TAILQ_EMPTY(&keyStorage->keyList))
|
||||
return;
|
||||
|
||||
UA_PubSubKeyListItem *item;
|
||||
TAILQ_FOREACH(item, &keyStorage->keyList, keyListEntry){
|
||||
TAILQ_REMOVE(&keyStorage->keyList, item, keyListEntry);
|
||||
UA_ByteString_clear(&item->key);
|
||||
UA_free(item);
|
||||
}
|
||||
keyStorage->keyListSize = 0;
|
||||
}
|
||||
|
||||
void
|
||||
UA_PubSubKeyStorage_delete(UA_Server *server, UA_PubSubKeyStorage *keyStorage) {
|
||||
UA_assert(keyStorage != NULL);
|
||||
UA_assert(server != NULL);
|
||||
|
||||
/*Remove callback*/
|
||||
if(!keyStorage->callBackId) {
|
||||
removeCallback(server, keyStorage->callBackId);
|
||||
keyStorage->callBackId = 0;
|
||||
}
|
||||
|
||||
UA_PubSubKeyStorage_clearKeyList(keyStorage);
|
||||
|
||||
if(keyStorage->securityGroupID.data)
|
||||
UA_String_clear(&keyStorage->securityGroupID);
|
||||
|
||||
memset(keyStorage, 0, sizeof(UA_PubSubKeyStorage));
|
||||
|
||||
UA_free(keyStorage);
|
||||
}
|
||||
|
||||
UA_StatusCode
|
||||
UA_PubSubKeyStorage_init(UA_Server *server, const UA_String *securityGroupId,
|
||||
const UA_String *securityPolicyUri,
|
||||
UA_UInt32 maxPastKeyCount, UA_UInt32 maxFutureKeyCount,
|
||||
UA_PubSubKeyStorage *keyStorage) {
|
||||
UA_StatusCode retval = UA_STATUSCODE_BAD;
|
||||
|
||||
if(!server || !securityPolicyUri || !securityGroupId || !keyStorage) {
|
||||
retval = UA_STATUSCODE_BADINVALIDARGUMENT;
|
||||
goto error;
|
||||
}
|
||||
|
||||
UA_PubSubSecurityPolicy *policy;
|
||||
|
||||
UA_PubSubKeyStorage *tmpKeyStorage =
|
||||
UA_Server_findKeyStorage(server, *securityGroupId);
|
||||
if(tmpKeyStorage) {
|
||||
++tmpKeyStorage->referenceCount;
|
||||
keyStorage = tmpKeyStorage;
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
keyStorage->referenceCount = 1;
|
||||
|
||||
retval = UA_String_copy(securityGroupId, &keyStorage->securityGroupID);
|
||||
if(retval != UA_STATUSCODE_GOOD)
|
||||
goto error;
|
||||
|
||||
retval = UA_Server_findPubSubSecurityPolicy(server, securityPolicyUri,
|
||||
&policy);
|
||||
if(retval != UA_STATUSCODE_GOOD)
|
||||
goto error;
|
||||
keyStorage->policy = policy;
|
||||
|
||||
UA_UInt32 currentkeyCount = 1;
|
||||
keyStorage->maxKeyListSize =
|
||||
maxPastKeyCount + currentkeyCount + maxFutureKeyCount;
|
||||
keyStorage->maxPastKeyCount = maxPastKeyCount;
|
||||
keyStorage->maxFutureKeyCount = maxFutureKeyCount;
|
||||
|
||||
/*Add this keystorage to the server keystoragelist*/
|
||||
LIST_INSERT_HEAD(&server->pubSubManager.pubSubKeyList, keyStorage, keyStorageList);
|
||||
|
||||
return retval;
|
||||
error:
|
||||
UA_PubSubKeyStorage_delete(server, keyStorage);
|
||||
return retval;
|
||||
}
|
||||
|
||||
UA_StatusCode
|
||||
UA_PubSubKeyStorage_storeSecurityKeys(UA_Server *server, UA_PubSubKeyStorage *keyStorage,
|
||||
UA_UInt32 currentTokenId, const UA_ByteString *currentKey,
|
||||
UA_ByteString *futureKeys, size_t futureKeyCount,
|
||||
UA_Duration msKeyLifeTime) {
|
||||
|
||||
UA_assert(server);
|
||||
|
||||
UA_StatusCode retval = UA_STATUSCODE_BAD;
|
||||
|
||||
if(futureKeyCount > 0 && !futureKeys) {
|
||||
retval = UA_STATUSCODE_BADARGUMENTSMISSING;
|
||||
goto error;
|
||||
}
|
||||
|
||||
size_t keyNumber = futureKeyCount;
|
||||
|
||||
if(currentKey && keyStorage->keyListSize == 0) {
|
||||
|
||||
keyStorage->keyListSize++;
|
||||
UA_PubSubKeyListItem *keyItem =
|
||||
(UA_PubSubKeyListItem *)UA_calloc(1, sizeof(UA_PubSubKeyListItem));
|
||||
if(!keyItem)
|
||||
goto error;
|
||||
retval = UA_ByteString_copy(currentKey, &keyItem->key);
|
||||
if(UA_StatusCode_isBad(retval))
|
||||
goto error;
|
||||
|
||||
keyItem->keyID = currentTokenId;
|
||||
|
||||
TAILQ_INIT(&keyStorage->keyList);
|
||||
TAILQ_INSERT_HEAD(&keyStorage->keyList, keyItem, keyListEntry);
|
||||
}
|
||||
|
||||
UA_PubSubKeyListItem *keyListIterator = TAILQ_FIRST(&keyStorage->keyList);
|
||||
UA_UInt32 startingTokenID = currentTokenId + 1;
|
||||
for(size_t i = 0; i < keyNumber; ++i) {
|
||||
retval = UA_PubSubKeyStorage_getKeyByKeyID(
|
||||
startingTokenID, keyStorage, &keyListIterator);
|
||||
/*Skipping key with matching KeyID in existing list*/
|
||||
if(retval == UA_STATUSCODE_BADNOTFOUND) {
|
||||
keyListIterator = UA_PubSubKeyStorage_push(keyStorage, &futureKeys[i], startingTokenID);
|
||||
if(!keyListIterator)
|
||||
goto error;
|
||||
|
||||
keyStorage->keyListSize++;
|
||||
}
|
||||
if(startingTokenID == UA_UINT32_MAX)
|
||||
startingTokenID = 1;
|
||||
else
|
||||
++startingTokenID;
|
||||
}
|
||||
|
||||
/*update keystorage references*/
|
||||
retval = UA_PubSubKeyStorage_getKeyByKeyID(currentTokenId, keyStorage, &keyStorage->currentItem);
|
||||
if (retval != UA_STATUSCODE_GOOD && !keyStorage->currentItem)
|
||||
goto error;
|
||||
|
||||
keyStorage->keyLifeTime = msKeyLifeTime;
|
||||
|
||||
return retval;
|
||||
error:
|
||||
if(keyStorage) {
|
||||
UA_PubSubKeyStorage_clearKeyList(keyStorage);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
UA_StatusCode
|
||||
UA_PubSubKeyStorage_getKeyByKeyID(const UA_UInt32 keyId, UA_PubSubKeyStorage *keyStorage,
|
||||
UA_PubSubKeyListItem **keyItem) {
|
||||
|
||||
if(!keyStorage)
|
||||
return UA_STATUSCODE_BADINVALIDARGUMENT;
|
||||
|
||||
UA_PubSubKeyListItem *item;
|
||||
TAILQ_FOREACH(item, &keyStorage->keyList, keyListEntry){
|
||||
if(item->keyID == keyId) {
|
||||
*keyItem = item;
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
}
|
||||
return UA_STATUSCODE_BADNOTFOUND;
|
||||
}
|
||||
|
||||
UA_PubSubKeyListItem *
|
||||
UA_PubSubKeyStorage_push(UA_PubSubKeyStorage *keyStorage, const UA_ByteString *key,
|
||||
UA_UInt32 keyID) {
|
||||
UA_PubSubKeyListItem *newItem = (UA_PubSubKeyListItem *)malloc(sizeof(UA_PubSubKeyListItem));
|
||||
if (!newItem)
|
||||
return NULL;
|
||||
|
||||
newItem->keyID = keyID;
|
||||
UA_ByteString_copy(key, &newItem->key);
|
||||
TAILQ_INSERT_TAIL(&keyStorage->keyList, newItem, keyListEntry);
|
||||
|
||||
return TAILQ_LAST(&keyStorage->keyList, keyListItems);
|
||||
}
|
||||
|
||||
UA_StatusCode
|
||||
UA_PubSubKeyStorage_addKeyRolloverCallback(UA_Server *server,
|
||||
UA_PubSubKeyStorage *keyStorage,
|
||||
UA_ServerCallback callback,
|
||||
UA_Duration timeToNextMs,
|
||||
UA_UInt64 *callbackID) {
|
||||
if(!server || !keyStorage || !callback || timeToNextMs <= 0)
|
||||
return UA_STATUSCODE_BADINVALIDARGUMENT;
|
||||
|
||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||
UA_DateTime now = UA_DateTime_nowMonotonic();
|
||||
|
||||
UA_DateTime dateTimeToNextKey = now + (UA_DateTime)(UA_DATETIME_MSEC * timeToNextMs);
|
||||
|
||||
retval = UA_Server_addTimedCallback(server, callback, keyStorage, dateTimeToNextKey,
|
||||
callbackID);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
splitCurrentKeyMaterial(UA_PubSubKeyStorage *keyStorage, UA_ByteString *signingKey,
|
||||
UA_ByteString *encryptingKey, UA_ByteString *keyNonce) {
|
||||
if(!keyStorage)
|
||||
return UA_STATUSCODE_BADNOTFOUND;
|
||||
|
||||
if(!keyStorage->policy)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
UA_PubSubSecurityPolicy *policy = keyStorage->policy;
|
||||
|
||||
UA_ByteString key = keyStorage->currentItem->key;
|
||||
|
||||
/*Check the main key length is the same according to policy*/
|
||||
if(key.length != policy->symmetricModule.secureChannelNonceLength)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
/*Get Key Length according to policy*/
|
||||
size_t signingkeyLength =
|
||||
policy->symmetricModule.cryptoModule.signatureAlgorithm.getLocalKeyLength(NULL);
|
||||
size_t encryptkeyLength =
|
||||
policy->symmetricModule.cryptoModule.encryptionAlgorithm.getLocalKeyLength(NULL);
|
||||
/*Rest of the part is the keyNonce*/
|
||||
size_t keyNonceLength = key.length - signingkeyLength - encryptkeyLength;
|
||||
|
||||
/*DivideKeys in origin ByteString*/
|
||||
UA_ByteString tSigningKey = {signingkeyLength, key.data};
|
||||
UA_ByteString_copy(&tSigningKey, signingKey);
|
||||
|
||||
UA_String tEncryptingKey = {encryptkeyLength, key.data + signingkeyLength};
|
||||
UA_ByteString_copy(&tEncryptingKey, encryptingKey);
|
||||
|
||||
UA_ByteString tKeyNonce = {keyNonceLength,
|
||||
key.data + signingkeyLength + encryptkeyLength};
|
||||
UA_ByteString_copy(&tKeyNonce, keyNonce);
|
||||
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
setPubSubGroupEncryptingKey(UA_Server *server, UA_NodeId PubSubGroupId, UA_UInt32 securityTokenId,
|
||||
UA_ByteString signingKey, UA_ByteString encryptingKey,
|
||||
UA_ByteString keyNonce) {
|
||||
UA_StatusCode retval = UA_STATUSCODE_BAD;
|
||||
retval = UA_Server_setWriterGroupEncryptionKeys(
|
||||
server, PubSubGroupId, securityTokenId, signingKey, encryptingKey, keyNonce);
|
||||
if(retval == UA_STATUSCODE_BADNOTFOUND)
|
||||
retval = UA_Server_setReaderGroupEncryptionKeys(
|
||||
server, PubSubGroupId, securityTokenId, signingKey, encryptingKey, keyNonce);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
setPubSubGroupEncryptingKeyForMatchingSecurityGroupId(
|
||||
UA_Server *server, UA_String securityGroupId, UA_UInt32 securityTokenId,
|
||||
UA_ByteString signingKey, UA_ByteString encryptingKey, UA_ByteString keyNonce) {
|
||||
UA_StatusCode retval = UA_STATUSCODE_BAD;
|
||||
UA_PubSubConnection *tmpPubSubConnections;
|
||||
|
||||
/* key storage is the same for all reader / writer groups, channel context isn't
|
||||
* => Update channelcontext in all Writergroups / ReaderGroups which have the same
|
||||
* securityGroupId*/
|
||||
TAILQ_FOREACH(tmpPubSubConnections, &server->pubSubManager.connections, listEntry) {
|
||||
/*ForEach writerGroup in server with matching SecurityGroupId*/
|
||||
UA_WriterGroup *tmpWriterGroup;
|
||||
LIST_FOREACH(tmpWriterGroup, &tmpPubSubConnections->writerGroups, listEntry) {
|
||||
|
||||
if(UA_String_equal(&tmpWriterGroup->config.securityGroupId,
|
||||
&securityGroupId)) {
|
||||
retval = UA_Server_setWriterGroupEncryptionKeys(
|
||||
server, tmpWriterGroup->identifier, securityTokenId, signingKey,
|
||||
encryptingKey, keyNonce);
|
||||
if(retval != UA_STATUSCODE_GOOD)
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
/*ForEach readerGroup in server with matching SecurityGroupId*/
|
||||
UA_ReaderGroup *tmpReaderGroup;
|
||||
LIST_FOREACH(tmpReaderGroup, &tmpPubSubConnections->readerGroups, listEntry) {
|
||||
|
||||
if(UA_String_equal(&tmpReaderGroup->config.securityGroupId,
|
||||
&securityGroupId)) {
|
||||
retval = UA_Server_setReaderGroupEncryptionKeys(
|
||||
server, tmpReaderGroup->identifier, securityTokenId, signingKey,
|
||||
encryptingKey, keyNonce);
|
||||
if(retval != UA_STATUSCODE_GOOD)
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
UA_StatusCode
|
||||
UA_PubSubKeyStorage_activateKeyToChannelContext(UA_Server *server, UA_NodeId pubSubGroupId,
|
||||
UA_String securityGroupId) {
|
||||
|
||||
if(securityGroupId.data == NULL)
|
||||
return UA_STATUSCODE_BADINVALIDARGUMENT;
|
||||
|
||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||
|
||||
UA_PubSubKeyStorage *keyStorage =
|
||||
UA_Server_findKeyStorage(server, securityGroupId);
|
||||
if(!keyStorage)
|
||||
return UA_STATUSCODE_BADNOTFOUND;
|
||||
|
||||
if(!keyStorage->policy && !(keyStorage->keyListSize > 0))
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
UA_UInt32 securityTokenId = keyStorage->currentItem->keyID;
|
||||
|
||||
/*DivideKeys in origin ByteString*/
|
||||
UA_ByteString signingKey;
|
||||
UA_ByteString encryptKey;
|
||||
UA_ByteString keyNonce;
|
||||
splitCurrentKeyMaterial(keyStorage, &signingKey, &encryptKey, &keyNonce);
|
||||
|
||||
if(!UA_NodeId_isNull(&pubSubGroupId))
|
||||
retval = setPubSubGroupEncryptingKey(server, pubSubGroupId, securityTokenId,
|
||||
signingKey, encryptKey, keyNonce);
|
||||
else
|
||||
retval = setPubSubGroupEncryptingKeyForMatchingSecurityGroupId(
|
||||
server, securityGroupId, securityTokenId, signingKey, encryptKey, keyNonce);
|
||||
|
||||
if(retval != UA_STATUSCODE_GOOD)
|
||||
UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER,
|
||||
"Failed to set Encrypting keys with Error: %s",
|
||||
UA_StatusCode_name(retval));
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
void
|
||||
UA_PubSubKeyStorage_keyRolloverCallback(UA_Server *server, UA_PubSubKeyStorage *keyStorage) {
|
||||
|
||||
UA_StatusCode retval = UA_PubSubKeyStorage_addKeyRolloverCallback(
|
||||
server, keyStorage, (UA_ServerCallback)UA_PubSubKeyStorage_keyRolloverCallback,
|
||||
keyStorage->keyLifeTime, &keyStorage->callBackId);
|
||||
if(retval != UA_STATUSCODE_GOOD) {
|
||||
UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER,
|
||||
"Failed to update keys for security group id '%.*s'. Reason: '%s'.",
|
||||
(int)keyStorage->securityGroupID.length,
|
||||
keyStorage->securityGroupID.data, UA_StatusCode_name(retval));
|
||||
}
|
||||
|
||||
if(keyStorage->currentItem != TAILQ_LAST(&keyStorage->keyList, keyListItems)) {
|
||||
keyStorage->currentItem = TAILQ_NEXT(keyStorage->currentItem, keyListEntry);
|
||||
keyStorage->currentTokenId = keyStorage->currentItem->keyID;
|
||||
|
||||
retval = UA_PubSubKeyStorage_activateKeyToChannelContext(
|
||||
server, UA_NODEID_NULL, keyStorage->securityGroupID);
|
||||
if(retval != UA_STATUSCODE_GOOD) {
|
||||
UA_LOG_ERROR(
|
||||
&server->config.logger, UA_LOGCATEGORY_SERVER,
|
||||
"Failed to update keys for security group id '%.*s'. Reason: '%s'.",
|
||||
(int)keyStorage->securityGroupID.length, keyStorage->securityGroupID.data,
|
||||
UA_StatusCode_name(retval));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UA_StatusCode
|
||||
UA_PubSubKeyStorage_update(UA_Server *server, UA_PubSubKeyStorage *keyStorage,
|
||||
const UA_ByteString *currentKey, UA_UInt32 currentKeyID,
|
||||
const size_t futureKeySize, UA_ByteString *futureKeys,
|
||||
UA_Duration msKeyLifeTime) {
|
||||
if(!keyStorage)
|
||||
return UA_STATUSCODE_BADINVALIDARGUMENT;
|
||||
|
||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||
UA_PubSubKeyListItem *keyListIterator = NULL;
|
||||
|
||||
if (currentKeyID != 0){
|
||||
/*if currentKeyId is known then update keystorage currentItem*/
|
||||
retval = UA_PubSubKeyStorage_getKeyByKeyID(currentKeyID, keyStorage,
|
||||
&keyListIterator);
|
||||
if (retval == UA_STATUSCODE_GOOD && keyListIterator) {
|
||||
keyStorage->currentItem = keyListIterator;
|
||||
/*Add new keys at the end of KeyList*/
|
||||
retval = UA_PubSubKeyStorage_storeSecurityKeys(
|
||||
server, keyStorage, currentKeyID, NULL,
|
||||
futureKeys, futureKeySize, msKeyLifeTime);
|
||||
if(retval != UA_STATUSCODE_GOOD)
|
||||
return retval;
|
||||
} else if(retval == UA_STATUSCODE_BADNOTFOUND) {
|
||||
/*If the CurrentTokenId is unknown, the existing list shall be discarded and
|
||||
* replaced by the fetched list*/
|
||||
UA_PubSubKeyStorage_clearKeyList(keyStorage);
|
||||
retval = UA_PubSubKeyStorage_storeSecurityKeys(server, keyStorage,
|
||||
currentKeyID, currentKey, futureKeys,
|
||||
futureKeySize, msKeyLifeTime);
|
||||
if (retval != UA_STATUSCODE_GOOD)
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
void
|
||||
UA_PubSubKeyStorage_removeKeyStorage(UA_Server *server, UA_PubSubKeyStorage *keyStorage) {
|
||||
if(!keyStorage) {
|
||||
return;
|
||||
}
|
||||
if(keyStorage->referenceCount > 1) {
|
||||
--keyStorage->referenceCount;
|
||||
return;
|
||||
}
|
||||
if(keyStorage->referenceCount == 1) {
|
||||
LIST_REMOVE(keyStorage, keyStorageList);
|
||||
UA_PubSubKeyStorage_delete(server, keyStorage);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
374
src/pubsub/ua_pubsub_keystorage.h
Normal file
374
src/pubsub/ua_pubsub_keystorage.h
Normal file
@ -0,0 +1,374 @@
|
||||
/* 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) 2019 ifak e.V. Magdeburg (Holger Zipper)
|
||||
* Copyright (c) 2022 Linutronix GmbH (Author: Muddasir Shakil)
|
||||
*/
|
||||
|
||||
#ifndef UA_PUBSUB_KEYSTORAGE
|
||||
#define UA_PUBSUB_KEYSTORAGE
|
||||
|
||||
#include <open62541/plugin/securitypolicy.h>
|
||||
#include <open62541/server.h>
|
||||
|
||||
#include "open62541_queue.h"
|
||||
|
||||
_UA_BEGIN_DECLS
|
||||
|
||||
#ifdef UA_ENABLE_PUBSUB_SKS
|
||||
|
||||
/**
|
||||
* PubSubKeyStorage
|
||||
* ================
|
||||
* A PubSubKeyStorage provides a linked list to store all the keys used to
|
||||
* secure the messages. It keeps the records of old keys (past keys), current
|
||||
* key, new keys (futurekeys), time to move to next key and callback id.
|
||||
*
|
||||
* PubSubKeyListItem is the basic item stored in the KeyList of KeyStorage. It
|
||||
* provides keyId, Key, and pointer to the next key in KeyList. The KeyId is used
|
||||
* to identify and update currentKey in the keystorage. The KeyId is the SecurityTokenId
|
||||
* that appears in the header of messages secured with the CurrentKey.
|
||||
*
|
||||
* Working
|
||||
* =======
|
||||
* +------------------------------+
|
||||
* |AddReaderGroup/AddWriterGroup |
|
||||
* +------------------------------+
|
||||
* |
|
||||
* V
|
||||
* +--------------------+
|
||||
* |CheckSecurityGroupId|
|
||||
* +--------------------+
|
||||
* |Yes
|
||||
* V
|
||||
* +--------------------+
|
||||
* |InitializeKeyStorage|
|
||||
* +--------------------+
|
||||
* |
|
||||
* V
|
||||
* +----------------------------+
|
||||
* |store/updateKeysInKeyStorage|
|
||||
* +----------------------------+
|
||||
* |
|
||||
* V
|
||||
* +------------------------------------------+
|
||||
* |activateKeysToAllPubSubGroupChannelContext|
|
||||
* +------------------------------------------+
|
||||
* | Ʌ
|
||||
* V |
|
||||
* +-----------------------+ |
|
||||
* |addKeyRolloverCallbacks| |
|
||||
* +-----------------------+ |
|
||||
* | |
|
||||
* V |
|
||||
* +-------------------+ |
|
||||
* |keyRolloverCallback| |
|
||||
* +-------------------+ |
|
||||
* |CurrentKey!=LastItem |
|
||||
* -------------------------+
|
||||
*
|
||||
* A KeyStorage is created and initialized when a ReaderGroup or WriterGroup is
|
||||
* created with securityGroupId and SecurityMode SignAndEncrypt. The new
|
||||
* KeyStorage is added to the server KeyStorageList. At this time KeyList is empty.
|
||||
*
|
||||
* UA_PubSubKeyStorage_storeSecurityKeys is used to push the keys into existing
|
||||
* keystorage. In order to update the KeyList of an existing keyStorage,
|
||||
* UA_PubSubKeyStorage_update is called.
|
||||
*
|
||||
* After adding/updating the keys to keystorage, the current key should be
|
||||
* activated to the associated PubSub Group's ChannelContext in the server. The
|
||||
* security Policy associated with PubSub Group will take the keys from
|
||||
* channel context and use them to secure the messages.
|
||||
* The UA_PubSubKeyStorage_storeSecurityKeys and UA_PubSubKeyStorage_update
|
||||
* method will be used by setSecurityKeysAction and getSecurityKeysAction to
|
||||
* retrieve the keys from SKS server and store keys in local storage.
|
||||
*
|
||||
* Each key has a life time, after which the current key is expired and move to
|
||||
* next key in the existing list. For this a callback function is added to the
|
||||
* server. The callback function keyRolloverCallback is added to the server as a
|
||||
* timed callback. The addKeyRolloverCallbacks function calculates the time
|
||||
* stamp to trigger the callback when the current Key expires and roll
|
||||
* over to the next key in existing list.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief This structure holds the information about the keys
|
||||
*/
|
||||
typedef struct UA_PubSubKeyListItem {
|
||||
/* The SecurityTokenId associated with Key*/
|
||||
UA_UInt32 keyID;
|
||||
|
||||
/* This key is not used directly since the protocol associated with the PubSubGroup(s)
|
||||
* specifies an algorithm to generate distinct keys for different types of
|
||||
* cryptography operations*/
|
||||
UA_ByteString key;
|
||||
|
||||
/* Pointers to the key list entries*/
|
||||
TAILQ_ENTRY(UA_PubSubKeyListItem) keyListEntry;
|
||||
} UA_PubSubKeyListItem;
|
||||
|
||||
/* Queue Definition*/
|
||||
typedef TAILQ_HEAD(keyListItems, UA_PubSubKeyListItem) keyListItems;
|
||||
|
||||
/**
|
||||
* @brief This structure holds all info and keys related to one SecurityGroup.
|
||||
* it is used as a list.
|
||||
*/
|
||||
typedef struct UA_PubSubKeyStorage {
|
||||
|
||||
/**
|
||||
* security group id of the security group related to this storage
|
||||
*/
|
||||
UA_String securityGroupID;
|
||||
|
||||
/**
|
||||
* none-owning pointer to the security policy related to this storage
|
||||
*/
|
||||
UA_PubSubSecurityPolicy *policy;
|
||||
|
||||
/**
|
||||
* in case of the SKS server, the key storage structure is deleted when removing the
|
||||
* security group.
|
||||
* in case of publisher / subscriber, one key storage structure is
|
||||
* referenced by multiple reader / writer groups have a reference count to manage free
|
||||
*/
|
||||
UA_UInt32 referenceCount;
|
||||
|
||||
/**
|
||||
* array of keys. the elements inside this array have a next pointer.
|
||||
* keyList can therefore be used as linked list.
|
||||
*/
|
||||
keyListItems keyList;
|
||||
|
||||
/**
|
||||
* size of the keyList
|
||||
*/
|
||||
size_t keyListSize;
|
||||
|
||||
/**
|
||||
* The maximum number of Past keys a keystorage is allowed to store
|
||||
*/
|
||||
UA_UInt32 maxPastKeyCount;
|
||||
|
||||
/**
|
||||
* The maximum number of Future keys a keyStorage is allowed to store
|
||||
*/
|
||||
UA_UInt32 maxFutureKeyCount;
|
||||
|
||||
/*
|
||||
* The maximum keylist size, calculated from maxPastKeyCount and maxFutureKeyCount
|
||||
*/
|
||||
UA_UInt32 maxKeyListSize;
|
||||
|
||||
/**
|
||||
* The SecurityTokenId that appears in the header of messages secured with the
|
||||
* CurrentKey. It starts at 1 and is incremented by 1 each time the KeyLifetime
|
||||
* elapses even if no keys are requested. If the CurrentTokenId increments past the
|
||||
* maximum value of UInt32 it restarts a 1.
|
||||
*/
|
||||
UA_UInt32 currentTokenId;
|
||||
|
||||
/**
|
||||
* the current key used to secure the messages
|
||||
*/
|
||||
UA_PubSubKeyListItem *currentItem;
|
||||
|
||||
/**
|
||||
* keyLifeTime used to update the CurrentKey from the Local KeyStorage
|
||||
*/
|
||||
UA_Duration keyLifeTime;
|
||||
|
||||
/**
|
||||
* id used to register the callback to retrieve the keys related to this security
|
||||
* group
|
||||
*/
|
||||
UA_UInt64 callBackId;
|
||||
|
||||
/**
|
||||
* Pointer to the key storage list
|
||||
*/
|
||||
LIST_ENTRY(UA_PubSubKeyStorage) keyStorageList;
|
||||
|
||||
} UA_PubSubKeyStorage;
|
||||
|
||||
/**
|
||||
* @brief Find the Keystorage from the Server KeyStorageList and returns the pointer to
|
||||
* the keystorage
|
||||
*
|
||||
* @param server holds the keystoragelist
|
||||
* @param securityGroupId of the keystorage to be found
|
||||
* @return Pointer to the keystorage on success, null pointer on failure
|
||||
*/
|
||||
UA_PubSubKeyStorage *
|
||||
UA_Server_findKeyStorage(UA_Server *server, UA_String securityGroupId);
|
||||
|
||||
/**
|
||||
* @brief retreives the security policy pointer from the PubSub configuration by
|
||||
* SecurityPolicyUri
|
||||
*
|
||||
* @param server the server object
|
||||
* @param securityPolicyUri the URI of the security policy
|
||||
* @param policy the pointer to the security policy
|
||||
* @return UA_StatusCode return status code
|
||||
*/
|
||||
UA_StatusCode
|
||||
UA_Server_findPubSubSecurityPolicy(UA_Server *server, const UA_String *securityPolicyUri,
|
||||
UA_PubSubSecurityPolicy **policy);
|
||||
|
||||
/**
|
||||
* @brief Deletes the keystorage from the server and its members
|
||||
*
|
||||
* @param server where the keystorage is created
|
||||
* @param keyStorage pointer to the keystorage
|
||||
*/
|
||||
void
|
||||
UA_PubSubKeyStorage_delete(UA_Server *server, UA_PubSubKeyStorage *keyStorage);
|
||||
|
||||
/**
|
||||
* @brief Initializes an empty Keystorage for the SecurityGroupId and add it to the Server
|
||||
* KeyStorageList
|
||||
*
|
||||
* @param server the server object
|
||||
* @param securityGroupId the identifier of the SecurityGroup
|
||||
* @param securityPolicyUri the security policy assocaited with the security algorithm
|
||||
* @param maxPastKeyCount maximum number of past keys a keystorage is allowed to store
|
||||
* @param maxFutureKeyCount maximum number of future keys a keystorage is allowed to store
|
||||
* @param keyStorage pointer to the keystorage to be initialized
|
||||
* @return UA_StatusCode return status code
|
||||
*/
|
||||
UA_StatusCode
|
||||
UA_PubSubKeyStorage_init(UA_Server *server, const UA_String *securityGroupId,
|
||||
const UA_String *securityPolicyUri,
|
||||
UA_UInt32 maxPastKeyCount, UA_UInt32 maxFutureKeyCount,
|
||||
UA_PubSubKeyStorage *keyStorage);
|
||||
|
||||
/**
|
||||
* @brief After Keystorage is initialized and added to the server, this method is called
|
||||
* to store the current Keys and futurekeys.
|
||||
*
|
||||
* @param server the server object
|
||||
* @param keyStorage pointer to the keyStorage
|
||||
* @param currentTokenId The token Id of the current key it starts with 1 and increaments
|
||||
* each time keylifetime expires
|
||||
* @param currentKey the key used for encrypt the current messages
|
||||
* @param futureKeys pointer to the future keys
|
||||
* @param futureKeyCount the number future keys provided
|
||||
* @param keyLifeTime the time period when the key expires and move to next future key in
|
||||
* milli seconds
|
||||
* @return UA_StatusCode the return status
|
||||
*/
|
||||
UA_StatusCode
|
||||
UA_PubSubKeyStorage_storeSecurityKeys(UA_Server *server, UA_PubSubKeyStorage *keyStorage,
|
||||
UA_UInt32 currentTokenId, const UA_ByteString *currentKey,
|
||||
UA_ByteString *futureKeys, size_t futureKeyCount,
|
||||
UA_Duration msKeyLifeTime);
|
||||
|
||||
/**
|
||||
* @brief Finds the KeyItem from the KeyList by KeyId
|
||||
*
|
||||
* @param keyId the identifier of the Key
|
||||
* @param keyStorage pointer to the keystorage
|
||||
* @param keyItem returned pointer to the keyItem in the KeyList
|
||||
* @return UA_StatusCode return status code
|
||||
*/
|
||||
UA_StatusCode
|
||||
UA_PubSubKeyStorage_getKeyByKeyID(const UA_UInt32 keyId, UA_PubSubKeyStorage *keyStorage,
|
||||
UA_PubSubKeyListItem **keyItem);
|
||||
|
||||
/**
|
||||
* @brief Adds a new KeyItem at the end of the KeyList
|
||||
* to the new KeyListItem.
|
||||
*
|
||||
* @param keyStorage pointer to the keystorage
|
||||
* @param key the key to be added
|
||||
* @param keyID the keyID associated with the key to be added
|
||||
*/
|
||||
UA_PubSubKeyListItem *
|
||||
UA_PubSubKeyStorage_push(UA_PubSubKeyStorage *keyStorage, const UA_ByteString *key,
|
||||
UA_UInt32 keyID);
|
||||
|
||||
/**
|
||||
* @brief It calculates the time to trigger the callback to update current key, adds the
|
||||
* callback to the server and returns the callbackId.
|
||||
*
|
||||
* @param server the server object
|
||||
* @param keyStorage the pointer to the existing keystorage in the server
|
||||
* @param callback the callback function to be added to the server
|
||||
* @param timeToNextMs time in milli seconds to trigger the callback function
|
||||
* @param callbackID the returned callbackId of the added callback function
|
||||
* @return UA_StatusCode the return status
|
||||
*/
|
||||
UA_StatusCode
|
||||
UA_PubSubKeyStorage_addKeyRolloverCallback(UA_Server *server,
|
||||
UA_PubSubKeyStorage *keyStorage,
|
||||
UA_ServerCallback callback,
|
||||
UA_Duration timeToNextMs,
|
||||
UA_UInt64 *callbackID);
|
||||
|
||||
/**
|
||||
* @brief It takes the current Key data, divide it into signing key, encrypting key and
|
||||
* keyNonce according to security policy associated with PubSub Group and set it in
|
||||
* channel context of the assocaited PubSub Group. In case of pubSubGroupId is
|
||||
* UA_NODEID_NULL, all the Reader/WriterGroup's channelcontext are updated with matching
|
||||
* SecurityGroupId.
|
||||
*
|
||||
* @param server The server object
|
||||
* @param pubSubGroupId the nodeId of the Reader/WirterGroup whose channel context to be
|
||||
* updated
|
||||
* @param securityGroupId The identifier for the SecurityGroup
|
||||
* @return UA_StatusCode return status code
|
||||
*/
|
||||
UA_StatusCode
|
||||
UA_PubSubKeyStorage_activateKeyToChannelContext(UA_Server *server, const UA_NodeId pubSubGroupId,
|
||||
const UA_String securityGroupId);
|
||||
|
||||
/**
|
||||
* @brief The callback function to update the current key from keystorage in the server
|
||||
* and activate the current key into channel context of the associated PubSub Group
|
||||
*
|
||||
* @param server the server object
|
||||
* @param keyStorage the pointer to the keystorage
|
||||
*/
|
||||
void
|
||||
UA_PubSubKeyStorage_keyRolloverCallback(UA_Server *server, UA_PubSubKeyStorage *keyStorage);
|
||||
|
||||
/**
|
||||
* @brief It updates/adds the current and future keys into the existing KeyStorage.
|
||||
* If the currentKeyID is known to existing keyStorage, then it is set as the currentKey
|
||||
* and any future keys are appended to the existing list. If the currentKeyId is not know
|
||||
* then, existing keyList is discarded and replaced with the new list.
|
||||
*
|
||||
* @param server the server object
|
||||
* @param keyStorage pointer to the keystorage
|
||||
* @param currentKey the currentKey data
|
||||
* @param currentKeyID the identifier of the current Key
|
||||
* @param futureKeySize the size of the future key list
|
||||
* @param futureKeys the pointer to the future keys list
|
||||
* @param msKeyLifeTime the updated time to move to next key
|
||||
* @return UA_StatusCode the return status
|
||||
*/
|
||||
UA_StatusCode
|
||||
UA_PubSubKeyStorage_update(UA_Server *server, UA_PubSubKeyStorage *keyStorage,
|
||||
const UA_ByteString *currentKey, UA_UInt32 currentKeyID,
|
||||
const size_t futureKeySize, UA_ByteString *futureKeys,
|
||||
UA_Duration msKeyLifeTime);
|
||||
|
||||
/**
|
||||
* @brief KeyStorage must be referenced by atleast one PubSubGroup. This method checks if
|
||||
* KeyStorage is referenced by more then 1 PubSubGroup, then it decreases the
|
||||
* referenceCount by one. If the referenceCount is exactly one then removes the KeyStorage
|
||||
* from the server and PubSubGroup.
|
||||
*
|
||||
* @param server that holds the keystorage and PubSubGroups0
|
||||
* @param keyStorage to be removed from server
|
||||
*/
|
||||
void
|
||||
UA_PubSubKeyStorage_removeKeyStorage(UA_Server *server, UA_PubSubKeyStorage *keyStorage);
|
||||
|
||||
#endif
|
||||
|
||||
_UA_END_DECLS
|
||||
|
||||
#endif /* UA_ENABLE_PUBSUB */
|
@ -7,6 +7,7 @@
|
||||
* Copyright (c) 2021 Fraunhofer IOSB (Author: Jan Hermes)
|
||||
* Copyright (c) 2022 Siemens AG (Author: Thomas Fischer)
|
||||
* Copyright (c) 2022 Fraunhofer IOSB (Author: Noel Graf)
|
||||
* Copyright (c) 2022 Linutronix GmbH (Author: Muddasir Shakil)
|
||||
*/
|
||||
|
||||
#include <open62541/server_pubsub.h>
|
||||
@ -15,6 +16,9 @@
|
||||
|
||||
#include "server/ua_server_internal.h"
|
||||
#include "ua_pubsub_ns0.h"
|
||||
#ifdef UA_ENABLE_PUBSUB_SKS
|
||||
#include "ua_pubsub_keystorage.h"
|
||||
#endif
|
||||
|
||||
#ifdef UA_ENABLE_PUBSUB_MQTT
|
||||
#include "../../plugins/mqtt/ua_mqtt-c_adapter.h"
|
||||
@ -633,7 +637,7 @@ UA_Server_addStandaloneSubscribedDataSet(UA_Server *server, const UA_StandaloneS
|
||||
|
||||
newSubscribedDataSet->config = tmpSubscribedDataSetConfig;
|
||||
newSubscribedDataSet->connectedReader = UA_NODEID_NULL;
|
||||
|
||||
|
||||
if (server->pubSubManager.subscribedDataSetsSize != 0)
|
||||
TAILQ_INSERT_TAIL(&server->pubSubManager.subscribedDataSets, newSubscribedDataSet, listEntry);
|
||||
else {
|
||||
@ -662,7 +666,7 @@ UA_Server_removeStandaloneSubscribedDataSet(UA_Server *server, const UA_NodeId s
|
||||
return UA_STATUSCODE_BADNOTFOUND;
|
||||
}
|
||||
|
||||
//search for referenced readers.
|
||||
//search for referenced readers.
|
||||
UA_PubSubConnection *tmpConnectoin;
|
||||
TAILQ_FOREACH(tmpConnectoin, &server->pubSubManager.connections, listEntry){
|
||||
UA_ReaderGroup *readerGroup;
|
||||
@ -773,6 +777,13 @@ UA_PubSubManager_delete(UA_Server *server, UA_PubSubManager *pubSubManager) {
|
||||
TAILQ_FOREACH_SAFE(tmpSDS1, &server->pubSubManager.subscribedDataSets, listEntry, tmpSDS2){
|
||||
UA_Server_removeStandaloneSubscribedDataSet(server, tmpSDS1->identifier);
|
||||
}
|
||||
|
||||
#ifdef UA_ENABLE_PUBSUB_SKS
|
||||
/* Remove the keyStorages */
|
||||
UA_PubSubKeyStorage *ks, *ksTmp;
|
||||
LIST_FOREACH_SAFE(ks, &server->pubSubManager.pubSubKeyList, keyStorageList, ksTmp)
|
||||
UA_PubSubKeyStorage_delete(server, ks);
|
||||
#endif
|
||||
}
|
||||
|
||||
/***********************************/
|
||||
|
@ -5,6 +5,7 @@
|
||||
* Copyright (c) 2017-2019 Fraunhofer IOSB (Author: Andreas Ebner)
|
||||
* Copyright (c) 2022 Siemens AG (Author: Thomas Fischer)
|
||||
* Copyright (c) 2022 Fraunhofer IOSB (Author: Noel Graf)
|
||||
* Copyright (c) 2022 Linutronix GmbH (Author: Muddasir Shakil)
|
||||
*/
|
||||
|
||||
#ifndef UA_PUBSUB_MANAGER_H_
|
||||
@ -55,6 +56,10 @@ typedef struct UA_PubSubManager {
|
||||
size_t reserveIdsSize;
|
||||
LIST_HEAD(UA_ListOfReserveIds, UA_ReserveId) reserveIds;
|
||||
|
||||
#ifdef UA_ENABLE_PUBSUB_SKS
|
||||
LIST_HEAD(PubSubKeyList, UA_PubSubKeyStorage) pubSubKeyList;
|
||||
#endif
|
||||
|
||||
#ifndef UA_ENABLE_PUBSUB_INFORMATIONMODEL
|
||||
UA_UInt32 uniqueIdCount;
|
||||
#endif
|
||||
|
@ -6,6 +6,7 @@
|
||||
* Copyright (c) 2019-2021 Kalycito Infotech Private Limited
|
||||
* Copyright (c) 2020 Yannick Wallerer, Siemens AG
|
||||
* Copyright (c) 2020-2022 Thomas Fischer, Siemens AG
|
||||
* Copyright (c) 2022 Linutronix GmbH (Author: Muddasir Shakil)
|
||||
*/
|
||||
|
||||
#include <open62541/types.h>
|
||||
@ -1121,7 +1122,7 @@ addStandaloneSubscribedDataSetRepresentation(UA_Server *server, UA_StandaloneSub
|
||||
UA_ObjectAttributes object_attr = UA_ObjectAttributes_default;
|
||||
object_attr.displayName = UA_LOCALIZEDTEXT("", sdsName);
|
||||
UA_Server_addObjectNode(server, UA_NODEID_NUMERIC(1, 0), /* Create a new id */
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_PUBLISHSUBSCRIBE_SUBSCRIBEDDATASETS),
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_PUBLISHSUBSCRIBE_SUBSCRIBEDDATASETS),
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
|
||||
UA_QUALIFIEDNAME(0, sdsName),
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_STANDALONESUBSCRIBEDDATASETTYPE),
|
||||
@ -1152,24 +1153,24 @@ addStandaloneSubscribedDataSetRepresentation(UA_Server *server, UA_StandaloneSub
|
||||
arrayDimensions[0] = (UA_UInt32) subscribedDataSet->config.subscribedDataSet.target.targetVariablesSize;
|
||||
attr.arrayDimensions = arrayDimensions;
|
||||
attr.accessLevel = UA_ACCESSLEVELMASK_READ;
|
||||
UA_Variant_setArray(&attr.value, subscribedDataSet->config.subscribedDataSet.target.targetVariables,
|
||||
subscribedDataSet->config.subscribedDataSet.target.targetVariablesSize,
|
||||
UA_Variant_setArray(&attr.value, subscribedDataSet->config.subscribedDataSet.target.targetVariables,
|
||||
subscribedDataSet->config.subscribedDataSet.target.targetVariablesSize,
|
||||
&UA_TYPES[UA_TYPES_FIELDTARGETDATATYPE]);
|
||||
ret |= UA_Server_addVariableNode(server, UA_NODEID_NULL, sdsObjectNode, UA_NODEID_NUMERIC(0, UA_NS0ID_HASPROPERTY),
|
||||
UA_QUALIFIEDNAME(0, "TargetVariables"), UA_NODEID_NUMERIC(0, UA_NS0ID_PROPERTYTYPE),
|
||||
attr, NULL, &targetVarsId);
|
||||
}
|
||||
|
||||
|
||||
UA_NodePropertyContext *isConnectedNodeContext = (UA_NodePropertyContext *) UA_malloc(sizeof(UA_NodePropertyContext));
|
||||
isConnectedNodeContext->parentNodeId = subscribedDataSet->identifier;
|
||||
isConnectedNodeContext->parentClassifier = UA_NS0ID_STANDALONESUBSCRIBEDDATASETREFDATATYPE;
|
||||
isConnectedNodeContext->elementClassiefier = UA_NS0ID_STANDALONESUBSCRIBEDDATASETTYPE_ISCONNECTED;
|
||||
|
||||
UA_ValueCallback valueCallback;
|
||||
valueCallback.onRead = onRead;
|
||||
valueCallback.onRead = onRead;
|
||||
valueCallback.onWrite = NULL;
|
||||
ret |= addVariableValueSource(server, valueCallback, connectedId, isConnectedNodeContext);
|
||||
|
||||
|
||||
UA_NodePropertyContext *metaDataContext = (UA_NodePropertyContext *)
|
||||
UA_malloc(sizeof(UA_NodePropertyContext));
|
||||
metaDataContext->parentNodeId = subscribedDataSet->identifier;
|
||||
@ -1633,6 +1634,102 @@ removeDataSetWriterAction(UA_Server *server,
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(UA_ENABLE_PUBSUB_SKS) && defined(UA_ENABLE_PUBSUB_INFORMATIONMODEL_METHODS)
|
||||
/**
|
||||
* @note The user credentials and permissions are checked in the AccessControl plugin
|
||||
* before this callback is executed.
|
||||
*/
|
||||
static UA_StatusCode
|
||||
setSecurityKeysAction(UA_Server *server, const UA_NodeId *sessionId, void *sessionHandle,
|
||||
const UA_NodeId *methodId, void *methodContext,
|
||||
const UA_NodeId *objectId, void *objectContext, size_t inputSize,
|
||||
const UA_Variant *input, size_t outputSize, UA_Variant *output) {
|
||||
|
||||
/*Check whether the channel is encrypted according to specification*/
|
||||
session_list_entry *session_entry;
|
||||
LIST_FOREACH(session_entry, &server->sessions, pointers) {
|
||||
if(UA_NodeId_equal(&session_entry->session.sessionId, sessionId)) {
|
||||
if(session_entry->session.header.channel->securityMode !=
|
||||
UA_MESSAGESECURITYMODE_SIGNANDENCRYPT)
|
||||
return UA_STATUSCODE_BADSECURITYMODEINSUFFICIENT;
|
||||
}
|
||||
}
|
||||
|
||||
if(!server || !input)
|
||||
return UA_STATUSCODE_BADINVALIDARGUMENT;
|
||||
|
||||
if(inputSize < 7)
|
||||
return UA_STATUSCODE_BADARGUMENTSMISSING;
|
||||
if(inputSize > 7 || outputSize > 0)
|
||||
return UA_STATUSCODE_BADTOOMANYARGUMENTS;
|
||||
|
||||
/*check for types*/
|
||||
if(!UA_Variant_hasScalarType(&input[0], &UA_TYPES[UA_TYPES_STRING]) || /*SecurityGroupId*/
|
||||
!UA_Variant_hasScalarType(&input[1], &UA_TYPES[UA_TYPES_STRING]) || /*SecurityPolicyUri*/
|
||||
!UA_Variant_hasScalarType(&input[2], &UA_TYPES[UA_TYPES_UINT32]) || /*CurrentTokenId*/
|
||||
!UA_Variant_hasScalarType(&input[3], &UA_TYPES[UA_TYPES_BYTESTRING]) || /*CurrentKey*/
|
||||
!UA_Variant_hasArrayType(&input[4], &UA_TYPES[UA_TYPES_BYTESTRING]) || /*FutureKeys*/
|
||||
(!UA_Variant_hasScalarType(&input[5], &UA_TYPES[UA_TYPES_DURATION]) &&
|
||||
!UA_Variant_hasScalarType(&input[5], &UA_TYPES[UA_TYPES_DOUBLE])) || /*TimeToNextKey*/
|
||||
(!UA_Variant_hasScalarType(&input[6], &UA_TYPES[UA_TYPES_DURATION]) &&
|
||||
!UA_Variant_hasScalarType(&input[6], &UA_TYPES[UA_TYPES_DOUBLE]))) /*TimeToNextKey*/
|
||||
return UA_STATUSCODE_BADTYPEMISMATCH;
|
||||
|
||||
UA_StatusCode retval = UA_STATUSCODE_BAD;
|
||||
UA_Duration callbackTime;
|
||||
UA_String *securityGroupId = (UA_String *)input[0].data;
|
||||
UA_String *securityPolicyUri = (UA_String *)input[1].data;
|
||||
UA_UInt32 currentKeyId = *(UA_UInt32 *)input[2].data;
|
||||
UA_ByteString *currentKey = (UA_ByteString *)input[3].data;
|
||||
UA_ByteString *futureKeys = (UA_ByteString *)input[4].data;
|
||||
size_t futureKeySize = input[4].arrayLength;
|
||||
UA_Duration msTimeToNextKey = *(UA_Duration *)input[5].data;
|
||||
UA_Duration msKeyLifeTime = *(UA_Duration *)input[6].data;
|
||||
|
||||
UA_PubSubKeyStorage *ks =
|
||||
UA_Server_findKeyStorage(server, *securityGroupId);
|
||||
if(!ks)
|
||||
return UA_STATUSCODE_BADNOTFOUND;
|
||||
|
||||
if(!UA_String_equal(securityPolicyUri, &ks->policy->policyUri))
|
||||
return UA_STATUSCODE_BADSECURITYPOLICYREJECTED;
|
||||
|
||||
if(ks->keyListSize == 0) {
|
||||
retval = UA_PubSubKeyStorage_storeSecurityKeys(
|
||||
server, ks, currentKeyId, currentKey, futureKeys, futureKeySize,
|
||||
msKeyLifeTime);
|
||||
if(retval != UA_STATUSCODE_GOOD)
|
||||
return retval;
|
||||
} else {
|
||||
retval = UA_PubSubKeyStorage_update(server, ks, currentKey, currentKeyId,
|
||||
futureKeySize, futureKeys, msKeyLifeTime);
|
||||
if(retval != UA_STATUSCODE_GOOD)
|
||||
return retval;
|
||||
}
|
||||
|
||||
retval = UA_PubSubKeyStorage_activateKeyToChannelContext(server, UA_NODEID_NULL,
|
||||
ks->securityGroupID);
|
||||
if(retval != UA_STATUSCODE_GOOD) {
|
||||
UA_LOG_INFO(
|
||||
&server->config.logger, UA_LOGCATEGORY_SERVER,
|
||||
"Failed to import Symmetric Keys into PubSub Channel Context with %s \n",
|
||||
UA_StatusCode_name(retval));
|
||||
return retval;
|
||||
}
|
||||
|
||||
callbackTime = msKeyLifeTime;
|
||||
if(msTimeToNextKey > 0)
|
||||
callbackTime = msTimeToNextKey;
|
||||
|
||||
/*move to setSecurityKeysAction*/
|
||||
retval = UA_PubSubKeyStorage_addKeyRolloverCallback(
|
||||
server, ks, (UA_ServerCallback)UA_PubSubKeyStorage_keyRolloverCallback, callbackTime,
|
||||
&ks->callBackId);
|
||||
return retval;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/**********************************************/
|
||||
/* Destructors */
|
||||
/**********************************************/
|
||||
@ -1856,7 +1953,7 @@ UA_Server_initPubSubNS0(UA_Server *server) {
|
||||
|
||||
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
|
||||
oAttr.displayName = UA_LOCALIZEDTEXT("", "SubscribedDataSets");
|
||||
UA_Server_addObjectNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_PUBLISHSUBSCRIBE_SUBSCRIBEDDATASETS),
|
||||
UA_Server_addObjectNode(server, UA_NODEID_NUMERIC(0, UA_NS0ID_PUBLISHSUBSCRIBE_SUBSCRIBEDDATASETS),
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_PUBLISHSUBSCRIBE), UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
|
||||
UA_QUALIFIEDNAME(0, "SubscribedDataSets"), UA_NODEID_NUMERIC(0, UA_NS0ID_SUBSCRIBEDDATASETFOLDERTYPE),
|
||||
oAttr, NULL, NULL);
|
||||
@ -1892,6 +1989,9 @@ UA_Server_initPubSubNS0(UA_Server *server) {
|
||||
retVal |= UA_Server_setMethodNodeCallback(server, UA_NODEID_NUMERIC(0, UA_NS0ID_READERGROUPTYPE_ADDDATASETREADER), addDataSetReaderAction);
|
||||
retVal |= UA_Server_setMethodNodeCallback(server, UA_NODEID_NUMERIC(0, UA_NS0ID_READERGROUPTYPE_REMOVEDATASETREADER), removeDataSetReaderAction);
|
||||
retVal |= UA_Server_setMethodNodeCallback(server, UA_NODEID_NUMERIC(0, UA_NS0ID_PUBLISHSUBSCRIBE_PUBSUBCONFIGURATION_RESERVEIDS), addReserveIdsAction);
|
||||
#ifdef UA_ENABLE_PUBSUB_SKS
|
||||
retVal |= UA_Server_setMethodNodeCallback(server, UA_NODEID_NUMERIC(0, UA_NS0ID_PUBLISHSUBSCRIBE_SETSECURITYKEYS), setSecurityKeysAction);
|
||||
#endif
|
||||
|
||||
#ifdef UA_ENABLE_PUBSUB_FILE_CONFIG
|
||||
retVal |= UA_addLoadPubSubConfigMethod(server);
|
||||
|
@ -6,6 +6,8 @@
|
||||
* Copyright (c) 2019 Fraunhofer IOSB (Author: Julius Pfrommer)
|
||||
* Copyright (c) 2019 Kalycito Infotech Private Limited
|
||||
* Copyright (c) 2021 Fraunhofer IOSB (Author: Jan Hermes)
|
||||
* Copyright (c) 2022 Linutronix GmbH (Author: Muddasir Shakil)
|
||||
*
|
||||
*/
|
||||
|
||||
#include <open62541/server_pubsub.h>
|
||||
@ -157,6 +159,33 @@ UA_Server_addReaderGroup(UA_Server *server, UA_NodeId connectionIdentifier,
|
||||
UA_PubSubManager_generateUniqueNodeId(&server->pubSubManager,
|
||||
&newGroup->identifier);
|
||||
#endif
|
||||
|
||||
#ifdef UA_ENABLE_PUBSUB_SKS
|
||||
if(readerGroupConfig->securityMode == UA_MESSAGESECURITYMODE_SIGN ||
|
||||
readerGroupConfig->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) {
|
||||
if(!UA_String_isEmpty(&readerGroupConfig->securityGroupId) &&
|
||||
readerGroupConfig->securityPolicy) {
|
||||
UA_String policyUri = readerGroupConfig->securityPolicy->policyUri;
|
||||
|
||||
newGroup->keyStorage = UA_Server_findKeyStorage(
|
||||
server, readerGroupConfig->securityGroupId);
|
||||
if(!newGroup->keyStorage) {
|
||||
newGroup->keyStorage = (UA_PubSubKeyStorage *)UA_calloc(1, sizeof(UA_PubSubKeyStorage));
|
||||
if(!newGroup->keyStorage)
|
||||
return UA_STATUSCODE_BADOUTOFMEMORY;
|
||||
}
|
||||
|
||||
retval = UA_PubSubKeyStorage_init(server, &readerGroupConfig->securityGroupId,
|
||||
&policyUri, 0, 0, newGroup->keyStorage);
|
||||
if(retval != UA_STATUSCODE_GOOD) {
|
||||
UA_free(newGroup);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if(readerGroupIdentifier)
|
||||
UA_NodeId_copy(&newGroup->identifier, readerGroupIdentifier);
|
||||
|
||||
@ -265,6 +294,15 @@ UA_Server_ReaderGroup_clear(UA_Server* server, UA_ReaderGroup *readerGroup) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef UA_ENABLE_PUBSUB_SKS
|
||||
if(readerGroup->config.securityMode == UA_MESSAGESECURITYMODE_SIGN ||
|
||||
readerGroup->config.securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) {
|
||||
UA_PubSubKeyStorage_removeKeyStorage(server, readerGroup->keyStorage);
|
||||
readerGroup->keyStorage = NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
UA_ReaderGroupConfig_clear(&readerGroup->config);
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
* Copyright (c) 2020 Yannick Wallerer, Siemens AG
|
||||
* Copyright (c) 2020 Thomas Fischer, Siemens AG
|
||||
* Copyright (c) 2021 Fraunhofer IOSB (Author: Jan Hermes)
|
||||
* Copyright (c) 2022 Linutronix GmbH (Author: Muddasir Shakil)
|
||||
*/
|
||||
|
||||
#include <open62541/server_pubsub.h>
|
||||
@ -117,6 +118,34 @@ UA_Server_addWriterGroup(UA_Server *server, const UA_NodeId connection,
|
||||
UA_PubSubManager_generateUniqueNodeId(&server->pubSubManager,
|
||||
&newWriterGroup->identifier);
|
||||
#endif
|
||||
|
||||
#ifdef UA_ENABLE_PUBSUB_SKS
|
||||
if(writerGroupConfig->securityMode == UA_MESSAGESECURITYMODE_SIGN ||
|
||||
writerGroupConfig->securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) {
|
||||
if(!UA_String_isEmpty(&writerGroupConfig->securityGroupId) &&
|
||||
writerGroupConfig->securityPolicy) {
|
||||
UA_String policyUri = writerGroupConfig->securityPolicy->policyUri;
|
||||
|
||||
newWriterGroup->keyStorage =
|
||||
UA_Server_findKeyStorage(
|
||||
server, writerGroupConfig->securityGroupId);
|
||||
if(!newWriterGroup->keyStorage) {
|
||||
newWriterGroup->keyStorage = (UA_PubSubKeyStorage *)UA_calloc(1, sizeof(UA_PubSubKeyStorage));
|
||||
if(!newWriterGroup)
|
||||
return UA_STATUSCODE_BADOUTOFMEMORY;
|
||||
}
|
||||
|
||||
res = UA_PubSubKeyStorage_init(server, &writerGroupConfig->securityGroupId,
|
||||
&policyUri, 0, 0, newWriterGroup->keyStorage);
|
||||
if(res != UA_STATUSCODE_GOOD) {
|
||||
UA_free(newWriterGroup);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if(writerGroupIdentifier)
|
||||
UA_NodeId_copy(&newWriterGroup->identifier, writerGroupIdentifier);
|
||||
return res;
|
||||
@ -647,6 +676,15 @@ UA_WriterGroup_clear(UA_Server *server, UA_WriterGroup *writerGroup) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef UA_ENABLE_PUBSUB_SKS
|
||||
if(writerGroup->config.securityMode == UA_MESSAGESECURITYMODE_SIGN ||
|
||||
writerGroup->config.securityMode == UA_MESSAGESECURITYMODE_SIGNANDENCRYPT) {
|
||||
UA_PubSubKeyStorage_removeKeyStorage(server,
|
||||
writerGroup->keyStorage);
|
||||
writerGroup->keyStorage = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
UA_NetworkMessageOffsetBuffer_clear(&writerGroup->bufferedMessage);
|
||||
}
|
||||
|
||||
|
@ -314,6 +314,10 @@ if(UA_ENABLE_PUBSUB)
|
||||
ua_add_test(pubsub/check_pubsub_decryption.c)
|
||||
ua_add_test(pubsub/check_pubsub_subscribe_encrypted.c)
|
||||
ua_add_test(pubsub/check_pubsub_encrypted_rt_levels.c)
|
||||
if(UA_ENABLE_PUBSUB_SKS)
|
||||
ua_add_test(pubsub/check_pubsub_keystorage.c)
|
||||
ua_add_test(pubsub/check_pubsub_sks_push.c)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (UA_ENABLE_PUBSUB_MONITORING)
|
||||
|
406
tests/pubsub/check_pubsub_keystorage.c
Normal file
406
tests/pubsub/check_pubsub_keystorage.c
Normal file
@ -0,0 +1,406 @@
|
||||
/* 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) 2022 Linutronix GmbH (Author: Muddasir Shakil)
|
||||
*/
|
||||
|
||||
#include <open62541/plugin/pubsub_udp.h>
|
||||
#include <open62541/plugin/securitypolicy_default.h>
|
||||
#include <open62541/server_config_default.h>
|
||||
#include <open62541/server_pubsub.h>
|
||||
|
||||
#include "ua_pubsub.h"
|
||||
#include "ua_pubsub_keystorage.h"
|
||||
#include "ua_server_internal.h"
|
||||
|
||||
#include <check.h>
|
||||
#include "testing_clock.h"
|
||||
|
||||
#define UA_PUBSUB_KEYMATERIAL_NONCELENGTH 32
|
||||
#define UA_AES128CTR_SIGNING_KEY_LENGTH 32
|
||||
#define UA_AES128CTR_KEY_LENGTH 32
|
||||
#define UA_AES128CTR_KEYNONCE_LENGTH 4
|
||||
#define UA_SymmetricKey_Length UA_AES128CTR_SIGNING_KEY_LENGTH + UA_AES128CTR_KEY_LENGTH + UA_AES128CTR_KEYNONCE_LENGTH
|
||||
|
||||
#define MSG_LENGTH_ENCRYPTED 85
|
||||
#define MSG_LENGTH_DECRYPTED 39
|
||||
#define MSG_HEADER "f111ba08016400014df4030100000008b02d012e01000000"
|
||||
#define MSG_HEADER_NO_SEC "f101ba08016400014df4"
|
||||
#define MSG_PAYLOAD_ENC "da434ce02ee19922c6e916c8154123baa25f67288e3378d613f3203909"
|
||||
#define MSG_PAYLOAD_DEC "e1101054c2949f3a" \
|
||||
"d701b4205f69841e" \
|
||||
"5f6901000d7657c2" \
|
||||
"949F3ad701"
|
||||
#define MSG_SIG "6e08a9ff14b83ea2247792eeffc757c85ac99c0ffa79e4fbe5629783dc77b403"
|
||||
#define MSG_SIG_INVALID "5e08a9ff14b83ea2247792eeffc757c85ac99c0ffa79e4fbe5629783dc77b403"
|
||||
|
||||
UA_Byte signingKeyPub[UA_AES128CTR_SIGNING_KEY_LENGTH] = {0};
|
||||
UA_Byte encryptingKeyPub[UA_AES128CTR_KEY_LENGTH] = {0};
|
||||
UA_Byte keyNoncePub[UA_AES128CTR_KEYNONCE_LENGTH] = {0};
|
||||
UA_Server *server = NULL;
|
||||
UA_ByteString currentKey;
|
||||
UA_ByteString *futureKey = NULL;
|
||||
UA_String SecurityGroupId;
|
||||
UA_NodeId connection, writerGroup, readerGroup, publishedDataSet, dataSetWriter;
|
||||
|
||||
|
||||
static UA_StatusCode
|
||||
generateKeyData(const UA_PubSubSecurityPolicy *policy, UA_ByteString *key) {
|
||||
if(!key || !policy)
|
||||
return UA_STATUSCODE_BADINVALIDARGUMENT;
|
||||
|
||||
UA_StatusCode retVal;
|
||||
|
||||
/* Can't not found in specification for pubsub key generation, so use the idea of
|
||||
* securechannel, see specification part6 p51 for more details*/
|
||||
|
||||
UA_Byte secretBytes[UA_PUBSUB_KEYMATERIAL_NONCELENGTH];
|
||||
UA_ByteString secret;
|
||||
secret.length = UA_PUBSUB_KEYMATERIAL_NONCELENGTH;
|
||||
secret.data = secretBytes;
|
||||
|
||||
UA_Byte seedBytes[UA_PUBSUB_KEYMATERIAL_NONCELENGTH];
|
||||
UA_ByteString seed;
|
||||
seed.data = seedBytes;
|
||||
seed.length = UA_PUBSUB_KEYMATERIAL_NONCELENGTH;
|
||||
|
||||
retVal = policy->symmetricModule.generateNonce(policy->policyContext, &secret);
|
||||
retVal |= policy->symmetricModule.generateNonce(policy->policyContext, &seed);
|
||||
if(retVal != UA_STATUSCODE_GOOD)
|
||||
return retVal;
|
||||
|
||||
retVal = policy->symmetricModule.generateKey(policy->policyContext, &secret, &seed, key);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
static void
|
||||
addTestWriterGroup(UA_String securitygroupId){
|
||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||
UA_ServerConfig *config = UA_Server_getConfig(server);
|
||||
UA_WriterGroupConfig writerGroupConfig;
|
||||
memset(&writerGroupConfig, 0, sizeof(writerGroupConfig));
|
||||
writerGroupConfig.name = UA_STRING("WriterGroup");
|
||||
writerGroupConfig.publishingInterval = 100;
|
||||
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
|
||||
|
||||
writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
|
||||
writerGroupConfig.securityGroupId = securitygroupId;
|
||||
writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0];
|
||||
|
||||
retval |= UA_Server_addWriterGroup(server, connection, &writerGroupConfig, &writerGroup);
|
||||
UA_Server_setWriterGroupOperational(server, writerGroup);
|
||||
}
|
||||
|
||||
static void
|
||||
addTestReaderGroup(UA_String securitygroupId){
|
||||
UA_ServerConfig *config = UA_Server_getConfig(server);
|
||||
|
||||
/* To check status after running both publisher and subscriber */
|
||||
UA_StatusCode retVal = UA_STATUSCODE_GOOD;
|
||||
|
||||
/* Reader Group */
|
||||
UA_ReaderGroupConfig readerGroupConfig;
|
||||
memset (&readerGroupConfig, 0, sizeof (UA_ReaderGroupConfig));
|
||||
readerGroupConfig.name = UA_STRING ("ReaderGroup");
|
||||
|
||||
/* Reader Group Encryption settings */
|
||||
readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
|
||||
readerGroupConfig.securityGroupId = securitygroupId;
|
||||
readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0];
|
||||
|
||||
retVal |= UA_Server_addReaderGroup(server, connection, &readerGroupConfig, &readerGroup);
|
||||
UA_Server_setReaderGroupOperational(server, readerGroup);
|
||||
}
|
||||
|
||||
static UA_PubSubKeyStorage*
|
||||
createKeyStoragewithkeys(UA_UInt32 currentTokenId, UA_UInt32 futureKeySize,
|
||||
UA_Duration msKeyLifeTime, UA_Duration msTimeToNextKey, UA_String testSecurityGroupId) {
|
||||
|
||||
UA_StatusCode retval = UA_STATUSCODE_BAD;
|
||||
UA_Duration callbackTime;
|
||||
addTestWriterGroup(SecurityGroupId);
|
||||
addTestReaderGroup(SecurityGroupId);
|
||||
|
||||
UA_PubSubKeyStorage *tKeyStorage =
|
||||
UA_Server_findKeyStorage(server, SecurityGroupId);
|
||||
|
||||
size_t keyLength = server->config.pubSubConfig.securityPolicies->symmetricModule
|
||||
.secureChannelNonceLength;
|
||||
UA_ByteString_allocBuffer(¤tKey, keyLength);
|
||||
generateKeyData(server->config.pubSubConfig.securityPolicies, ¤tKey);
|
||||
|
||||
futureKey = (UA_ByteString *)UA_calloc(futureKeySize, sizeof(UA_ByteString));
|
||||
if(!futureKey)
|
||||
return NULL;
|
||||
|
||||
for(size_t i = 0; i < futureKeySize; i++) {
|
||||
UA_ByteString_allocBuffer(&futureKey[i], keyLength);
|
||||
generateKeyData(server->config.pubSubConfig.securityPolicies, &futureKey[i]);
|
||||
}
|
||||
|
||||
retval = UA_PubSubKeyStorage_storeSecurityKeys(server, tKeyStorage,
|
||||
currentTokenId, ¤tKey, futureKey,
|
||||
futureKeySize, msKeyLifeTime);
|
||||
if(retval != UA_STATUSCODE_GOOD)
|
||||
return NULL;
|
||||
|
||||
retval = UA_PubSubKeyStorage_activateKeyToChannelContext(server, UA_NODEID_NULL,
|
||||
tKeyStorage->securityGroupID);
|
||||
if(retval != UA_STATUSCODE_GOOD)
|
||||
return NULL;
|
||||
|
||||
callbackTime = msKeyLifeTime;
|
||||
if(msTimeToNextKey > 0)
|
||||
callbackTime = msTimeToNextKey;
|
||||
|
||||
/*move to setSecurityKeysAction*/
|
||||
retval = UA_PubSubKeyStorage_addKeyRolloverCallback(
|
||||
server, tKeyStorage, (UA_ServerCallback)UA_PubSubKeyStorage_keyRolloverCallback, callbackTime,
|
||||
&tKeyStorage->callBackId);
|
||||
|
||||
return tKeyStorage;
|
||||
}
|
||||
|
||||
static UA_Byte *
|
||||
hexstr_to_char(const char *hexstr) {
|
||||
size_t len = strlen(hexstr);
|
||||
if(len % 2 != 0)
|
||||
return NULL;
|
||||
size_t final_len = len / 2;
|
||||
UA_Byte *chrs = (UA_Byte *)malloc((final_len + 1) * sizeof(*chrs));
|
||||
for(size_t i = 0, j = 0; j < final_len; i += 2, j++)
|
||||
chrs[j] =
|
||||
(UA_Byte)((hexstr[i] % 32 + 9) % 25 * 16 + (hexstr[i + 1] % 32 + 9) % 25);
|
||||
chrs[final_len] = '\0';
|
||||
return chrs;
|
||||
}
|
||||
|
||||
static void
|
||||
setup(void) {
|
||||
server = UA_Server_new();
|
||||
SecurityGroupId = UA_STRING("TestSecurityGroup");
|
||||
UA_ServerConfig *config = UA_Server_getConfig(server);
|
||||
UA_ServerConfig_setDefault(config);
|
||||
UA_ServerConfig_addPubSubTransportLayer(config, UA_PubSubTransportLayerUDPMP());
|
||||
|
||||
config->pubSubConfig.securityPolicies = (UA_PubSubSecurityPolicy*)
|
||||
UA_malloc(sizeof(UA_PubSubSecurityPolicy));
|
||||
config->pubSubConfig.securityPoliciesSize = 1;
|
||||
UA_PubSubSecurityPolicy_Aes256Ctr(config->pubSubConfig.securityPolicies,
|
||||
&config->logger);
|
||||
|
||||
UA_Server_run_startup(server);
|
||||
//add 2 connections
|
||||
UA_PubSubConnectionConfig connectionConfig;
|
||||
memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig));
|
||||
connectionConfig.name = UA_STRING("UADP Connection");
|
||||
UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL, UA_STRING("opc.udp://224.0.0.22:4840/")};
|
||||
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
|
||||
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
|
||||
connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
|
||||
UA_Server_addPubSubConnection(server, &connectionConfig, &connection);
|
||||
|
||||
}
|
||||
|
||||
static void teardown(void) {
|
||||
UA_Server_run_shutdown(server);
|
||||
UA_Server_delete(server);
|
||||
if(futureKey) {
|
||||
UA_free(futureKey);
|
||||
futureKey = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
START_TEST(TestPubSubKeyStorage_initialize) {
|
||||
|
||||
UA_StatusCode retval = UA_STATUSCODE_BAD;
|
||||
UA_String tSecurityPolicyUri = server->config.pubSubConfig.securityPolicies->policyUri;
|
||||
UA_UInt32 maxPastkeyCount = 0;
|
||||
UA_UInt32 maxFuturekeyCount = 0;
|
||||
UA_PubSubKeyStorage *tKeyStorage = (UA_PubSubKeyStorage *)UA_calloc(1, sizeof(UA_PubSubKeyStorage));
|
||||
ck_assert_ptr_ne(tKeyStorage, NULL);
|
||||
retval =
|
||||
UA_PubSubKeyStorage_init(server, &SecurityGroupId, &tSecurityPolicyUri,
|
||||
maxPastkeyCount, maxFuturekeyCount, tKeyStorage);
|
||||
|
||||
ck_assert_int_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert_ptr_eq(server->config.pubSubConfig.securityPolicies, tKeyStorage->policy);
|
||||
UA_PubSubKeyListItem *item;
|
||||
UA_UInt32 startingKeyId = 1;
|
||||
TAILQ_FOREACH(item, &tKeyStorage->keyList, keyListEntry){
|
||||
ck_assert_msg(item->keyID == startingKeyId, "Expected KeyIDs to be incremented by 1 starting from currentKeyId");
|
||||
startingKeyId++;
|
||||
}
|
||||
ck_assert_msg(UA_String_equal(&tKeyStorage->securityGroupID, &SecurityGroupId), "Expected SecurityGroupId to be equal to keystorage->securityGroupID");
|
||||
/*check if the keystorage is in the Server Keystorage list*/
|
||||
ck_assert_ptr_eq(server->pubSubManager.pubSubKeyList.lh_first, tKeyStorage);
|
||||
} END_TEST
|
||||
|
||||
START_TEST(TestPubSubKeyStorage_InvalidSecurityPolicy) {
|
||||
|
||||
UA_StatusCode retval = UA_STATUSCODE_BAD;
|
||||
UA_String tSecurityPolicyUri = UA_STRING("I am Wrong");
|
||||
UA_UInt32 maxPastkeyCount = 0;
|
||||
UA_UInt32 maxFuturekeyCount = 0;
|
||||
UA_PubSubKeyStorage *tKeyStorage = (UA_PubSubKeyStorage *)UA_calloc(1, sizeof(UA_PubSubKeyStorage));
|
||||
ck_assert_ptr_ne(tKeyStorage, NULL);
|
||||
retval =
|
||||
UA_PubSubKeyStorage_init(server, &SecurityGroupId, &tSecurityPolicyUri,
|
||||
maxPastkeyCount, maxFuturekeyCount, tKeyStorage);
|
||||
|
||||
ck_assert_int_eq(retval, UA_STATUSCODE_BADNOTFOUND);
|
||||
} END_TEST
|
||||
|
||||
START_TEST(TestPubSubKeyStorageSetKeys){
|
||||
UA_UInt32 currentTokenId = 1;
|
||||
UA_UInt32 futureKeySize = 2;
|
||||
UA_Duration msTimeToNextKey = 2000;
|
||||
UA_String testSecurityGroupId = UA_STRING("TestSecurityGroup");
|
||||
UA_PubSubKeyStorage *tKeyStorage = createKeyStoragewithkeys(currentTokenId, futureKeySize, msTimeToNextKey, 0, testSecurityGroupId);
|
||||
UA_PubSubKeyListItem *keyListIterator;
|
||||
ck_assert_ptr_ne(tKeyStorage, NULL);
|
||||
ck_assert_msg(UA_ByteString_equal(¤tKey, &tKeyStorage->keyList.tqh_first->key), "Expected CurrentKey to be equal to the first key in the KeyList");
|
||||
ck_assert_msg(UA_ByteString_equal(¤tKey, &tKeyStorage->currentItem->key), "Expected CurrentItem to be equal to the CurrentKey");
|
||||
keyListIterator = tKeyStorage->keyList.tqh_first->keyListEntry.tqe_next;
|
||||
for (size_t i = 0; i < futureKeySize; i++) {
|
||||
ck_assert_msg(UA_ByteString_equal(&futureKey[i], &keyListIterator->key), "Expected FutureKey to be equal to the second key in the KeyList");
|
||||
keyListIterator = keyListIterator->keyListEntry.tqe_next;
|
||||
}
|
||||
keyListIterator = TAILQ_LAST(&tKeyStorage->keyList, keyListItems);
|
||||
ck_assert_msg(UA_ByteString_equal(&futureKey[futureKeySize - 1], &keyListIterator->key), "Expected lastItem to be equal to the last FutureKey");
|
||||
ck_assert_msg(futureKeySize + 1 == tKeyStorage->keyListSize,"Expected KeyListSize to be equal to FutureKeySize + 1");
|
||||
ck_assert_msg(tKeyStorage->keyLifeTime == msTimeToNextKey, "Expected keyLifetime to be equal to the Keystorage->keyLifeTime");
|
||||
|
||||
} END_TEST
|
||||
|
||||
START_TEST(TestPubSubKeyStorage_MovetoNextKeyCallback){
|
||||
UA_UInt32 currentTokenId = 1;
|
||||
UA_UInt32 futureKeySize = 2;
|
||||
UA_Duration msTimeToNextKey = 2000;
|
||||
UA_String testSecurityGroupId = UA_STRING("TestSecurityGroup");
|
||||
UA_PubSubKeyStorage *tKeyStorage = createKeyStoragewithkeys(currentTokenId, futureKeySize, msTimeToNextKey, 0, testSecurityGroupId);
|
||||
ck_assert_ptr_ne(tKeyStorage, NULL);
|
||||
UA_PubSubKeyListItem *nextCurrentKey = TAILQ_NEXT(tKeyStorage->currentItem, keyListEntry);
|
||||
UA_fakeSleep(2000);
|
||||
UA_Server_run_iterate(server,false);
|
||||
ck_assert_ptr_eq(nextCurrentKey, tKeyStorage->currentItem);
|
||||
ck_assert_msg(UA_ByteString_equal(&nextCurrentKey->key, &tKeyStorage->currentItem->key), "Expected Current key to be the First Future key after first TimeToNextKey expires");
|
||||
/*securityTokenId must be updated after KeyLifeTime elapses*/
|
||||
ck_assert_uint_eq(nextCurrentKey->keyID, tKeyStorage->currentTokenId);
|
||||
UA_WriterGroup *wg = UA_WriterGroup_findWGbyId(server, writerGroup);
|
||||
ck_assert_uint_eq(wg->securityTokenId, nextCurrentKey->keyID);
|
||||
UA_ReaderGroup *rg = UA_ReaderGroup_findRGbyId(server, readerGroup);
|
||||
ck_assert_uint_eq(rg->securityTokenId, nextCurrentKey->keyID);
|
||||
} END_TEST
|
||||
|
||||
START_TEST(TestPubSubKeystorage_ImportedKey){
|
||||
|
||||
UA_StatusCode retval = UA_STATUSCODE_BAD;
|
||||
|
||||
UA_Byte nonceData[8]= {1,2,3,4,5,6,7,8};
|
||||
UA_ByteString testMsgNonce = {8, nonceData};
|
||||
UA_ByteString buffer, expect_buf, signature;
|
||||
const char * msg_unenc = MSG_HEADER MSG_PAYLOAD_DEC;
|
||||
|
||||
UA_UInt32 currentTokenId = 1;
|
||||
UA_UInt32 futureKeySize = 2;
|
||||
UA_Duration msTimeToNextKey = 2000;
|
||||
UA_String testSecurityGroupId = UA_STRING("TestSecurityGroup");
|
||||
|
||||
buffer.length = MSG_LENGTH_DECRYPTED;
|
||||
buffer.data = hexstr_to_char(msg_unenc);
|
||||
UA_ByteString_copy(&buffer, &expect_buf);
|
||||
|
||||
createKeyStoragewithkeys(currentTokenId, futureKeySize, msTimeToNextKey,0, testSecurityGroupId);
|
||||
|
||||
/*encrypt and sign with Writer channelContext*/
|
||||
|
||||
UA_WriterGroup *wg = UA_WriterGroup_findWGbyId(server, writerGroup);
|
||||
retval = wg->config.securityPolicy->setMessageNonce(wg->securityPolicyContext, &testMsgNonce);
|
||||
retval = wg->config.securityPolicy->symmetricModule.cryptoModule.encryptionAlgorithm.encrypt( wg->securityPolicyContext, &buffer);
|
||||
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected retval to be GOOD");
|
||||
size_t sigSize = wg->config.securityPolicy->symmetricModule.cryptoModule.
|
||||
signatureAlgorithm.getLocalSignatureSize(wg->securityPolicyContext);
|
||||
UA_ByteString_allocBuffer(&signature, sigSize);
|
||||
retval = wg->config.securityPolicy->symmetricModule.cryptoModule.signatureAlgorithm.sign(wg->securityPolicyContext,&buffer,&signature);
|
||||
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected retval to be GOOD: Error Code %s", UA_StatusCode_name(retval));
|
||||
|
||||
/*decrypt and verify with the imported key in the ReaderGroup*/
|
||||
UA_ReaderGroup *rg = UA_ReaderGroup_findRGbyId(server, readerGroup);
|
||||
retval = rg->config.securityPolicy->setMessageNonce(rg->securityPolicyContext, &testMsgNonce);
|
||||
retval = rg->config.securityPolicy->symmetricModule.cryptoModule.signatureAlgorithm.verify(rg->securityPolicyContext, &buffer,&signature);
|
||||
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected retval to be GOOD: Error Code %s", UA_StatusCode_name(retval));
|
||||
retval = rg->config.securityPolicy->symmetricModule.cryptoModule.encryptionAlgorithm.decrypt(rg->securityPolicyContext,&buffer);
|
||||
ck_assert(memcmp(buffer.data, expect_buf.data, buffer.length) == 0);
|
||||
} END_TEST
|
||||
|
||||
START_TEST(TestPubSubKeyStorage_InitWithWriterGroup){
|
||||
addTestWriterGroup(SecurityGroupId);
|
||||
UA_WriterGroup *wg = UA_WriterGroup_findWGbyId(server, writerGroup);
|
||||
UA_PubSubKeyStorage *ks = UA_Server_findKeyStorage(server, SecurityGroupId);
|
||||
ck_assert_ptr_ne(wg->keyStorage, NULL);
|
||||
ck_assert_ptr_eq(ks, wg->keyStorage);
|
||||
} END_TEST
|
||||
|
||||
START_TEST(TestPubSubKeyStorage_InitWithReaderGroup){
|
||||
addTestReaderGroup(SecurityGroupId);
|
||||
UA_ReaderGroup *rg = UA_ReaderGroup_findRGbyId(server, readerGroup);
|
||||
UA_PubSubKeyStorage *ks = UA_Server_findKeyStorage(server, SecurityGroupId);
|
||||
ck_assert_ptr_ne(rg->keyStorage, NULL);
|
||||
ck_assert_ptr_eq(ks, rg->keyStorage);
|
||||
} END_TEST
|
||||
|
||||
START_TEST(TestAddingNewGroupToExistingKeyStorage){
|
||||
addTestWriterGroup(SecurityGroupId);
|
||||
UA_PubSubKeyStorage *ks = UA_Server_findKeyStorage(server, SecurityGroupId);
|
||||
ck_assert_msg(ks->referenceCount == 1, "Expected the reference Count to be exactly 1 after adding one Group");
|
||||
addTestReaderGroup(SecurityGroupId);
|
||||
ck_assert_msg(ks->referenceCount == 2, "Expected the reference Count to be exactly 2 after adding second Group same SecurityGroupId");
|
||||
UA_WriterGroup *wg = UA_WriterGroup_findWGbyId(server, writerGroup);
|
||||
UA_ReaderGroup *rg = UA_ReaderGroup_findRGbyId(server, readerGroup);
|
||||
ck_assert_ptr_eq(ks, rg->keyStorage);
|
||||
ck_assert_ptr_eq(ks, wg->keyStorage);
|
||||
ck_assert_ptr_eq(rg->keyStorage, wg->keyStorage);
|
||||
} END_TEST
|
||||
|
||||
START_TEST(TestRemoveAPubSubGroupWithKeyStorage){
|
||||
addTestWriterGroup(SecurityGroupId);
|
||||
addTestReaderGroup(SecurityGroupId);
|
||||
UA_PubSubKeyStorage *ks = UA_Server_findKeyStorage(server, SecurityGroupId);
|
||||
UA_UInt32 refCountBefore = ks->referenceCount;
|
||||
UA_Server_removeWriterGroup(server, writerGroup);
|
||||
--refCountBefore;
|
||||
ck_assert_msg(ks->referenceCount == refCountBefore, "Expected keyStroage referenceCount to be One less then before after removing a Group");
|
||||
UA_Server_removeReaderGroup(server, readerGroup);
|
||||
ks = NULL;
|
||||
ks = UA_Server_findKeyStorage(server, SecurityGroupId);
|
||||
ck_assert_ptr_eq(ks, NULL);
|
||||
} END_TEST
|
||||
|
||||
int
|
||||
main(void) {
|
||||
int number_failed = 0;
|
||||
TCase *tc_pubsub_keystorage = tcase_create("PubSub KeyStorage");
|
||||
tcase_add_checked_fixture(tc_pubsub_keystorage, setup, teardown);
|
||||
tcase_add_test(tc_pubsub_keystorage, TestPubSubKeyStorage_initialize);
|
||||
tcase_add_test(tc_pubsub_keystorage, TestPubSubKeyStorage_InvalidSecurityPolicy);
|
||||
tcase_add_test(tc_pubsub_keystorage, TestPubSubKeyStorageSetKeys);
|
||||
tcase_add_test(tc_pubsub_keystorage, TestPubSubKeyStorage_MovetoNextKeyCallback);
|
||||
tcase_add_test(tc_pubsub_keystorage, TestPubSubKeystorage_ImportedKey);
|
||||
tcase_add_test(tc_pubsub_keystorage, TestPubSubKeyStorage_InitWithWriterGroup);
|
||||
tcase_add_test(tc_pubsub_keystorage, TestPubSubKeyStorage_InitWithReaderGroup);
|
||||
tcase_add_test(tc_pubsub_keystorage, TestAddingNewGroupToExistingKeyStorage);
|
||||
tcase_add_test(tc_pubsub_keystorage, TestRemoveAPubSubGroupWithKeyStorage);
|
||||
|
||||
Suite *s =
|
||||
suite_create("PubSub Keystorage and handling keys for Publisher and Subscriber");
|
||||
suite_add_tcase(s, tc_pubsub_keystorage);
|
||||
|
||||
SRunner *sr = srunner_create(s);
|
||||
srunner_set_fork_status(sr, CK_NOFORK);
|
||||
srunner_run_all(sr, CK_NORMAL);
|
||||
number_failed = srunner_ntests_failed(sr);
|
||||
srunner_free(sr);
|
||||
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
396
tests/pubsub/check_pubsub_sks_push.c
Normal file
396
tests/pubsub/check_pubsub_sks_push.c
Normal file
@ -0,0 +1,396 @@
|
||||
/* 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) 2022 Linutronix GmbH (Author: Muddasir Shakil)
|
||||
*/
|
||||
|
||||
#include <open62541/plugin/pubsub_udp.h>
|
||||
#include <open62541/plugin/securitypolicy_default.h>
|
||||
#include <open62541/server_pubsub.h>
|
||||
#include <open62541/server_config_default.h>
|
||||
|
||||
#include <open62541/client.h>
|
||||
#include <open62541/client_config_default.h>
|
||||
#include <open62541/client_highlevel.h>
|
||||
|
||||
#include "../encryption/certificates.h"
|
||||
#include "ua_pubsub.h"
|
||||
#include "ua_pubsub_keystorage.h"
|
||||
#include "ua_server_internal.h"
|
||||
|
||||
#include <check.h>
|
||||
#include "thread_wrapper.h"
|
||||
|
||||
#define UA_PUBSUB_KEYMATERIAL_NONCELENGTH 32
|
||||
|
||||
UA_Server *server = NULL;
|
||||
UA_ByteString currentKey;
|
||||
UA_ByteString *futureKey = NULL;
|
||||
UA_String securityGroupId;
|
||||
UA_NodeId connection, writerGroup, readerGroup, publishedDataSet, dataSetWriter;
|
||||
UA_Boolean running;
|
||||
THREAD_HANDLE server_thread;
|
||||
|
||||
THREAD_CALLBACK(serverloop) {
|
||||
while(running)
|
||||
UA_Server_run_iterate(server, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
generateKeyData(const UA_PubSubSecurityPolicy *policy, UA_ByteString *key) {
|
||||
if(!key || !policy)
|
||||
return UA_STATUSCODE_BADINVALIDARGUMENT;
|
||||
|
||||
UA_StatusCode retVal;
|
||||
|
||||
UA_Byte secretBytes[UA_PUBSUB_KEYMATERIAL_NONCELENGTH];
|
||||
UA_ByteString secret;
|
||||
secret.length = UA_PUBSUB_KEYMATERIAL_NONCELENGTH;
|
||||
secret.data = secretBytes;
|
||||
|
||||
UA_Byte seedBytes[UA_PUBSUB_KEYMATERIAL_NONCELENGTH];
|
||||
UA_ByteString seed;
|
||||
seed.data = seedBytes;
|
||||
seed.length = UA_PUBSUB_KEYMATERIAL_NONCELENGTH;
|
||||
|
||||
retVal = policy->symmetricModule.generateNonce(policy->policyContext, &secret);
|
||||
retVal |= policy->symmetricModule.generateNonce(policy->policyContext, &seed);
|
||||
if(retVal != UA_STATUSCODE_GOOD)
|
||||
return retVal;
|
||||
|
||||
retVal =
|
||||
policy->symmetricModule.generateKey(policy->policyContext, &secret, &seed, key);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
encyrptedclientconnect(UA_Client *client) {
|
||||
UA_ByteString *trustList = NULL;
|
||||
size_t trustListSize = 0;
|
||||
UA_ByteString *revocationList = NULL;
|
||||
size_t revocationListSize = 0;
|
||||
|
||||
/* Load certificate and private key */
|
||||
UA_ByteString certificate;
|
||||
certificate.length = CERT_DER_LENGTH;
|
||||
certificate.data = CERT_DER_DATA;
|
||||
ck_assert_uint_ne(certificate.length, 0);
|
||||
|
||||
UA_ByteString privateKey;
|
||||
privateKey.length = KEY_DER_LENGTH;
|
||||
privateKey.data = KEY_DER_DATA;
|
||||
ck_assert_uint_ne(privateKey.length, 0);
|
||||
|
||||
/* Secure client initialization */
|
||||
UA_ClientConfig *cc = UA_Client_getConfig(client);
|
||||
cc->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
|
||||
|
||||
UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey, trustList,
|
||||
trustListSize, revocationList,
|
||||
revocationListSize);
|
||||
cc->securityPolicyUri =
|
||||
UA_STRING_ALLOC("http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256");
|
||||
ck_assert(client != NULL);
|
||||
|
||||
/* Secure client connect */
|
||||
return UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
}
|
||||
|
||||
static UA_StatusCode
|
||||
callSetSecurityKey(UA_Client *client, UA_String pSecurityGroupId, UA_UInt32 currentTokenId, UA_UInt32 futureKeySize){
|
||||
UA_NodeId parentId = UA_NODEID_NUMERIC(0, UA_NS0ID_PUBLISHSUBSCRIBE);
|
||||
UA_NodeId methodId = UA_NODEID_NUMERIC(0, UA_NS0ID_PUBLISHSUBSCRIBE_SETSECURITYKEYS);
|
||||
size_t inputSize = 7;
|
||||
UA_Variant inputs[7];
|
||||
size_t outputSize;
|
||||
UA_Variant *output;
|
||||
|
||||
UA_Variant_setScalar(&inputs[0], &pSecurityGroupId, &UA_TYPES[UA_TYPES_STRING]);
|
||||
|
||||
UA_String policUri =
|
||||
UA_STRING("http://opcfoundation.org/UA/SecurityPolicy#PubSub-Aes256-CTR");
|
||||
UA_Variant_setScalar(&inputs[1], &policUri, &UA_TYPES[UA_TYPES_STRING]);
|
||||
|
||||
UA_Variant_setScalar(&inputs[2], ¤tTokenId, &UA_TYPES[UA_TYPES_UINT32]);
|
||||
|
||||
size_t keyLength = server->config.pubSubConfig.securityPolicies->symmetricModule.secureChannelNonceLength;
|
||||
UA_ByteString_allocBuffer(¤tKey, keyLength);
|
||||
generateKeyData(server->config.pubSubConfig.securityPolicies, ¤tKey);
|
||||
UA_Variant_setScalar(&inputs[3], ¤tKey, &UA_TYPES[UA_TYPES_BYTESTRING]);
|
||||
|
||||
futureKey = (UA_ByteString *)UA_calloc(futureKeySize, sizeof(UA_ByteString));
|
||||
|
||||
for (size_t i = 0; i < futureKeySize; i++) {
|
||||
UA_ByteString_allocBuffer(&futureKey[i], keyLength);
|
||||
generateKeyData(server->config.pubSubConfig.securityPolicies, &futureKey[i]);
|
||||
}
|
||||
UA_Variant_setArrayCopy(&inputs[4], futureKey, futureKeySize, &UA_TYPES[UA_TYPES_BYTESTRING]);
|
||||
|
||||
UA_Duration msTimeNextKey = 0;
|
||||
UA_Variant_setScalar(&inputs[5], &msTimeNextKey, &UA_TYPES[UA_TYPES_DURATION]);
|
||||
|
||||
UA_Duration mskeyLifeTime = 2000;
|
||||
UA_Variant_setScalar(&inputs[6], &mskeyLifeTime, &UA_TYPES[UA_TYPES_DURATION]);
|
||||
|
||||
return UA_Client_call(client,parentId, methodId, inputSize, inputs, &outputSize, &output);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
addTestWriterGroup(UA_String securitygroupId) {
|
||||
UA_StatusCode retval = UA_STATUSCODE_GOOD;
|
||||
UA_ServerConfig *config = UA_Server_getConfig(server);
|
||||
UA_WriterGroupConfig writerGroupConfig;
|
||||
memset(&writerGroupConfig, 0, sizeof(writerGroupConfig));
|
||||
writerGroupConfig.name = UA_STRING("WriterGroup");
|
||||
writerGroupConfig.publishingInterval = 100;
|
||||
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
|
||||
|
||||
writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
|
||||
writerGroupConfig.securityGroupId = securitygroupId;
|
||||
writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0];
|
||||
|
||||
retval |=
|
||||
UA_Server_addWriterGroup(server, connection, &writerGroupConfig, &writerGroup);
|
||||
UA_Server_setWriterGroupOperational(server, writerGroup);
|
||||
}
|
||||
|
||||
static void
|
||||
addTestReaderGroup(UA_String securitygroupId) {
|
||||
UA_ServerConfig *config = UA_Server_getConfig(server);
|
||||
|
||||
/* To check status after running both publisher and subscriber */
|
||||
UA_StatusCode retVal = UA_STATUSCODE_GOOD;
|
||||
|
||||
/* Reader Group */
|
||||
UA_ReaderGroupConfig readerGroupConfig;
|
||||
memset(&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig));
|
||||
readerGroupConfig.name = UA_STRING("ReaderGroup");
|
||||
|
||||
/* Reader Group Encryption settings */
|
||||
readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
|
||||
readerGroupConfig.securityGroupId = securitygroupId;
|
||||
readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0];
|
||||
|
||||
retVal |=
|
||||
UA_Server_addReaderGroup(server, connection, &readerGroupConfig, &readerGroup);
|
||||
}
|
||||
|
||||
static void
|
||||
setup(void) {
|
||||
running = true;
|
||||
|
||||
/* Load certificate and private key */
|
||||
UA_ByteString certificate;
|
||||
certificate.length = CERT_DER_LENGTH;
|
||||
certificate.data = CERT_DER_DATA;
|
||||
|
||||
UA_ByteString privateKey;
|
||||
privateKey.length = KEY_DER_LENGTH;
|
||||
privateKey.data = KEY_DER_DATA;
|
||||
|
||||
size_t trustListSize = 0;
|
||||
UA_ByteString *trustList = NULL;
|
||||
size_t issuerListSize = 0;
|
||||
UA_ByteString *issuerList = NULL;
|
||||
UA_ByteString *revocationList = NULL;
|
||||
size_t revocationListSize = 0;
|
||||
|
||||
server = UA_Server_new();
|
||||
UA_ServerConfig *config = UA_Server_getConfig(server);
|
||||
UA_ServerConfig_setDefaultWithSecurityPolicies(config, 4840, &certificate, &privateKey,
|
||||
trustList, trustListSize,
|
||||
issuerList, issuerListSize,
|
||||
revocationList, revocationListSize);
|
||||
|
||||
/* Set the ApplicationUri used in the certificate */
|
||||
UA_String_clear(&config->applicationDescription.applicationUri);
|
||||
config->applicationDescription.applicationUri =
|
||||
UA_STRING_ALLOC("urn:unconfigured:application");
|
||||
|
||||
UA_ServerConfig_addPubSubTransportLayer(config, UA_PubSubTransportLayerUDPMP());
|
||||
|
||||
config->pubSubConfig.securityPolicies =
|
||||
(UA_PubSubSecurityPolicy *)UA_malloc(sizeof(UA_PubSubSecurityPolicy));
|
||||
config->pubSubConfig.securityPoliciesSize = 1;
|
||||
UA_PubSubSecurityPolicy_Aes256Ctr(config->pubSubConfig.securityPolicies,
|
||||
&config->logger);
|
||||
|
||||
UA_Server_run_startup(server);
|
||||
// add 2 connections
|
||||
UA_PubSubConnectionConfig connectionConfig;
|
||||
memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig));
|
||||
connectionConfig.name = UA_STRING("UADP Connection");
|
||||
UA_NetworkAddressUrlDataType networkAddressUrl = {
|
||||
UA_STRING_NULL, UA_STRING("opc.udp://224.0.0.22:4840/")};
|
||||
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
|
||||
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
|
||||
connectionConfig.transportProfileUri =
|
||||
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
|
||||
UA_Server_addPubSubConnection(server, &connectionConfig, &connection);
|
||||
|
||||
securityGroupId = UA_STRING("TestSecurityGroup");
|
||||
|
||||
addTestReaderGroup(securityGroupId);
|
||||
addTestWriterGroup(securityGroupId);
|
||||
|
||||
UA_Server_run_startup(server);
|
||||
THREAD_CREATE(server_thread, serverloop);
|
||||
}
|
||||
|
||||
static void
|
||||
teardown(void) {
|
||||
running = false;
|
||||
THREAD_JOIN(server_thread);
|
||||
UA_Server_run_shutdown(server);
|
||||
UA_Server_delete(server);
|
||||
if(futureKey) {
|
||||
UA_free(futureKey);
|
||||
futureKey = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
START_TEST(TestSetSecurityKeys_InsufficientSecurityMode) {
|
||||
UA_Client *client = UA_Client_new();
|
||||
UA_ClientConfig_setDefault(UA_Client_getConfig(client));
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
if(retval != UA_STATUSCODE_GOOD) {
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
retval = callSetSecurityKey(client, securityGroupId, 1, 2);
|
||||
ck_assert_msg(retval == UA_STATUSCODE_BADSECURITYMODEINSUFFICIENT, "Expected BAD_SECURITYMODEINSUFFICIENT but erorr code : %s \n", UA_StatusCode_name(retval));
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_BADSECURITYMODEINSUFFICIENT);
|
||||
} END_TEST
|
||||
|
||||
START_TEST(TestSetSecurityKeys_MissingSecurityGroup) {
|
||||
UA_Client *client = UA_Client_new();
|
||||
UA_StatusCode retval = encyrptedclientconnect(client);
|
||||
UA_String wrongSecurityGroupId = UA_STRING("WrongSecurityGroupId");
|
||||
retval = callSetSecurityKey(client, wrongSecurityGroupId, 1, 2);
|
||||
ck_assert_msg(retval == UA_STATUSCODE_BADNOTFOUND, "Expected BAD_BADNOTFOUND but erorr code : %s \n", UA_StatusCode_name(retval));
|
||||
} END_TEST
|
||||
|
||||
START_TEST(TestSetSecurityKeys_GOOD) {
|
||||
UA_Client *client = UA_Client_new();
|
||||
UA_UInt32 futureKeySize = 2;
|
||||
UA_UInt32 currentTokenId = 1;
|
||||
UA_UInt32 startingTokenId = 1;
|
||||
|
||||
UA_StatusCode retval = encyrptedclientconnect(client);
|
||||
|
||||
UA_PubSubKeyStorage *ks = UA_Server_findKeyStorage(server, securityGroupId);
|
||||
|
||||
retval = callSetSecurityKey(client, securityGroupId, currentTokenId, futureKeySize);
|
||||
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected StatusCode Good but erorr code : %s \n", UA_StatusCode_name(retval));
|
||||
ck_assert_uint_eq(ks->currentItem->keyID, currentTokenId);
|
||||
|
||||
startingTokenId = currentTokenId;
|
||||
UA_PubSubKeyListItem *iterator = TAILQ_FIRST(&ks->keyList);
|
||||
for (size_t i = 0; i < futureKeySize + 1; i++) {
|
||||
ck_assert_ptr_ne(iterator, NULL);
|
||||
ck_assert_uint_eq(iterator->keyID, startingTokenId);
|
||||
iterator = TAILQ_NEXT(iterator, keyListEntry);
|
||||
startingTokenId++;
|
||||
}
|
||||
ck_assert_uint_eq(ks->keyListSize, futureKeySize+1);
|
||||
} END_TEST
|
||||
|
||||
START_TEST(TestSetSecurityKeys_UpdateCurrentKeyFromExistingList){
|
||||
UA_Client *client = UA_Client_new();
|
||||
UA_UInt32 futureKeySize = 2;
|
||||
UA_UInt32 currentTokenId = 1;
|
||||
|
||||
UA_StatusCode retval = encyrptedclientconnect(client);
|
||||
|
||||
UA_PubSubKeyStorage *ks = UA_Server_findKeyStorage(server, securityGroupId);
|
||||
|
||||
retval = callSetSecurityKey(client, securityGroupId, currentTokenId, futureKeySize);
|
||||
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected StatusCode Good but erorr code : %s \n", UA_StatusCode_name(retval));
|
||||
|
||||
futureKeySize = 0;
|
||||
currentTokenId = 3;
|
||||
retval = callSetSecurityKey(client, securityGroupId, currentTokenId, futureKeySize);
|
||||
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected StatusCode Good but erorr code : %s \n", UA_StatusCode_name(retval));
|
||||
ck_assert_uint_eq(ks->currentItem->keyID, currentTokenId);
|
||||
} END_TEST
|
||||
|
||||
START_TEST(TestSetSecurityKeys_UpdateCurrentKeyFromExistingListAndAddNewFutureKeys){
|
||||
UA_Client *client = UA_Client_new();
|
||||
UA_UInt32 futureKeySize = 2;
|
||||
UA_UInt32 currentTokenId = 1;
|
||||
UA_UInt32 startingTokenId = 1;
|
||||
size_t keyListSize;
|
||||
UA_StatusCode retval = encyrptedclientconnect(client);
|
||||
|
||||
UA_PubSubKeyStorage *ks = UA_Server_findKeyStorage(server, securityGroupId);
|
||||
retval = callSetSecurityKey(client, securityGroupId, currentTokenId, futureKeySize);
|
||||
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected StatusCode Good but erorr code : %s \n", UA_StatusCode_name(retval));
|
||||
|
||||
keyListSize = ks->keyListSize;
|
||||
futureKeySize = 3;
|
||||
currentTokenId = 2;
|
||||
retval = callSetSecurityKey(client, securityGroupId, currentTokenId, futureKeySize);
|
||||
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected StatusCode Good but erorr code : %s \n", UA_StatusCode_name(retval));
|
||||
ck_assert_uint_eq(ks->currentItem->keyID, currentTokenId);
|
||||
/*After updating: KeyListSize = Pervious KeyListSize + FutureKeySize - Duplicated Keys with matching KeyID */
|
||||
ck_assert_uint_eq(keyListSize + futureKeySize - 1, ks->keyListSize);
|
||||
UA_PubSubKeyListItem *iterator = TAILQ_FIRST(&ks->keyList);
|
||||
for (size_t i = 0; i < ks->keyListSize; i++) {
|
||||
ck_assert_ptr_ne(iterator, NULL);
|
||||
ck_assert_uint_eq(iterator->keyID, startingTokenId);
|
||||
iterator = TAILQ_NEXT(iterator, keyListEntry);
|
||||
startingTokenId++;
|
||||
}
|
||||
} END_TEST
|
||||
|
||||
START_TEST(TestSetSecurityKeys_ReplaceExistingKeyListWithFetchedKeyList){
|
||||
UA_Client *client = UA_Client_new();
|
||||
UA_UInt32 futureKeySize = 2;
|
||||
UA_UInt32 currentTokenId = 1;
|
||||
UA_UInt32 startingTokenId = 1;
|
||||
|
||||
UA_StatusCode retval = encyrptedclientconnect(client);
|
||||
|
||||
UA_PubSubKeyStorage *ks = UA_Server_findKeyStorage(server, securityGroupId);
|
||||
|
||||
retval = callSetSecurityKey(client, securityGroupId, currentTokenId, futureKeySize);
|
||||
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected StatusCode Good but erorr code : %s \n", UA_StatusCode_name(retval));
|
||||
|
||||
futureKeySize = 3;
|
||||
currentTokenId = 4;
|
||||
startingTokenId = currentTokenId;
|
||||
retval = callSetSecurityKey(client, securityGroupId, currentTokenId, futureKeySize);
|
||||
ck_assert_msg(retval == UA_STATUSCODE_GOOD, "Expected StatusCode Good but erorr code : %s \n", UA_StatusCode_name(retval));
|
||||
UA_PubSubKeyListItem *iterator = TAILQ_FIRST(&ks->keyList);
|
||||
for (size_t i = 0; i < futureKeySize + 1; i++) {
|
||||
ck_assert_ptr_ne(iterator, NULL);
|
||||
ck_assert_uint_eq(iterator->keyID, startingTokenId);
|
||||
iterator = TAILQ_NEXT(iterator, keyListEntry);
|
||||
startingTokenId++;
|
||||
}
|
||||
ck_assert_uint_eq(ks->keyListSize, futureKeySize+1);
|
||||
} END_TEST
|
||||
|
||||
int
|
||||
main(void) {
|
||||
int number_failed = 0;
|
||||
TCase *tc_pubsub_sks_push = tcase_create("PubSub SKS Push");
|
||||
tcase_add_checked_fixture(tc_pubsub_sks_push, setup, teardown);
|
||||
tcase_add_test(tc_pubsub_sks_push, TestSetSecurityKeys_InsufficientSecurityMode);
|
||||
tcase_add_test(tc_pubsub_sks_push, TestSetSecurityKeys_MissingSecurityGroup);
|
||||
tcase_add_test(tc_pubsub_sks_push, TestSetSecurityKeys_GOOD);
|
||||
tcase_add_test(tc_pubsub_sks_push, TestSetSecurityKeys_UpdateCurrentKeyFromExistingList);
|
||||
tcase_add_test(tc_pubsub_sks_push, TestSetSecurityKeys_UpdateCurrentKeyFromExistingListAndAddNewFutureKeys);
|
||||
tcase_add_test(tc_pubsub_sks_push, TestSetSecurityKeys_ReplaceExistingKeyListWithFetchedKeyList);
|
||||
Suite *s = suite_create("PubSub SKS Push");
|
||||
suite_add_tcase(s, tc_pubsub_sks_push);
|
||||
|
||||
SRunner *sr = srunner_create(s);
|
||||
srunner_set_fork_status(sr, CK_NOFORK);
|
||||
srunner_run_all(sr, CK_NORMAL);
|
||||
number_failed = srunner_ntests_failed(sr);
|
||||
srunner_free(sr);
|
||||
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
25
tools/ci.sh
25
tools/ci.sh
@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Exit immediately if a command exits with a non-zero status
|
||||
set -e
|
||||
set -e
|
||||
|
||||
# Use the error status of the first failure in a pipeline
|
||||
set -o pipefail
|
||||
@ -127,7 +127,7 @@ function set_capabilities {
|
||||
for filename in bin/tests/*; do
|
||||
sudo setcap cap_sys_ptrace,cap_net_raw,cap_net_admin=eip $filename
|
||||
done
|
||||
}
|
||||
}
|
||||
|
||||
function unit_tests {
|
||||
mkdir -p build; cd build; rm -rf *
|
||||
@ -271,6 +271,27 @@ function unit_tests_encryption_mbedtls_pubsub {
|
||||
make test ARGS="-V"
|
||||
}
|
||||
|
||||
function unit_tests_pubsub_sks {
|
||||
mkdir -p build; cd build; rm -rf *
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug \
|
||||
-DUA_NAMESPACE_ZERO=FULL \
|
||||
-DUA_BUILD_EXAMPLES=ON \
|
||||
-DUA_BUILD_UNIT_TESTS=ON \
|
||||
-DUA_ENABLE_DISCOVERY=ON \
|
||||
-DUA_ENABLE_DISCOVERY_MULTICAST=ON \
|
||||
-DUA_ENABLE_ENCRYPTION=MBEDTLS \
|
||||
-DUA_ENABLE_PUBSUB=ON \
|
||||
-DUA_ENABLE_PUBSUB_DELTAFRAMES=ON \
|
||||
-DUA_ENABLE_PUBSUB_INFORMATIONMODEL=ON \
|
||||
-DUA_ENABLE_PUBSUB_INFORMATIONMODEL_METHODS=ON \
|
||||
-DUA_ENABLE_PUBSUB_MONITORING=ON \
|
||||
-DUA_ENABLE_PUBSUB_ENCRYPTION=ON \
|
||||
-DUA_ENABLE_PUBSUB_SKS=ON \
|
||||
..
|
||||
make ${MAKEOPTS}
|
||||
make test ARGS="-V"
|
||||
}
|
||||
|
||||
##########################################
|
||||
# Build and Run Unit Tests with Coverage #
|
||||
##########################################
|
||||
|
Loading…
Reference in New Issue
Block a user