opc source code

This commit is contained in:
flower_linux
2026-06-09 17:27:24 +08:00
parent 70f675a48b
commit f0da5cb3c3
2002 changed files with 269812 additions and 7 deletions

View File

@@ -0,0 +1,8 @@
########################################
# PubSub Security Key Service Examples #
########################################
if(UA_NAMESPACE_ZERO STREQUAL "FULL")
add_example(server_pubsub_central_sks server_pubsub_central_sks.c)
endif()
add_example(pubsub_publish_encrypted_sks pubsub_publish_encrypted_sks.c)
add_example(pubsub_subscribe_encrypted_sks pubsub_subscribe_encrypted_sks.c)

View File

@@ -0,0 +1,261 @@
/* 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/client.h>
#include <open62541/client_config_default.h>
#include <open62541/plugin/log_stdout.h>
#include <open62541/plugin/securitypolicy_default.h>
#include <open62541/server.h>
#include <open62541/server_pubsub.h>
#include <open62541/server_config_default.h>
#include <signal.h>
#include "common.h"
#define DEMO_SECURITYGROUPNAME "DemoSecurityGroup"
#define SKS_SERVER_DISCOVERYURL "opc.tcp://localhost:4840"
UA_NodeId connectionIdent, publishedDataSetIdent, writerGroupIdent;
UA_Boolean running = true;
static void
stopHandler(int sign) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
running = false;
}
static void
addPubSubConnection(UA_Server *server, UA_String *transportProfile,
UA_NetworkAddressUrlDataType *networkAddressUrl) {
UA_PubSubConnectionConfig connectionConfig;
memset(&connectionConfig, 0, sizeof(connectionConfig));
connectionConfig.name = UA_STRING("UADP Connection 1");
connectionConfig.transportProfileUri = *transportProfile;
connectionConfig.enabled = UA_TRUE;
UA_Variant_setScalar(&connectionConfig.address, networkAddressUrl,
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT16;
connectionConfig.publisherId.uint16 = 2234;
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent);
}
static void
addPublishedDataSet(UA_Server *server) {
UA_PublishedDataSetConfig publishedDataSetConfig;
memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
publishedDataSetConfig.name = UA_STRING("Demo PDS");
UA_Server_addPublishedDataSet(server, &publishedDataSetConfig,
&publishedDataSetIdent);
}
static void
addDataSetField(UA_Server *server) {
UA_NodeId dataSetFieldIdent;
UA_DataSetFieldConfig dataSetFieldConfig;
memset(&dataSetFieldConfig, 0, sizeof(UA_DataSetFieldConfig));
dataSetFieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
dataSetFieldConfig.field.variable.fieldNameAlias = UA_STRING("Server localtime");
dataSetFieldConfig.field.variable.promotedField = UA_FALSE;
dataSetFieldConfig.field.variable.publishParameters.publishedVariable =
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME);
dataSetFieldConfig.field.variable.publishParameters.attributeId =
UA_ATTRIBUTEID_VALUE;
UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldConfig,
&dataSetFieldIdent);
}
static void
sksPullRequestCallback(UA_Server *server, UA_StatusCode sksPullRequestStatus, void *data) {
UA_PubSubState state = UA_PUBSUBSTATE_OPERATIONAL;
UA_Server_WriterGroup_getState(server, writerGroupIdent, &state);
if(sksPullRequestStatus == UA_STATUSCODE_GOOD && state == UA_PUBSUBSTATE_DISABLED)
UA_Server_setWriterGroupOperational(server, writerGroupIdent);
}
static void
addWriterGroup(UA_Server *server, UA_ClientConfig *sksClientConfig) {
UA_WriterGroupConfig writerGroupConfig;
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
writerGroupConfig.publishingInterval = 100;
writerGroupConfig.enabled = UA_FALSE;
writerGroupConfig.writerGroupId = 100;
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
writerGroupConfig.messageSettings.content.decoded.type =
&UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE];
/* Encryption settings */
writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
/* we need to set the SecurityGroup for this reader group.
* It is used to provide access to the security keys on SKS*/
writerGroupConfig.securityGroupId = UA_STRING(DEMO_SECURITYGROUPNAME);
UA_ServerConfig *config = UA_Server_getConfig(server);
writerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0];
UA_UadpWriterGroupMessageDataType *writerGroupMessage =
UA_UadpWriterGroupMessageDataType_new();
writerGroupMessage->networkMessageContentMask =
(UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID |
(UA_UadpNetworkMessageContentMask)
UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
(UA_UadpNetworkMessageContentMask)
UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
(UA_UadpNetworkMessageContentMask)
UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
writerGroupConfig.messageSettings.content.decoded.data = writerGroupMessage;
UA_Server_addWriterGroup(server, connectionIdent, &writerGroupConfig,
&writerGroupIdent);
UA_UadpWriterGroupMessageDataType_delete(writerGroupMessage);
/* We need to set the sks client before setting Reader/Writer Group into operational
* because it will fetch the initial set of keys. The sks client is required to set
* once per security group on publisher/subscriber application.*/
UA_Server_setSksClient(server, writerGroupConfig.securityGroupId, sksClientConfig,
SKS_SERVER_DISCOVERYURL, sksPullRequestCallback, NULL);
}
static void
addDataSetWriter(UA_Server *server) {
/* We need now a DataSetWriter within the WriterGroup. This means we must
* create a new DataSetWriterConfig and add call the addWriterGroup function. */
UA_NodeId dataSetWriterIdent;
UA_DataSetWriterConfig dataSetWriterConfig;
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
dataSetWriterConfig.dataSetWriterId = 62541;
dataSetWriterConfig.keyFrameCount = 10;
UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
&dataSetWriterConfig, &dataSetWriterIdent);
}
static int
run(UA_UInt16 port, UA_String *transportProfile,
UA_NetworkAddressUrlDataType *networkAddressUrl, UA_ClientConfig *sksClientConfig) {
signal(SIGINT, stopHandler);
signal(SIGTERM, stopHandler);
UA_Server *server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_ServerConfig_setMinimal(config, port, NULL);
/* Instantiate the PubSub SecurityPolicy */
config->pubSubConfig.securityPolicies =
(UA_PubSubSecurityPolicy *)UA_malloc(sizeof(UA_PubSubSecurityPolicy));
config->pubSubConfig.securityPoliciesSize = 1;
UA_PubSubSecurityPolicy_Aes256Ctr(config->pubSubConfig.securityPolicies,
config->logging);
addPubSubConnection(server, transportProfile, networkAddressUrl);
addPublishedDataSet(server);
addDataSetField(server);
addWriterGroup(server, sksClientConfig);
addDataSetWriter(server);
UA_StatusCode retval = UA_Server_run(server, &running);
UA_ClientConfig_delete(sksClientConfig);
UA_Server_delete(server);
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}
static UA_ClientConfig *
encyrptedClient(const char *username, const char *password,
UA_ByteString certificate, UA_ByteString privateKey) {
UA_ClientConfig *cc = (UA_ClientConfig *)UA_calloc(1, sizeof(UA_ClientConfig));
cc->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey, 0, 0, NULL, 0);
cc->securityPolicyUri =
UA_STRING_ALLOC("http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256");
UA_UserNameIdentityToken* identityToken = UA_UserNameIdentityToken_new();
identityToken->userName = UA_STRING_ALLOC(username);
identityToken->password = UA_STRING_ALLOC(password);
UA_ExtensionObject_clear(&cc->userIdentityToken);
cc->userIdentityToken.encoding = UA_EXTENSIONOBJECT_DECODED;
cc->userIdentityToken.content.decoded.type = &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN];
cc->userIdentityToken.content.decoded.data = identityToken;
return cc;
}
static void
usage(char *progname) {
printf("usage: %s server_ctt <server-certificate.der> <private-key.der>\n"
"<uri> [device]\n",
progname);
}
int
main(int argc, char **argv) {
UA_String transportProfile =
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
UA_NetworkAddressUrlDataType networkAddressUrl = {
UA_STRING_NULL, UA_STRING("opc.udp://224.0.0.22:4840/")};
UA_UInt16 port = 4841;
/* Load certificate */
size_t pos = 1;
UA_ByteString certificate = UA_BYTESTRING_NULL;
if((size_t)argc >= pos + 1) {
certificate = loadFile(argv[1]);
if(certificate.length == 0) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Unable to load file %s.", argv[pos]);
return EXIT_FAILURE;
}
pos++;
}
/* Load the private key */
UA_ByteString privateKey = UA_BYTESTRING_NULL;
if((size_t)argc >= pos + 1) {
privateKey = loadFile(argv[2]);
if(privateKey.length == 0) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Unable to load file %s.", argv[pos]);
return EXIT_FAILURE;
}
pos++;
}
if(argc > 3) {
if(strcmp(argv[1], "-h") == 0) {
usage(argv[0]);
return EXIT_SUCCESS;
} else if(strncmp(argv[pos], "opc.udp://", 10) == 0) {
networkAddressUrl.url = UA_STRING(argv[pos]);
} else if(strncmp(argv[pos], "opc.eth://", 10) == 0) {
transportProfile = UA_STRING(
"http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp");
if(argc < 4) {
printf("Error: UADP/ETH needs an interface name\n");
return EXIT_FAILURE;
}
networkAddressUrl.networkInterface = UA_STRING(argv[pos + 1]);
networkAddressUrl.url = UA_STRING(argv[pos]);
} else {
printf("Error: unknown URI\n");
return EXIT_FAILURE;
}
}
/* we need to setup a client with encrypted connection and user credentials
* that can be trusted by the SKS Server instance.
* This Client will be used by Publisher/Subscriber application to regularly
* fetch the security keys from SKS.*/
UA_ClientConfig *sksClientConfig =
encyrptedClient("user1", "password", certificate, privateKey);
return run(port, &transportProfile, &networkAddressUrl, sksClientConfig);
}

