opc source code
This commit is contained in:
8
third_party/open62541/examples/pubsub/sks/CMakeLists.txt
vendored
Normal file
8
third_party/open62541/examples/pubsub/sks/CMakeLists.txt
vendored
Normal 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)
|
||||
261
third_party/open62541/examples/pubsub/sks/pubsub_publish_encrypted_sks.c
vendored
Normal file
261
third_party/open62541/examples/pubsub/sks/pubsub_publish_encrypted_sks.c
vendored
Normal 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);
|
||||
}
|
||||
421
third_party/open62541/examples/pubsub/sks/pubsub_subscribe_encrypted_sks.c
vendored
Normal file
421
third_party/open62541/examples/pubsub/sks/pubsub_subscribe_encrypted_sks.c
vendored
Normal 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);
|
||||
}
|
||||
534
third_party/open62541/examples/pubsub/sks/server_pubsub_central_sks.c
vendored
Normal file
534
third_party/open62541/examples/pubsub/sks/server_pubsub_central_sks.c
vendored
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user