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:
Muddasir shakil 2022-10-17 20:55:11 +02:00 committed by GitHub
parent 737f0562fc
commit b457206d24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1906 additions and 12 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View 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 */

View File

@ -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
}
/***********************************/

View File

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

View File

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

View File

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

View File

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

View File

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

View 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(&currentKey, keyLength);
generateKeyData(server->config.pubSubConfig.securityPolicies, &currentKey);
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, &currentKey, 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(&currentKey, &tKeyStorage->keyList.tqh_first->key), "Expected CurrentKey to be equal to the first key in the KeyList");
ck_assert_msg(UA_ByteString_equal(&currentKey, &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;
}

View 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], &currentTokenId, &UA_TYPES[UA_TYPES_UINT32]);
size_t keyLength = server->config.pubSubConfig.securityPolicies->symmetricModule.secureChannelNonceLength;
UA_ByteString_allocBuffer(&currentKey, keyLength);
generateKeyData(server->config.pubSubConfig.securityPolicies, &currentKey);
UA_Variant_setScalar(&inputs[3], &currentKey, &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;
}

View File

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