View File

@@ -0,0 +1,421 @@
/* 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/client_config_default.h>
#include <open62541/plugin/log_stdout.h>
#include <open62541/plugin/securitypolicy_default.h>
#include <open62541/server_config_default.h>
#include <open62541/server.h>
#include <open62541/server_pubsub.h>
#include "common.h"
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#define DEMO_SECURITYGROUPNAME "DemoSecurityGroup"
#define SKS_SERVER_DISCOVERYURL "opc.tcp://localhost:4840"
UA_NodeId connectionIdentifier;
UA_NodeId readerGroupIdentifier;
UA_NodeId readerIdentifier;
UA_DataSetReaderConfig readerConfig;
static void
fillTestDataSetMetaData(UA_DataSetMetaDataType *pMetaData);
/**
* Followed by the main server code, making use of the above definitions */
UA_Boolean running = true;
static void
stopHandler(int sign) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
running = false;
}
/* Add new connection to the server */
static UA_StatusCode
addPubSubConnection(UA_Server *server, UA_String *transportProfile,
UA_NetworkAddressUrlDataType *networkAddressUrl) {
if((server == NULL) || (transportProfile == NULL) || (networkAddressUrl == NULL)) {
return UA_STATUSCODE_BADINTERNALERROR;
}
UA_StatusCode retval = UA_STATUSCODE_GOOD;
/* Configuration creation for the connection */
UA_PubSubConnectionConfig connectionConfig;
memset(&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig));
connectionConfig.name = UA_STRING("UDPMC Connection 1");
connectionConfig.transportProfileUri = *transportProfile;
connectionConfig.enabled = UA_TRUE;
UA_Variant_setScalar(&connectionConfig.address, networkAddressUrl,
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT32;
connectionConfig.publisherId.uint32 = UA_UInt32_random();
retval |=
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier);
if(retval != UA_STATUSCODE_GOOD) {
return retval;
}
return retval;
}
static void
sksPullRequestCallback(UA_Server *server, UA_StatusCode sksPullRequestStatus, void *data) {
if(sksPullRequestStatus == UA_STATUSCODE_GOOD)
UA_Server_setReaderGroupOperational(server, readerGroupIdentifier);
if(sksPullRequestStatus != UA_STATUSCODE_GOOD)
running = false;
}
/**
* **ReaderGroup**
*
* ReaderGroup is used to group a list of DataSetReaders. All ReaderGroups are
* created within a PubSubConnection and automatically deleted if the connection
* is removed. All network message related filters are only available in the
* DataSetReader. */
/* Add ReaderGroup to the created connection */
static UA_StatusCode
addReaderGroup(UA_Server *server, UA_ClientConfig *sksClientConfig) {
if(server == NULL) {
return UA_STATUSCODE_BADINTERNALERROR;
}
UA_StatusCode retval = UA_STATUSCODE_GOOD;
UA_ReaderGroupConfig readerGroupConfig;
memset(&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig));
readerGroupConfig.name = UA_STRING("ReaderGroup1");
/* Encryption settings */
UA_ServerConfig *config = UA_Server_getConfig(server);
readerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
/* we need to set the SecurityGroup for this reader group.
* It is used to provide access to the security keys on SKS*/
readerGroupConfig.securityGroupId = UA_STRING(DEMO_SECURITYGROUPNAME);
readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0];
retval |= UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig,
&readerGroupIdentifier);
/* We need to set the sks client before setting Reader/Writer Group into operational
* because it will fetch the initial set of keys. The sks client is required to set
* once per security group on publisher/subscriber application.*/
UA_Server_setSksClient(server, readerGroupConfig.securityGroupId, (UA_ClientConfig *)sksClientConfig,
SKS_SERVER_DISCOVERYURL, sksPullRequestCallback, NULL);
return retval;
}
/**
* **DataSetReader**
*
* DataSetReader can receive NetworkMessages with the DataSetMessage
* of interest sent by the Publisher. DataSetReader provides
* the configuration necessary to receive and process DataSetMessages
* on the Subscriber side. DataSetReader must be linked with a
* SubscribedDataSet and be contained within a ReaderGroup. */
/* Add DataSetReader to the ReaderGroup */
static UA_StatusCode
addDataSetReader(UA_Server *server) {
if(server == NULL) {
return UA_STATUSCODE_BADINTERNALERROR;
}
UA_StatusCode retval = UA_STATUSCODE_GOOD;
memset(&readerConfig, 0, sizeof(UA_DataSetReaderConfig));
readerConfig.name = UA_STRING("DataSet Reader 1");
/* Parameters to filter which DataSetMessage has to be processed
* by the DataSetReader */
/* The following parameters are used to show that the data published by
* tutorial_pubsub_publish.c is being subscribed and is being updated in
* the information model */
UA_UInt16 publisherIdentifier = 2234;
readerConfig.publisherId.type = &UA_TYPES[UA_TYPES_UINT16];
readerConfig.publisherId.data = &publisherIdentifier;
readerConfig.writerGroupId = 100;
readerConfig.dataSetWriterId = 62541;
/* Setting up Meta data configuration in DataSetReader */
fillTestDataSetMetaData(&readerConfig.dataSetMetaData);
retval |= UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig,
&readerIdentifier);
return retval;
}
/**
* **SubscribedDataSet**
*
* Set SubscribedDataSet type to TargetVariables data type.
* Add subscribedvariables to the DataSetReader */
static UA_StatusCode
addSubscribedVariables(UA_Server *server, UA_NodeId dataSetReaderId) {
if(server == NULL)
return UA_STATUSCODE_BADINTERNALERROR;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
UA_NodeId folderId;
UA_String folderName = readerConfig.dataSetMetaData.name;
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
UA_QualifiedName folderBrowseName;
if(folderName.length > 0) {
oAttr.displayName.locale = UA_STRING("en-US");
oAttr.displayName.text = folderName;
folderBrowseName.namespaceIndex = 1;
folderBrowseName.name = folderName;
} else {
oAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Subscribed Variables");
folderBrowseName = UA_QUALIFIEDNAME(1, "Subscribed Variables");
}
UA_Server_addObjectNode(
server, UA_NODEID_NULL, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), folderBrowseName,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId);
/**
* **TargetVariables**
*
* The SubscribedDataSet option TargetVariables defines a list of Variable mappings
* between received DataSet fields and target Variables in the Subscriber
* AddressSpace. The values subscribed from the Publisher are updated in the value
* field of these variables */
/* Create the TargetVariables with respect to DataSetMetaData fields */
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable *)UA_calloc(
readerConfig.dataSetMetaData.fieldsSize, sizeof(UA_FieldTargetVariable));
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
/* Variable to subscribe data */
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
UA_LocalizedText_copy(&readerConfig.dataSetMetaData.fields[i].description,
&vAttr.description);
vAttr.displayName.locale = UA_STRING("en-US");
vAttr.displayName.text = readerConfig.dataSetMetaData.fields[i].name;
vAttr.dataType = readerConfig.dataSetMetaData.fields[i].dataType;
UA_NodeId newNode;
retval |= UA_Server_addVariableNode(
server, UA_NODEID_NUMERIC(1, (UA_UInt32)i + 50000), folderId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, (char *)readerConfig.dataSetMetaData.fields[i].name.data),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vAttr, NULL, &newNode);
/* For creating Targetvariables */
UA_FieldTargetDataType_init(&targetVars[i].targetVariable);
targetVars[i].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
targetVars[i].targetVariable.targetNodeId = newNode;
}
retval = UA_Server_DataSetReader_createTargetVariables(
server, dataSetReaderId, readerConfig.dataSetMetaData.fieldsSize, targetVars);
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++)
UA_FieldTargetDataType_clear(&targetVars[i].targetVariable);
UA_free(targetVars);
UA_free(readerConfig.dataSetMetaData.fields);
return retval;
}
/**
* **DataSetMetaData**
*
* The DataSetMetaData describes the content of a DataSet. It provides the information
* necessary to decode DataSetMessages on the Subscriber side. DataSetMessages received
* from the Publisher are decoded into DataSet and each field is updated in the Subscriber
* based on datatype match of TargetVariable fields of Subscriber and
* PublishedDataSetFields of Publisher */
/* Define MetaData for TargetVariables */
static void
fillTestDataSetMetaData(UA_DataSetMetaDataType *pMetaData) {
if(pMetaData == NULL) {
return;
}
UA_DataSetMetaDataType_init(pMetaData);
pMetaData->name = UA_STRING("DataSet 1");
/* Static definition of number of fields size to 4 to create four different
* targetVariables of distinct datatype
* Currently the publisher sends only DateTime data type */
pMetaData->fieldsSize = 4;
pMetaData->fields = (UA_FieldMetaData *)UA_Array_new(
pMetaData->fieldsSize, &UA_TYPES[UA_TYPES_FIELDMETADATA]);
/* DateTime DataType */
UA_FieldMetaData_init(&pMetaData->fields[0]);
UA_NodeId_copy(&UA_TYPES[UA_TYPES_DATETIME].typeId, &pMetaData->fields[0].dataType);
pMetaData->fields[0].builtInType = UA_NS0ID_DATETIME;
pMetaData->fields[0].name = UA_STRING("DateTime");
pMetaData->fields[0].valueRank = -1; /* scalar */
/* Int32 DataType */
UA_FieldMetaData_init(&pMetaData->fields[1]);
UA_NodeId_copy(&UA_TYPES[UA_TYPES_INT32].typeId, &pMetaData->fields[1].dataType);
pMetaData->fields[1].builtInType = UA_NS0ID_INT32;
pMetaData->fields[1].name = UA_STRING("Int32");
pMetaData->fields[1].valueRank = -1; /* scalar */
/* Int64 DataType */
UA_FieldMetaData_init(&pMetaData->fields[2]);
UA_NodeId_copy(&UA_TYPES[UA_TYPES_INT64].typeId, &pMetaData->fields[2].dataType);
pMetaData->fields[2].builtInType = UA_NS0ID_INT64;
pMetaData->fields[2].name = UA_STRING("Int64");
pMetaData->fields[2].valueRank = -1; /* scalar */
/* Boolean DataType */
UA_FieldMetaData_init(&pMetaData->fields[3]);
UA_NodeId_copy(&UA_TYPES[UA_TYPES_BOOLEAN].typeId, &pMetaData->fields[3].dataType);
pMetaData->fields[3].builtInType = UA_NS0ID_BOOLEAN;
pMetaData->fields[3].name = UA_STRING("BoolToggle");
pMetaData->fields[3].valueRank = -1; /* scalar */
}
/*
* TODO: add something similary ass addDataSetMetadata for security configuration
*/
static int
run(UA_UInt16 port, UA_String *transportProfile,
UA_NetworkAddressUrlDataType *networkAddressUrl, UA_ClientConfig *sksClientConfig) {
signal(SIGINT, stopHandler);
signal(SIGTERM, stopHandler);
/* Return value initialized to Status Good */
UA_StatusCode retval = UA_STATUSCODE_GOOD;
UA_Server *server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_ServerConfig_setMinimal(config, port, NULL);
/* Instantiate the PubSub SecurityPolicy */
config->pubSubConfig.securityPolicies =
(UA_PubSubSecurityPolicy *)UA_malloc(sizeof(UA_PubSubSecurityPolicy));
config->pubSubConfig.securityPoliciesSize = 1;
UA_PubSubSecurityPolicy_Aes256Ctr(config->pubSubConfig.securityPolicies,
config->logging);
/* API calls */
/* Add PubSubConnection */
retval |= addPubSubConnection(server, transportProfile, networkAddressUrl);
if(retval != UA_STATUSCODE_GOOD)
return EXIT_FAILURE;
/* Add ReaderGroup to the created PubSubConnection */
retval |= addReaderGroup(server, sksClientConfig);
if(retval != UA_STATUSCODE_GOOD)
return EXIT_FAILURE;
/* Add DataSetReader to the created ReaderGroup */
retval |= addDataSetReader(server);
if(retval != UA_STATUSCODE_GOOD)
return EXIT_FAILURE;
/* Add SubscribedVariables to the created DataSetReader */
retval |= addSubscribedVariables(server, readerIdentifier);
if(retval != UA_STATUSCODE_GOOD)
return EXIT_FAILURE;
retval = UA_Server_run(server, &running);
UA_Server_delete(server);
UA_ClientConfig_delete(sksClientConfig);
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}
static UA_ClientConfig *
encyrptedClient(const char *username, const char *password,
UA_ByteString certificate, UA_ByteString privateKey) {
UA_ClientConfig *cc = (UA_ClientConfig *)UA_calloc(1, sizeof(UA_ClientConfig));
cc->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey, 0, 0, NULL, 0);
cc->securityPolicyUri =
UA_STRING_ALLOC("http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256");
UA_UserNameIdentityToken* identityToken = UA_UserNameIdentityToken_new();
identityToken->userName = UA_STRING_ALLOC(username);
identityToken->password = UA_STRING_ALLOC(password);
UA_ExtensionObject_clear(&cc->userIdentityToken);
cc->userIdentityToken.encoding = UA_EXTENSIONOBJECT_DECODED;
cc->userIdentityToken.content.decoded.type = &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN];
cc->userIdentityToken.content.decoded.data = identityToken;
return cc;
}
static void
usage(char *progname) {
printf("usage: %s server_ctt <server-certificate.der> <private-key.der>\n"
"<uri> [device]\n",
progname);
}
int
main(int argc, char **argv) {
UA_String transportProfile =
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
UA_NetworkAddressUrlDataType networkAddressUrl = {
UA_STRING_NULL, UA_STRING("opc.udp://224.0.0.22:4840/")};
UA_UInt16 port = 4801;
/* Load certificate */
size_t pos = 1;
UA_ByteString certificate = UA_BYTESTRING_NULL;
if((size_t)argc >= pos + 1) {
certificate = loadFile(argv[1]);
if(certificate.length == 0) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Unable to load file %s.", argv[pos]);
return EXIT_FAILURE;
}
pos++;
}
/* Load the private key */
UA_ByteString privateKey = UA_BYTESTRING_NULL;
if((size_t)argc >= pos + 1) {
privateKey = loadFile(argv[2]);
if(privateKey.length == 0) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Unable to load file %s.", argv[pos]);
return EXIT_FAILURE;
}
pos++;
}
if(argc > 3) {
if(strcmp(argv[1], "-h") == 0) {
usage(argv[0]);
return EXIT_SUCCESS;
} else if(strncmp(argv[pos], "opc.udp://", 10) == 0) {
networkAddressUrl.url = UA_STRING(argv[pos]);
} else if(strncmp(argv[pos], "opc.eth://", 10) == 0) {
transportProfile = UA_STRING(
"http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp");
if(argc < 4) {
printf("Error: UADP/ETH needs an interface name\n");
return EXIT_FAILURE;
}
networkAddressUrl.networkInterface = UA_STRING(argv[pos + 1]);
networkAddressUrl.url = UA_STRING(argv[pos]);
} else {
printf("Error: unknown URI\n");
return EXIT_FAILURE;
}
}
/* we need to setup a client with encrypted connection and user credentials
* that can be trusted by the SKS Server instance.
* This Client will be used by Publisher/Subscriber application to regularly
* fetch the security keys from SKS.*/
UA_ClientConfig *sksClientConfig =
encyrptedClient("user1", "password", certificate, privateKey);
return run(port, &transportProfile, &networkAddressUrl, sksClientConfig);
}

View File

@@ -0,0 +1,534 @@
/* 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/log_stdout.h>
#include <open62541/plugin/pki_default.h>
#include <open62541/plugin/securitypolicy_default.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <signal.h>
#include <stdlib.h>
#include "common.h"
#define MAX_OPERATION_LIMIT 10000
#define MINUTE_SECONDS 60
#define MILLI_SECONDS 1000
#define MAX_OPERATION_LIMIT 10000
#define policUri "http://opcfoundation.org/UA/SecurityPolicy#PubSub-Aes256-CTR"
#define DEMO_KEYLIFETIME_MINUTES 1
#define DEMO_MAXFUTUREKEYCOUNT 1
#define DEMO_MAXPASTKEYCOUNT 1
#define DEMO_SECURITYGROUPNAME "DemoSecurityGroup"
static void
disableAnonymous(UA_ServerConfig *config) {
for(size_t i = 0; i < config->endpointsSize; i++) {
UA_EndpointDescription *ep = &config->endpoints[i];
for(size_t j = 0; j < ep->userIdentityTokensSize; j++) {
UA_UserTokenPolicy *utp = &ep->userIdentityTokens[j];
if(utp->tokenType != UA_USERTOKENTYPE_ANONYMOUS)
continue;
UA_UserTokenPolicy_clear(utp);
/* Move the last to this position */
if(j + 1 < ep->userIdentityTokensSize) {
ep->userIdentityTokens[j] =
ep->userIdentityTokens[ep->userIdentityTokensSize - 1];
j--;
}
ep->userIdentityTokensSize--;
}
/* Delete the entire array if the last UserTokenPolicy was removed */
if(ep->userIdentityTokensSize == 0) {
UA_free(ep->userIdentityTokens);
ep->userIdentityTokens = NULL;
}
}
}
#ifdef UA_ENABLE_ENCRYPTION
static void
disableUnencrypted(UA_ServerConfig *config) {
for(size_t i = 0; i < config->endpointsSize; i++) {
UA_EndpointDescription *ep = &config->endpoints[i];
if(ep->securityMode != UA_MESSAGESECURITYMODE_NONE)
continue;
UA_EndpointDescription_clear(ep);
/* Move the last to this position */
if(i + 1 < config->endpointsSize) {
config->endpoints[i] = config->endpoints[config->endpointsSize - 1];
i--;
}
config->endpointsSize--;
}
/* Delete the entire array if the last Endpoint was removed */
if(config->endpointsSize == 0) {
UA_free(config->endpoints);
config->endpoints = NULL;
}
}
static void
disableOutdatedSecurityPolicy(UA_ServerConfig *config) {
for(size_t i = 0; i < config->endpointsSize; i++) {
UA_EndpointDescription *ep = &config->endpoints[i];
UA_ByteString basic128uri =
UA_BYTESTRING("http://opcfoundation.org/UA/SecurityPolicy#Basic128Rsa15");
UA_ByteString basic256uri =
UA_BYTESTRING("http://opcfoundation.org/UA/SecurityPolicy#Basic256");
if(!UA_String_equal(&ep->securityPolicyUri, &basic128uri) &&
!UA_String_equal(&ep->securityPolicyUri, &basic256uri))
continue;
UA_EndpointDescription_clear(ep);
/* Move the last to this position */
if(i + 1 < config->endpointsSize) {
config->endpoints[i] = config->endpoints[config->endpointsSize - 1];
i--;
}
config->endpointsSize--;
}
/* Delete the entire array if the last Endpoint was removed */
if(config->endpointsSize == 0) {
UA_free(config->endpoints);
config->endpoints = NULL;
}
}
#endif
/* This access control callback checks the user access on the SecurityGroup object, when
* GetSecurityKeys method is called. */
static UA_Boolean
getUserExecutableOnObject_sks(UA_Server *server, UA_AccessControl *ac,
const UA_NodeId *sessionId, void *sessionContext,
const UA_NodeId *methodId, void *methodContext,
const UA_NodeId *objectId, void *objectContext) {
if(objectContext && sessionContext) {
UA_ByteString *username = (UA_ByteString *)objectContext;
UA_ByteString *sessionUsername = (UA_ByteString *)sessionContext;
if(!UA_ByteString_equal(username, sessionUsername))
return false;
}
return true;
}
/**
* We need to add a SecurityGroup for the management of security keys on SKS server.
* The publishers/subcribers can requests the keys on SKS with their associated
* security groups. The SKS will check if the user credentials used to establish
* the session have the access to the requested security group managed by SKS.
*/
static UA_StatusCode
addSecurityGroup(UA_Server *server, UA_NodeId *outNodeId) {
UA_Duration keyLifeTimeMinutes = DEMO_KEYLIFETIME_MINUTES;
UA_UInt32 maxFutureKeyCount = DEMO_MAXFUTUREKEYCOUNT;
UA_UInt32 maxPastKeyCount = DEMO_MAXPASTKEYCOUNT;
char *securityGroupName = DEMO_SECURITYGROUPNAME;
UA_NodeId securityGroupParent =
UA_NODEID_NUMERIC(0, UA_NS0ID_PUBLISHSUBSCRIBE_SECURITYGROUPS);
UA_SecurityGroupConfig config;
memset(&config, 0, sizeof(UA_SecurityGroupConfig));
config.keyLifeTime = keyLifeTimeMinutes * MINUTE_SECONDS * MILLI_SECONDS;
config.securityPolicyUri = UA_STRING(policUri);
config.securityGroupName = UA_STRING(securityGroupName);
config.maxFutureKeyCount = maxFutureKeyCount;
config.maxPastKeyCount = maxPastKeyCount;
return UA_Server_addSecurityGroup(server, securityGroupParent, &config, outNodeId);
}
/*
* we need to set user access for the security groups. The allowed users can be
* set in the node context of the SecurityGroup Object Node, which are checked
* are checked by access control plugin.
*/
static UA_StatusCode
setSecurityGroupRolePermission(UA_Server *server, UA_NodeId securityGroupNodeId,
void *nodeContext) {
if(!server && !nodeContext)
return UA_STATUSCODE_BADINVALIDARGUMENT;
UA_ByteString allowedUsername = UA_STRING((char *)nodeContext);
return UA_Server_setNodeContext(server, securityGroupNodeId, &allowedUsername);
}
UA_Boolean running = true;
static void
stopHandler(int sign) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Received Ctrl-C");
running = 0;
}
static void
usage(char *progname) {
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Usage:\n"
#ifndef UA_ENABLE_ENCRYPTION
"%s [<server-certificate.der>]\n"
#else
"%s <server-certificate.der> <private-key.der>\n"
#ifndef __linux__
"\t[--trustlist <tl1.ctl> <tl2.ctl> ... ]\n"
"\t[--issuerlist <il1.der> <il2.der> ... ]\n"
"\t[--revocationlist <rv1.crl> <rv2.crl> ...]\n"
#else
"\t[--trustlistFolder <folder>]\n"
"\t[--issuerlistFolder <folder>]\n"
"\t[--revocationlistFolder <folder>]\n"
#endif
"\t[--enableUnencrypted]\n"
"\t[--enableOutdatedSecurityPolicy]\n"
#endif
"\t[--enableTimestampCheck]\n"
"\t[--enableAnonymous]\n",
progname);
}
int
main(int argc, char **argv) {
signal(SIGINT, stopHandler); /* catches ctrl-c */
signal(SIGTERM, stopHandler);
for(int i = 1; i < argc; i++) {
if(strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
usage(argv[0]);
return EXIT_SUCCESS;
}
}
UA_ServerConfig config;
memset(&config, 0, sizeof(UA_ServerConfig));
/* Load certificate */
size_t pos = 1;
UA_ByteString certificate = UA_BYTESTRING_NULL;
if((size_t)argc >= pos + 1) {
certificate = loadFile(argv[1]);
if(certificate.length == 0) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Unable to load file %s.", argv[pos]);
return EXIT_FAILURE;
}
pos++;
}
#ifdef UA_ENABLE_ENCRYPTION
/* Load the private key */
UA_ByteString privateKey = UA_BYTESTRING_NULL;
if((size_t)argc >= pos + 1) {
privateKey = loadFile(argv[2]);
if(privateKey.length == 0) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Unable to load file %s.", argv[pos]);
return EXIT_FAILURE;
}
pos++;
}
char filetype = ' '; /* t==trustlist, l == issuerList, r==revocationlist */
UA_Boolean enableUnencr = false;
UA_Boolean enableSec = false;
#ifndef __linux__
UA_ByteString trustList[100];
size_t trustListSize = 0;
UA_ByteString issuerList[100];
size_t issuerListSize = 0;
UA_ByteString revocationList[100];
size_t revocationListSize = 0;
#else
const char *trustlistFolder = NULL;
const char *issuerlistFolder = NULL;
const char *revocationlistFolder = NULL;
#endif /* __linux__ */
#endif /* UA_ENABLE_ENCRYPTION */
UA_Boolean enableAnon = false;
UA_Boolean enableTime = false;
UA_UInt16 port = 4840;
/* Loop over the remaining arguments */
for(; pos < (size_t)argc; pos++) {
if(strcmp(argv[pos], "--port") == 0) {
pos++;
int inNum = atoi(argv[pos]);
if(inNum <= 0) {
usage(argv[0]);
return EXIT_FAILURE;
}
port = (UA_UInt16)inNum;
continue;
}
if(strcmp(argv[pos], "--enableAnonymous") == 0) {
enableAnon = true;
continue;
}
if(strcmp(argv[pos], "--enableTimestampCheck") == 0) {
enableTime = true;
continue;
}
#ifdef UA_ENABLE_ENCRYPTION
if(strcmp(argv[pos], "--enableUnencrypted") == 0) {
enableUnencr = true;
continue;
}
if(strcmp(argv[pos], "--enableOutdatedSecurityPolicy") == 0) {
enableSec = true;
continue;
}
#ifndef __linux__
if(strcmp(argv[pos], "--trustlist") == 0) {
filetype = 't';
continue;
}
if(strcmp(argv[pos], "--issuerlist") == 0) {
filetype = 'l';
continue;
}
if(strcmp(argv[pos], "--revocationlist") == 0) {
filetype = 'r';
continue;
}
if(filetype == 't') {
if(trustListSize >= 100) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Too many trust lists");
return EXIT_FAILURE;
}
trustList[trustListSize] = loadFile(argv[pos]);
if(trustList[trustListSize].data == NULL) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Unable to load trust list %s", argv[pos]);
return EXIT_FAILURE;
}
trustListSize++;
continue;
}
if(filetype == 'l') {
if(issuerListSize >= 100) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Too many trust lists");
return EXIT_FAILURE;
}
issuerList[issuerListSize] = loadFile(argv[pos]);
if(issuerList[issuerListSize].data == NULL) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Unable to load trust list %s", argv[pos]);
return EXIT_FAILURE;
}
issuerListSize++;
continue;
}
if(filetype == 'r') {
if(revocationListSize >= 100) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Too many revocation lists");
return EXIT_FAILURE;
}
revocationList[revocationListSize] = loadFile(argv[pos]);
if(revocationList[revocationListSize].data == NULL) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Unable to load revocationlist %s", argv[pos]);
return EXIT_FAILURE;
}
revocationListSize++;
continue;
}
#else /* __linux__ */
if(strcmp(argv[pos], "--trustlistFolder") == 0) {
filetype = 't';
continue;
}
if(strcmp(argv[pos], "--issuerlistFolder") == 0) {
filetype = 'l';
continue;
}
if(strcmp(argv[pos], "--revocationlistFolder") == 0) {
filetype = 'r';
continue;
}
if(filetype == 't') {
trustlistFolder = argv[pos];
continue;
}
if(filetype == 'l') {
issuerlistFolder = argv[pos];
continue;
}
if(filetype == 'r') {
revocationlistFolder = argv[pos];
continue;
}
#endif /* __linux__ */
#endif /* UA_ENABLE_ENCRYPTION */
usage(argv[0]);
return EXIT_FAILURE;
}
UA_Server *server = NULL;
#ifdef UA_ENABLE_ENCRYPTION
#ifndef __linux__
UA_StatusCode res = UA_ServerConfig_setDefaultWithSecurityPolicies(
&config, port, &certificate, &privateKey, trustList, trustListSize, issuerList,
issuerListSize, revocationList, revocationListSize);
if(res != UA_STATUSCODE_GOOD)
goto cleanup;
#else /* On Linux we can monitor the certs folder and reload when changes are made */
UA_StatusCode res = UA_ServerConfig_setDefaultWithSecurityPolicies(
&config, port, &certificate, &privateKey, NULL, 0, NULL, 0, NULL, 0);
if(res != UA_STATUSCODE_GOOD)
goto cleanup;
#ifdef UA_ENABLE_CERT_REJECTED_DIR
res |= UA_CertificateVerification_CertFolders(&config.secureChannelPKI,
trustlistFolder, issuerlistFolder,
revocationlistFolder, NULL);
res |= UA_CertificateVerification_CertFolders(&config.sessionPKI,
trustlistFolder, issuerlistFolder,
revocationlistFolder, NULL);
#else
res |= UA_CertificateVerification_CertFolders(&config.secureChannelPKI,
trustlistFolder, issuerlistFolder,
revocationlistFolder);
res |= UA_CertificateVerification_CertFolders(&config.sessionPKI,
trustlistFolder, issuerlistFolder,
revocationlistFolder);
#endif
if(res != UA_STATUSCODE_GOOD)
goto cleanup;
#endif /* __linux__ */
if(!enableUnencr)
disableUnencrypted(&config);
if(!enableSec)
disableOutdatedSecurityPolicy(&config);
#else /* UA_ENABLE_ENCRYPTION */
UA_StatusCode res = UA_ServerConfig_setMinimal(&config, port, &certificate);
if(res != UA_STATUSCODE_GOOD)
goto cleanup;
#endif /* UA_ENABLE_ENCRYPTION */
if(!enableAnon)
disableAnonymous(&config);
/* Limit the number of SecureChannels and Sessions */
config.maxSecureChannels = 10;
config.maxSessions = 20;
/* Revolve the SecureChannel token every 300 seconds */
config.maxSecurityTokenLifetime = 300000;
/* Set operation limits */
config.maxNodesPerRead = MAX_OPERATION_LIMIT;
config.maxNodesPerWrite = MAX_OPERATION_LIMIT;
config.maxNodesPerMethodCall = MAX_OPERATION_LIMIT;
config.maxNodesPerBrowse = MAX_OPERATION_LIMIT;
config.maxNodesPerRegisterNodes = MAX_OPERATION_LIMIT;
config.maxNodesPerTranslateBrowsePathsToNodeIds = MAX_OPERATION_LIMIT;
config.maxNodesPerNodeManagement = MAX_OPERATION_LIMIT;
config.maxMonitoredItemsPerCall = MAX_OPERATION_LIMIT;
/* Set Subscription limits */
#ifdef UA_ENABLE_SUBSCRIPTIONS
config.maxSubscriptions = 20;
#endif
/* If RequestTimestamp is '0', log the warning and proceed */
config.verifyRequestTimestamp = UA_RULEHANDLING_WARN;
if(enableTime)
config.verifyRequestTimestamp = UA_RULEHANDLING_DEFAULT;
/* Override with a custom access control policy */
UA_String_clear(&config.applicationDescription.applicationUri);
config.applicationDescription.applicationUri =
UA_String_fromChars("urn:open62541.server.application");
config.shutdownDelay = 5000.0; /* 5s */
/* Add supported pubsub security policies by this sks instance */
config.pubSubConfig.securityPolicies =
(UA_PubSubSecurityPolicy *)UA_malloc(sizeof(UA_PubSubSecurityPolicy));
config.pubSubConfig.securityPoliciesSize = 1;
UA_PubSubSecurityPolicy_Aes256Ctr(config.pubSubConfig.securityPolicies,
config.logging);
/* User Access Control */
config.accessControl.getUserExecutableOnObject = getUserExecutableOnObject_sks;
server = UA_Server_newWithConfig(&config);
if(!server) {
res = UA_STATUSCODE_BADINTERNALERROR;
goto cleanup;
}
/* Add SecurityGroup on SKS server */
UA_NodeId outNodeId;
res = addSecurityGroup(server, &outNodeId);
if(res != UA_STATUSCODE_GOOD)
goto cleanup;
/* set user access permissions on securitygroup*/
char *username = "user1";
res = setSecurityGroupRolePermission(server, outNodeId, username);
if(res != UA_STATUSCODE_GOOD)
goto cleanup;
/* run server */
res = UA_Server_run(server, &running);
cleanup:
if(server)
UA_Server_delete(server);
else
UA_ServerConfig_clean(&config);
UA_ByteString_clear(&certificate);
#if defined(UA_ENABLE_ENCRYPTION)
UA_ByteString_clear(&privateKey);
#ifndef __linux__
for(size_t i = 0; i < trustListSize; i++)
UA_ByteString_clear(&trustList[i]);
for(size_t i = 0; i < issuerListSize; i++)
UA_ByteString_clear(&issuerList[i]);
for(size_t i = 0; i < revocationListSize; i++)
UA_ByteString_clear(&revocationList[i]);
#endif
#endif
return res == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}