[REVIEW] Pubsub Encryption using key stored in TPM (#4560)

* feat(pubsub): Add PubSub example to use keys stored in TPM

 - Need to encrypt the AES and HMAC keys using the key stored
   in TPM, before running the PubSub application
 - The PubSub application decrypts the keys available in the
   filesystem using the TPM key

Change-Id: I41c7906675333fa46673dabae1bb6d6d1c3fbecc

* feat(tools): Support to encrypt the key file using TPM key

 - Add cert_encrypt_tpm.c to read the key file present in the
   file system, encrypt it using the key stored in TPM and
   store the encrypted data in different file
 - Delete the original key after creating encrypted key

Change-Id: I9fc77ebf0c76a990c70f4d228950fba09fc39c51

* docs(pubsub): Add README for PubSub TPM keystore application

 - README includes environment setup
 - Steps to generate encryption and signing keys for PubSub
 - Steps to use TPM keys to encrypt the keys in filesystem
 - Build and run Pubsub application
 - Change CMake build flag

Change-Id: I025662bd36ed9f27c7c23b8eda6e2f52cad82021

Co-authored-by: andreasebner <andreas.ebner@iosb.fraunhofer.de>
This commit is contained in:
Opcua_Tsn_Team_Kalycito 2021-10-08 17:34:47 +05:30 committed by GitHub
parent 43f6f035a5
commit b9a8fcbd3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1518 additions and 9 deletions

View File

@ -17,10 +17,6 @@ set(PYTHON_EXECUTABLE ${Python3_EXECUTABLE})
find_package(Git)
include(AssignSourceGroup)
include(GNUInstallDirs)
if(UA_ENABLE_TPM2_SECURITY)
find_library(TPM2_LIB tpm2_pkcs11)
message(${TPM2_LIB})
endif()
# Set when installed via make install
set(open62541_TOOLS_DIR ${PROJECT_SOURCE_DIR}/tools)
@ -227,6 +223,11 @@ elseif(UA_ENABLE_ENCRYPTION_TPM2 STREQUAL "KEYSTORE")
set(UA_ENABLE_TPM2_KEYSTORE ON)
endif()
if(UA_ENABLE_TPM2_SECURITY)
find_library(TPM2_LIB tpm2_pkcs11)
message(${TPM2_LIB})
endif()
if(UA_ENABLE_TPM2_SECURITY)
if(NOT UA_ENABLE_PUBSUB_ENCRYPTION)
message(FATAL_ERROR "TPM2 encryption cannot be used with disabled UA_ENABLE_PUBSUB_ENCRYPTION")
@ -234,8 +235,14 @@ if(UA_ENABLE_TPM2_SECURITY)
endif()
if(UA_ENABLE_TPM2_KEYSTORE)
if(NOT UA_ENABLE_ENCRYPTION)
message(FATAL_ERROR "TPM2 Keystore cannot be used with disabled UA_ENABLE_ENCRYPTION")
if(UA_ENABLE_PUBSUB)
if(NOT UA_ENABLE_PUBSUB_ENCRYPTION)
message(FATAL_ERROR "TPM2 Keystore for PubSub cannot be used with disabled UA_ENABLE_PUBSUB_ENCRYPTION")
endif()
else()
if(NOT UA_ENABLE_ENCRYPTION)
message(FATAL_ERROR "TPM2 Keystore cannot be used with disabled UA_ENABLE_ENCRYPTION")
endif()
endif()
endif()

View File

@ -250,6 +250,12 @@ if(UA_ENABLE_PUBSUB)
add_example(pubsub_publish_encrypted_tpm pubsub/pubsub_publish_encrypted_tpm.c)
add_example(pubsub_subscribe_encrypted_tpm pubsub/pubsub_subscribe_encrypted_tpm.c)
endif()
if(UA_ENABLE_TPM2_KEYSTORE)
add_example(pubsub_publish_encrypted_tpm_keystore pubsub/pubsub_publish_encrypted_tpm_keystore.c)
add_example(pubsub_subscribe_encrypted_tpm_keystore pubsub/pubsub_subscribe_encrypted_tpm_keystore.c)
target_link_libraries(pubsub_publish_encrypted_tpm_keystore tpm2_pkcs11 ssl crypto)
target_link_libraries(pubsub_subscribe_encrypted_tpm_keystore tpm2_pkcs11 ssl crypto)
endif()
endif()

View File

@ -113,7 +113,7 @@ Delete the original encryption and signing key
To run client and server applications over Ethernet in two nodes connected in peer-to-peer network
cd ../../
mkdir build && cd build
cmake -DUA_BUILD_EXAMPLES=ON -DUA_BUILD_TOOLS=ON -DUA_ENABLE_ENCRYPTION=MBEDTLS -DUA_ENABLE_ENCRYPTION_TPM2=KEYSTORE ..
cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_ENCRYPTION=MBEDTLS -DUA_ENABLE_ENCRYPTION_TPM2=KEYSTORE ..
make -j$(nproc)
The binaries are generated in build/bin/ folder

View File

@ -91,7 +91,7 @@ Set environment variable for TCTI connection and path for database:
To run PubSub applications over Ethernet in two nodes connected in peer-to-peer network
cd open62541/
mkdir build && cd build
cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON -DUA_ENABLE_PUBSUB_ETH_UADP=ON -DUA_ENABLE_PUBSUB_ENCRYPTION=ON -DUA_ENABLE_TPM2_SECURITY=ON ..
cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON -DUA_ENABLE_PUBSUB_ETH_UADP=ON -DUA_ENABLE_PUBSUB_ENCRYPTION=ON -DUA_ENABLE_ENCRYPTION_TPM2=PKCS11 ..
make
The binaries are generated in build/bin/ folder

View File

@ -0,0 +1,156 @@
1. TESTED ENVIRONMENT:
Apollo Lake processor-1.60GHz
OS - Ubuntu - 20.04.2 LTS
Kernel - 5.4.0-74-generic
2. ENVIRONMENT SETUP:
Follow the below steps in both Publisher and Subscriber nodes.
Install required packages:
sudo apt -y install acl autoconf autoconf-archive automake build-essential cmake doxygen gcc git iproute2 libcurl4-openssl-dev libjson-c-dev libcmocka0 libcmocka-dev libglib2.0-dev libini-config-dev libmbedtls-dev libssl-dev libsqlite3-dev libtool libyaml-dev pkg-config procps python3-pip sqlite3 udev uthash-dev
Install the TPM2 Software Stack library:
cd ${HOME}
git clone https://github.com/tpm2-software/tpm2-tss.git
cd ${HOME}/tpm2-tss
git checkout 2.4.6
./bootstrap
./configure --with-udevrulesdir=/etc/udev/rules.d --with-udevrulesprefix=70-
make -j$(nproc)
sudo make install
sudo ldconfig
sudo udevadm control --reload-rules && sudo udevadm trigger
Install the TPM2 Tools (tpm2-tools):
cd ${HOME}
git clone https://github.com/tpm2-software/tpm2-tools.git
cd tpm2-tools
git checkout 4.3.2
./bootstrap
./configure
make -j$(nproc)
sudo make install
sudo apt install opensc
Install adaptation of PKCS#11 for TPM2 (tpm2-pkcs11):
cd ${HOME}
git clone https://github.com/tpm2-software/tpm2-pkcs11.git
cd ${HOME}/tpm2-pkcs11
git checkout 1.6.0
./bootstrap
./configure
make -j$(nproc)
sudo make install
sudo ldconfig
cp ${HOME}/tpm2-pkcs11/src/pkcs11.h /usr/include
Install the required python packages:
cd ${HOME}/tpm2-pkcs11/tools/
sudo pip3 install pyasn1_modules
pip3 install .
Create an directory to create slot
cd ${HOME}/
mkdir pkcs11_store
Set environment variable for TCTI connection and path for database. If TPM hardware is not installed skip the below section:
cd ${HOME}/
echo 'export TPM2TOOLS_TCTI="device:/dev/tpm0"' >> .bashrc
echo 'export TPM2_PKCS11_STORE="'${HOME}'/pkcs11_store"' >> .bashrc
echo 'PATH=$PATH:'${HOME}'/pkcs11_store:'${HOME}'/tpm2-pkcs11/tools' >> .bashrc
source .bashrc
If TPM hardware is not available use TPM simulator to run the application.
Install the TPM2 simulator:
cd ${HOME}
mkdir ibmtpm1637
cd ibmtpm1637
wget https://sourceforge.net/projects/ibmswtpm2/files/latest/download/ibmtpm1637.tar.gz
tar -xzf ibmtpm1637.tar.gz
cd src
make -j$(nproc)
Install the TPM2 access broker and resource manager:
cd ${HOME}
git clone https://github.com/tpm2-software/tpm2-abrmd.git
cd ${HOME}/tpm2-abrmd
git checkout 2.4.0
sudo useradd --system --user-group tss # User already exists. Ignore message.
./bootstrap
./configure --with-dbuspolicydir=/etc/dbus-1/system.d/ --with-systemdsystemunitdir=/lib/systemd/system --with-systemdpresetdir=/lib/systemd/system-preset --datarootdir=/usr/share
make -j$(nproc)
sudo make install
sudo ldconfig
sudo pkill -HUP dbus-daemon
systemctl daemon-reload
Set environment variable for TCTI connection and path for database:
cd ${HOME}/pkcs11_store
cp ../ibmtpm1637/src/tpm_server ${HOME}/pkcs11_store
cd ${HOME}/
echo 'export TPM2TOOLS_TCTI="mssim:host=localhost,port=2231"' >> .bashrc
echo 'export TPM2_PKCS11_STORE="'${HOME}'/pkcs11_store"' >> .bashrc
echo 'PATH=$PATH:'${HOME}'/pkcs11_store:'${HOME}'/tpm2-pkcs11/tools' >> .bashrc
source .bashrc
In a terminal start the TPM2 simulator using the below command:
cd ${HOME}/pkcs11_store && tpm_server
In another terminal start the access broker and resource manager:
sudo tpm2-abrmd --allow-root --tcti="mssim" --logger=stdout
Creates the tpm2_pkcs11.sqlite3 database
pid="$(tpm2_ptool init | grep id | cut -d' ' -f 2-2)"
Create a token
tpm2_ptool addtoken --pid=$pid --sopin=123456 --userpin=123456 --label=opcua
The details of the options used in this command are:
--pid - The primary object id to associate with this token.
--sopin - The Administrator pin. This pin is used for object recovery.
--userpin - The user pin. This pin is used for authentication of object.
--label - An unique label to identify the profile in use.
--path - The location of the store directory.
Create the AES key
tpm2_ptool addkey --label=opcua --key-label=tpm_encrypt_key --userpin=123456 --algorithm=aes128
3. GENERATE AND ENCRYPT PUB/SUB KEYS
Verify if the TPM supports the following capbilities (https://github.com/tpm2-software/tpm2-tools/blob/master/man/tpm2_getcap.1.md):
tpm2_getcap algorithms | grep 'aes\|cbc'
tpm2_getcap commands | grep 'TPM2_CC_EncryptDecrypt\|TPM2_CC_VerifySignature'
# If above capbilities are not available, the application will not execute properly
Create encryption and signing key in the file system and copy the same in both Publisher and Subscriber nodes.
cd open62541/tools/tpm_keystore/
openssl rand 16 > aes128_encrypt.key
openssl rand 16 > aes128_signing.key
Seal the encryption and signing key files using the key available in TPM
gcc cert_encrypt_tpm.c -o cert_encrypt_tpm -ltpm2_pkcs11 -lssl -lcrypto
./cert_encrypt_tpm -s<slotID> -p<userPin> -l<keyLable> -f<keyToBeEncrypted> -o<keyToStoreEncryptedData>
Eg: ./cert_encrypt_tpm -s1 -p123456 -ltpm_encrypt_key -faes128_encrypt.key -oaes128_encrypt_sealed.key
./cert_encrypt_tpm -s1 -p123456 -ltpm_encrypt_key -faes128_signing.key -oaes128_signing_sealed.key
Delete the original encryption and signing key
rm aes128_encrypt.key
rm aes128_signing.key
4. BUILD AND RUN PUB/SUB APPLICATION
To run PubSub applications over Ethernet in two nodes connected in peer-to-peer network
cd ../../
mkdir build && cd build
cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON -DUA_ENABLE_PUBSUB_ETH_UADP=ON -DUA_ENABLE_PUBSUB_ENCRYPTION=ON -DUA_ENABLE_ENCRYPTION_TPM2=KEYSTORE ..
make -j$(nproc)
The binaries are generated in build/bin/ folder
./bin/examples/pubsub_publish_encrypted_tpm_keystore opc.eth://<MAC_of_subscriber_node> <interface> <encryption_key> <signing_key> <solt_id> <user_pin> <key_lable>
./bin/examples/pubsub_subscribe_encrypted_tpm_keystore opc.eth://<MAC_of_subscriber_node> <interface> <encryption_key> <signing_key> <solt_id> <user_pin> <key_lable>
Eg: ./bin/examples/pubsub_publish_encrypted_tpm_keystore opc.eth://00-07-32-6b-a6-63 enp2s0 ../tools/tpm_keystore/aes128_encrypt_sealed.key ../tools/tpm_keystore/aes128_signing_sealed.key 1 123456 tpm_encrypt_key
./bin/examples/pubsub_subscribe_encrypted_tpm_keystore opc.eth://00-07-32-6b-a6-63 enp2s0 ../tools/tpm_keystore/aes128_encrypt_sealed.key ../tools/tpm_keystore/aes128_signing_sealed.key 1 123456 tpm_encrypt_key
NOTE: Ignore the following warnings and error displayed during the run,
WARNING:fapi:src/tss2-fapi/api/Fapi_List.c:226:Fapi_List_Finish() Profile of path not provisioned: /HS/SRK
ERROR:fapi:src/tss2-fapi/api/Fapi_List.c:81:Fapi_List() ErrorCode (0x00060034) Entities_List
WARNING: Listing FAPI token objects failed: "fapi:Provisioning was not executed."
Please see https://github.com/tpm2-software/tpm2-pkcs11/blob/master/docs/FAPI.md for more details
WARNING: Getting tokens from fapi backend failed.

View File

@ -0,0 +1,584 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
*
* Copyright (c) 2021 Kalycito Infotech Private Limited
*/
/* Note: Have to enable UA_ENABLE_PUBSUB_ENCRYPTION and UA_ENABLE_ENCRYPTION_TPM2=KEYSTORE
to run the application */
#include <open62541/plugin/log_stdout.h>
#include <open62541/plugin/pubsub_ethernet.h>
#include <open62541/plugin/pubsub_udp.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <open62541/plugin/securitypolicy_default.h>
#include <signal.h>
#include <openssl/evp.h>
#include <pkcs11.h>
#include "common.h"
#define UA_AES128CTR_SIGNING_KEY_LENGTH 32
#define UA_AES128CTR_KEY_LENGTH 16
#define UA_AES128CTR_KEYNONCE_LENGTH 4
UA_Byte signingKey[UA_AES128CTR_SIGNING_KEY_LENGTH] = {0};
UA_Byte encryptingKey[UA_AES128CTR_KEY_LENGTH] = {0};
UA_Byte keyNonce[UA_AES128CTR_KEYNONCE_LENGTH] = {0};
UA_NodeId connectionIdent, publishedDataSetIdent, writerGroupIdent;
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.publisherId.numeric = 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) {
/* Create variable to publish integer data */
UA_NodeId publisherNode;
UA_VariableAttributes attr = UA_VariableAttributes_default;
attr.description = UA_LOCALIZEDTEXT("en-US","Published Int32");
attr.displayName = UA_LOCALIZEDTEXT("en-US","Published Int32");
attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
UA_Int32 publisherData = 42;
UA_Variant_setScalar(&attr.value, &publisherData, &UA_TYPES[UA_TYPES_INT32]);
UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, 1000),
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "Published Int32"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attr, NULL, &publisherNode);
/* Data Set Field */
UA_NodeId dataSetFieldIdent;
UA_DataSetFieldConfig dataSetFieldConfig;
memset(&dataSetFieldConfig, 0, sizeof(UA_DataSetFieldConfig));
dataSetFieldConfig.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
dataSetFieldConfig.field.variable.fieldNameAlias = UA_STRING("Published Int32");
dataSetFieldConfig.field.variable.promotedField = UA_FALSE;
dataSetFieldConfig.field.variable.publishParameters.publishedVariable = publisherNode;
dataSetFieldConfig.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
UA_Server_addDataSetField (server, publishedDataSetIdent, &dataSetFieldConfig, &dataSetFieldIdent);
}
static void
addWriterGroup(UA_Server *server) {
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 */
UA_ServerConfig *config = UA_Server_getConfig(server);
writerGroupConfig.securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
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_Server_setWriterGroupOperational(server, writerGroupIdent);
UA_UadpWriterGroupMessageDataType_delete(writerGroupMessage);
/* Add the encryption key informaton */
UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKey};
UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKey};
UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNonce};
UA_Server_setWriterGroupEncryptionKeys(server, writerGroupIdent, 1, sk, ek, kn);
}
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);
}
UA_Boolean running = true;
static void stopHandler(int sign) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
running = false;
}
static int run(UA_String *transportProfile,
UA_NetworkAddressUrlDataType *networkAddressUrl) {
signal(SIGINT, stopHandler);
signal(SIGTERM, stopHandler);
UA_Server *server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_ServerConfig_setDefault(config);
/* Instantiate the PubSub SecurityPolicy */
config->pubSubConfig.securityPolicies = (UA_PubSubSecurityPolicy*)
UA_malloc(sizeof(UA_PubSubSecurityPolicy));
config->pubSubConfig.securityPoliciesSize = 1;
UA_PubSubSecurityPolicy_Aes128Ctr(config->pubSubConfig.securityPolicies,
&config->logger);
#ifdef UA_ENABLE_PUBSUB_ETH_UADP
UA_ServerConfig_addPubSubTransportLayer(config, UA_PubSubTransportLayerEthernet());
#else
UA_ServerConfig_addPubSubTransportLayer(config, UA_PubSubTransportLayerUDPMP());
#endif
addPubSubConnection(server, transportProfile, networkAddressUrl);
addPublishedDataSet(server);
addDataSetField(server);
addWriterGroup(server);
addDataSetWriter(server);
UA_StatusCode retval = UA_Server_run(server, &running);
UA_Server_delete(server);
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}
static void get_MAC(const uint8_t *message, size_t message_len, unsigned char **message_digest,
unsigned int *message_digest_len)
{
EVP_MD_CTX *md_ctx;
if((md_ctx = EVP_MD_CTX_new()) == NULL) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Error while EVP_MD_CTX_new");
return;
}
if(1 != EVP_DigestInit_ex(md_ctx, EVP_sha256(), NULL)) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Error while EVP_DigestInit_ex");
return;
}
if(1 != EVP_DigestUpdate(md_ctx, message, message_len)) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Error while EVP_DigestUpdate");
return;
}
if((*message_digest = (unsigned char *)OPENSSL_malloc((size_t)EVP_MD_size(EVP_sha256()))) == NULL) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Error while OPENSSL_malloc");
return;
}
if(1 != EVP_DigestFinal_ex(md_ctx, *message_digest, message_digest_len)) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Error while EVP_DigestFinal_ex");
return;
}
EVP_MD_CTX_free(md_ctx);
}
/* If object is found the object_handle is set */
static UA_Boolean
find_object_by_label(CK_SESSION_HANDLE hSession, char *label, unsigned long *object_handle) {
UA_StatusCode rv = UA_STATUSCODE_GOOD;
UA_Boolean rtnval = UA_FALSE;
unsigned long foundCount = 0;
do
{
CK_OBJECT_HANDLE hObject = 0;
rv = (UA_StatusCode)C_FindObjects(hSession, &hObject, 1, &foundCount );
if (rv == UA_STATUSCODE_GOOD) {
/* This will show the labels and values */
CK_ATTRIBUTE attrTemplate[] = {
{CKA_LABEL, NULL_PTR, 0}
};
rv = (UA_StatusCode)C_GetAttributeValue(hSession, hObject, attrTemplate, 1);
if (attrTemplate[0].ulValueLen > 0)
attrTemplate[0].pValue = (char *)UA_malloc(attrTemplate[0].ulValueLen);
rv = (UA_StatusCode)C_GetAttributeValue(hSession, hObject, attrTemplate, 1);
if (attrTemplate[0].ulValueLen > 0) {
char * val = (char *)UA_malloc(attrTemplate[0].ulValueLen + 1);
strncpy(val, (const char*)attrTemplate[0].pValue, attrTemplate[0].ulValueLen);
val[attrTemplate[0].ulValueLen] = '\0';
if (strcasecmp(val, (char *)label) == 0) rtnval = true;
UA_free(val);
*object_handle = hObject;
}
if (attrTemplate[0].pValue)
UA_free(attrTemplate[0].pValue);
} else {
rtnval = UA_FALSE;
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_FindObjects failed = 0x%.8lx", (long unsigned int)rv);
}
} while( rv == UA_STATUSCODE_GOOD && foundCount > 0 && !rtnval);
return rtnval;
}
static UA_StatusCode
decrypt_data(unsigned long slotNum, unsigned char *pin, char *label, UA_ByteString *in_data,
UA_ByteString **decrypted_data, UA_ByteString * iv_data, uint64_t orginal_data_length) {
UA_StatusCode rv = UA_STATUSCODE_GOOD;
UA_ByteString *out_data = *decrypted_data;
unsigned long *pSlotList = NULL;
unsigned long slotID = 0;
unsigned long int ulSlotCount = 0;
unsigned long hSession;
CK_SESSION_INFO sessionInfo;
unsigned long hObject = 0;
unsigned char *ptr_encrypted_data;
unsigned long encrypted_data_length = 0;
unsigned char *data_clear;
unsigned long clear_data_length = 0;
unsigned long declen = 16;
unsigned char iv[iv_data->length];
UA_Boolean key_object_found = UA_FALSE;
CK_OBJECT_CLASS key_class = CKO_SECRET_KEY;
CK_KEY_TYPE key_type = CKK_AES;
CK_ATTRIBUTE attrTemplate[] = {
{CKA_CLASS, &key_class, sizeof(key_class)},
{CKA_KEY_TYPE, &key_type, sizeof(key_type)},
{CKA_LABEL, (void *)label, strlen(label)}
};
if (iv_data && iv_data->length > 0 && iv_data->data) {
rv = (UA_StatusCode)C_Initialize(NULL_PTR);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_Initialize failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
} else {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"The initializtion vector is not valid");
goto cleanup;
}
rv = (UA_StatusCode)C_GetSlotList(CK_TRUE, NULL, &ulSlotCount);
if ((rv == UA_STATUSCODE_GOOD) && (ulSlotCount > 0)) {
pSlotList = (unsigned long*)UA_malloc(ulSlotCount * sizeof (unsigned long));
if (pSlotList == NULL) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"System error: unable to allocate memory");
goto cleanup;
}
rv = (UA_StatusCode)C_GetSlotList(CK_TRUE, pSlotList, &ulSlotCount);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"GetSlotList failed: Unable to get slot list = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
} else {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"GetSlotList failed: Unable to get slot count = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
for (unsigned long int i = 0; i < ulSlotCount; i++) {
slotID = pSlotList[i];
if (slotID == slotNum) {
CK_TOKEN_INFO token_info;
rv = (UA_StatusCode)C_GetTokenInfo(slotID, &token_info);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_GetTokenInfo failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
break;
}
}
rv = (UA_StatusCode)C_OpenSession(slotID, CKF_SERIAL_SESSION | CKF_RW_SESSION, (CK_VOID_PTR) NULL, NULL, &hSession);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_OpenSession failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
rv = (UA_StatusCode)C_GetSessionInfo(hSession, &sessionInfo);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_GetSessionInfo failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
if(sessionInfo.state != CKS_RW_USER_FUNCTIONS) {
/* Logs a user into a token */
rv = (UA_StatusCode)C_Login(hSession, CKU_USER, pin, strlen((const char *)pin));
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"C_Login failed: Error = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
}
rv = (UA_StatusCode)C_FindObjectsInit(hSession, attrTemplate, sizeof(attrTemplate)/sizeof (CK_ATTRIBUTE));
if (rv == UA_STATUSCODE_GOOD) {
key_object_found = find_object_by_label(hSession, label, &hObject);
if (key_object_found == UA_FALSE){
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "ERROR: key object not found");
goto cleanup;
}
rv = (UA_StatusCode)C_FindObjectsFinal(hSession);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_FindObjectsFinal failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
} else {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_FindObjectsInit failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
for (size_t i=0; i < iv_data->length; i++) {
iv[i] = *((CK_BYTE *)(iv_data->data + i));
}
CK_MECHANISM mechanism = {CKM_AES_CBC, iv, sizeof(iv)};
encrypted_data_length = in_data->length;
clear_data_length = encrypted_data_length;
ptr_encrypted_data = (CK_BYTE *)(UA_malloc(encrypted_data_length * sizeof(CK_BYTE)));
memset(ptr_encrypted_data, 0, encrypted_data_length);
memcpy(ptr_encrypted_data, (CK_BYTE *)(in_data->data), in_data->length);
rv = (UA_StatusCode)C_DecryptInit(hSession, &mechanism, hObject);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_DecryptInit failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
data_clear = (CK_BYTE *)UA_malloc(clear_data_length * sizeof(CK_BYTE));
memset(data_clear, 0, clear_data_length);
unsigned long part_number = 0;
while (rv == UA_STATUSCODE_GOOD && part_number * 16 < clear_data_length - 16) {
rv = (UA_StatusCode)C_DecryptUpdate(hSession, ptr_encrypted_data + part_number * 16,
16, &data_clear[part_number*16], &declen);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_Decryptupdate failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
part_number++;
}
rv = (UA_StatusCode)C_DecryptFinal(hSession, &data_clear[part_number *16], &declen);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_DecryptFinal failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
if (orginal_data_length > clear_data_length)
orginal_data_length = 0;
if (out_data->data) {
UA_free(out_data->data);
out_data->length = 0;
}
out_data->data = (UA_Byte *)UA_malloc(orginal_data_length);
memcpy(out_data->data, data_clear, orginal_data_length);
out_data->length = orginal_data_length;
C_Logout(hSession);
C_CloseSession(hSession);
C_Finalize(NULL);
cleanup:
if (data_clear)
UA_free(data_clear);
if (pSlotList)
UA_free(pSlotList);
if (ptr_encrypted_data)
UA_free(ptr_encrypted_data);
return rv;
}
static UA_StatusCode
decrypt(unsigned long slotNum, unsigned char *pin, char *label,
UA_ByteString *in_data, UA_ByteString **decrypted_data) {
UA_StatusCode rv = UA_STATUSCODE_GOOD;
/* For decrypt
Calculate the HMAC of the output without the 32 byte (256 bit) md_value
Check the calcualted md with the md from the input
if they match continue to decrypt the input */
unsigned char *md_value;
unsigned int md_len;
unsigned int expected_md_len = 32;
UA_ByteString *enc_data = UA_ByteString_new();
UA_ByteString *iv_data = UA_ByteString_new();
/* Get the HMAC */
get_MAC(in_data->data, in_data->length - expected_md_len, &md_value, &md_len);
if (memcmp(in_data->data + in_data->length - expected_md_len, md_value, md_len) == 0) {
/* Get the iv that was appended to the encrypted data
find the iv. It was packed in the first 16 bytes of the last 56 in the file */
UA_ByteString_allocBuffer(iv_data, 16);
uint8_t * ptr_in_data = (uint8_t *)(in_data->data);
memcpy(iv_data->data, ptr_in_data + in_data->length - sizeof(uint64_t) - expected_md_len - iv_data->length, iv_data->length);
/* Find the data length in output */
uint64_t clear_data_length;
clear_data_length = *((uint64_t *)(ptr_in_data + in_data->length - sizeof(uint64_t) - expected_md_len));
/* Remove the extra 56 bytes that were added at the end of the encrypted data */
UA_ByteString_allocBuffer(enc_data, in_data->length - sizeof(uint64_t) - expected_md_len - iv_data->length);
memcpy(enc_data->data, ptr_in_data, enc_data->length);
rv = (UA_StatusCode)decrypt_data(slotNum, pin, label, enc_data, decrypted_data, iv_data, clear_data_length);
if(rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "decrypt_data failed");
}
UA_ByteString_delete(iv_data);
UA_ByteString_delete(enc_data);
} else {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "HMAC does not match");
return EXIT_FAILURE;
}
if(md_value) OPENSSL_free(md_value);
return rv;
}
static void
usage(char *progname) {
printf("usage: %s [uri] [device] [encryptionKey] [signingkey] [slotId] [userPin] [keyLabel]\n", progname);
}
int main(int argc, char **argv) {
UA_StatusCode rv = UA_STATUSCODE_GOOD;
UA_ByteString encrypt_in_data = UA_BYTESTRING_NULL;
UA_ByteString sign_in_data = UA_BYTESTRING_NULL;
UA_ByteString *encrypt_out_data = NULL;
UA_ByteString *sign_out_data = NULL;
char *encryptionKeyFile = NULL;
unsigned char *userpin = NULL;
char *signingKeyFile = NULL;
char *keyLabel = NULL;
unsigned long slotId = 0;
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/")};
if (argc > 1) {
if (strcmp(argv[1], "-h") == 0) {
usage(argv[0]);
return EXIT_SUCCESS;
} else if (strncmp(argv[1], "opc.udp://", 10) == 0) {
networkAddressUrl.url = UA_STRING(argv[1]);
} else if (strncmp(argv[1], "opc.eth://", 10) == 0) {
transportProfile =
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp");
if (argc != 8) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Error: Provide uri, interface name, encryptionKey, signingkey, slotId, userpin, and keyLabel\n");
return EXIT_FAILURE;
}
} else {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Error: unknown URI");
return EXIT_FAILURE;
}
networkAddressUrl.url = UA_STRING(argv[1]);
networkAddressUrl.networkInterface = UA_STRING(argv[2]);
encryptionKeyFile = argv[3];
signingKeyFile = argv[4];
slotId = (unsigned long)atoi(argv[5]);
userpin = (unsigned char*)argv[6];
keyLabel = argv[7];
}
else {
usage(argv[0]);
return EXIT_SUCCESS;
}
encrypt_in_data = loadFile(encryptionKeyFile);
if(encrypt_in_data.length == 0) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Unable to load encryption file %s", encryptionKeyFile);
return EXIT_FAILURE;
}
sign_in_data = loadFile(signingKeyFile);
if(sign_in_data.length == 0) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Unable to load signing file %s", signingKeyFile);
return EXIT_FAILURE;
}
encrypt_out_data = (UA_ByteString *)UA_malloc(sizeof(UA_ByteString));
encrypt_out_data->data = NULL;
encrypt_out_data->length = 0;
sign_out_data = (UA_ByteString *)UA_malloc(sizeof(UA_ByteString));
sign_out_data->data = NULL;
sign_out_data->length = 0;
rv = decrypt(slotId, userpin, keyLabel, &encrypt_in_data, &encrypt_out_data);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Decrypt failed for encryption key");
return EXIT_FAILURE;
}
rv = decrypt(slotId, userpin, keyLabel, &sign_in_data, &sign_out_data);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Decrypt failed for signing key");
return EXIT_FAILURE;
}
signingKey[UA_AES128CTR_SIGNING_KEY_LENGTH] = *(UA_Byte*)sign_out_data->data;
encryptingKey[UA_AES128CTR_KEY_LENGTH] = *(UA_Byte*)encrypt_out_data->data;
UA_ByteString_clear(&encrypt_in_data);
UA_ByteString_clear(&sign_in_data);
if (sign_out_data) {
if (sign_out_data->data)
UA_free(sign_out_data->data);
UA_free(sign_out_data);
}
if (encrypt_out_data) {
if (encrypt_out_data->data)
UA_free(encrypt_out_data->data);
UA_free(encrypt_out_data);
}
return run(&transportProfile, &networkAddressUrl);
}

View File

@ -0,0 +1,756 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
*
* Copyright (c) 2021 Kalycito Infotech Private Limited
*/
/* Note: Have to enable UA_ENABLE_PUBSUB_ENCRYPTION and UA_ENABLE_ENCRYPTION_TPM2=KEYSTORE
to run the application */
#include <open62541/plugin/log_stdout.h>
#include <open62541/plugin/pubsub_udp.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <open62541/types_generated.h>
#include <open62541/plugin/securitypolicy_default.h>
#include "ua_pubsub.h"
#include "common.h"
#if defined (UA_ENABLE_PUBSUB_ETH_UADP)
#include <open62541/plugin/pubsub_ethernet.h>
#endif
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <openssl/evp.h>
#include <pkcs11.h>
#define UA_AES128CTR_SIGNING_KEY_LENGTH 32
#define UA_AES128CTR_KEY_LENGTH 16
#define UA_AES128CTR_KEYNONCE_LENGTH 4
UA_Byte signingKey[UA_AES128CTR_SIGNING_KEY_LENGTH] = {0};
UA_Byte encryptingKey[UA_AES128CTR_KEY_LENGTH] = {0};
UA_Byte keyNonce[UA_AES128CTR_KEYNONCE_LENGTH] = {0};
UA_NodeId connectionIdentifier;
UA_NodeId readerGroupIdentifier;
UA_NodeId readerIdentifier;
UA_DataSetReaderConfig readerConfig;
static void fillTestDataSetMetaData(UA_DataSetMetaDataType *pMetaData);
/* 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.publisherId.numeric = UA_UInt32_random ();
retval |= UA_Server_addPubSubConnection (server, &connectionConfig, &connectionIdentifier);
if (retval != UA_STATUSCODE_GOOD) {
return retval;
}
return retval;
}
/**
* **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) {
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;
readerGroupConfig.securityPolicy = &config->pubSubConfig.securityPolicies[0];
retval |= UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig,
&readerGroupIdentifier);
/* Add the encryption key informaton */
UA_ByteString sk = {UA_AES128CTR_SIGNING_KEY_LENGTH, signingKey};
UA_ByteString ek = {UA_AES128CTR_KEY_LENGTH, encryptingKey};
UA_ByteString kn = {UA_AES128CTR_KEYNONCE_LENGTH, keyNonce};
// TODO security token not necessary for readergroup (extracted from security-header)
UA_Server_setReaderGroupEncryptionKeys(server, readerGroupIdentifier, 1, sk, ek, kn);
// TODO setOperational MUST be after setting keys
UA_Server_setReaderGroupOperational(server, readerGroupIdentifier);
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]);
/* Int32 DataType */
UA_FieldMetaData_init (&pMetaData->fields[0]);
UA_NodeId_copy(&UA_TYPES[UA_TYPES_INT32].typeId,
&pMetaData->fields[0].dataType);
pMetaData->fields[0].builtInType = UA_NS0ID_INT32;
pMetaData->fields[0].name = UA_STRING ("Int32");
pMetaData->fields[0].valueRank = -1; /* scalar */
/* DateTime DataType */
UA_FieldMetaData_init (&pMetaData->fields[1]);
UA_NodeId_copy (&UA_TYPES[UA_TYPES_DATETIME].typeId,
&pMetaData->fields[1].dataType);
pMetaData->fields[1].builtInType = UA_NS0ID_DATETIME;
pMetaData->fields[1].name = UA_STRING ("DateTime");
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
*/
/**
* 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;
}
static int
run(UA_String *transportProfile, UA_NetworkAddressUrlDataType *networkAddressUrl) {
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, 4801, NULL);
/* Instantiate the PubSub SecurityPolicy */
config->pubSubConfig.securityPolicies = (UA_PubSubSecurityPolicy*)
UA_malloc(sizeof(UA_PubSubSecurityPolicy));
config->pubSubConfig.securityPoliciesSize = 1;
UA_PubSubSecurityPolicy_Aes128Ctr(config->pubSubConfig.securityPolicies,
&config->logger);
/* Add the PubSub network layer implementation to the server config.
* The TransportLayer is acting as factory to create new connections
* on runtime. Details about the PubSubTransportLayer can be found inside the
* tutorial_pubsub_connection */
#ifdef UA_ENABLE_PUBSUB_ETH_UADP
UA_ServerConfig_addPubSubTransportLayer(config, UA_PubSubTransportLayerEthernet());
#else
UA_ServerConfig_addPubSubTransportLayer(config, UA_PubSubTransportLayerUDPMP());
#endif
/* 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);
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);
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}
static void get_MAC(const uint8_t *message, size_t message_len, unsigned char **message_digest,
unsigned int *message_digest_len)
{
EVP_MD_CTX *md_ctx;
if((md_ctx = EVP_MD_CTX_new()) == NULL) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Error while EVP_MD_CTX_new");
return;
}
if(1 != EVP_DigestInit_ex(md_ctx, EVP_sha256(), NULL)) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Error while EVP_DigestInit_ex");
return;
}
if(1 != EVP_DigestUpdate(md_ctx, message, message_len)) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Error while EVP_DigestUpdate");
return;
}
if((*message_digest = (unsigned char *)OPENSSL_malloc((size_t)EVP_MD_size(EVP_sha256()))) == NULL) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Error while OPENSSL_malloc");
return;
}
if(1 != EVP_DigestFinal_ex(md_ctx, *message_digest, message_digest_len)) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Error while EVP_DigestFinal_ex");
return;
}
EVP_MD_CTX_free(md_ctx);
}
/* If object is found the object_handle is set */
static UA_Boolean
find_object_by_label(CK_SESSION_HANDLE hSession, char *label, unsigned long *object_handle) {
UA_StatusCode rv = UA_STATUSCODE_GOOD;
UA_Boolean rtnval = UA_FALSE;
unsigned long foundCount = 0;
do
{
CK_OBJECT_HANDLE hObject = 0;
rv = (UA_StatusCode)C_FindObjects(hSession, &hObject, 1, &foundCount );
if (rv == UA_STATUSCODE_GOOD) {
/* This will show the labels and values */
CK_ATTRIBUTE attrTemplate[] = {
{CKA_LABEL, NULL_PTR, 0}
};
rv = (UA_StatusCode)C_GetAttributeValue(hSession, hObject, attrTemplate, 1);
if (attrTemplate[0].ulValueLen > 0)
attrTemplate[0].pValue = (char *)UA_malloc(attrTemplate[0].ulValueLen);
rv = (UA_StatusCode)C_GetAttributeValue(hSession, hObject, attrTemplate, 1);
if (attrTemplate[0].ulValueLen > 0) {
char * val = (char *)UA_malloc(attrTemplate[0].ulValueLen + 1);
strncpy(val, (const char*)attrTemplate[0].pValue, attrTemplate[0].ulValueLen);
val[attrTemplate[0].ulValueLen] = '\0';
if (strcasecmp(val, (char *)label) == 0) rtnval = true;
UA_free(val);
*object_handle = hObject;
}
if (attrTemplate[0].pValue)
UA_free(attrTemplate[0].pValue);
} else {
rtnval = UA_FALSE;
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_FindObjects failed = 0x%.8lx", (long unsigned int)rv);
}
} while( rv == UA_STATUSCODE_GOOD && foundCount > 0 && !rtnval);
return rtnval;
}
static UA_StatusCode
decrypt_data(unsigned long slotNum, unsigned char *pin, char *label, UA_ByteString *in_data,
UA_ByteString **decrypted_data, UA_ByteString * iv_data, uint64_t orginal_data_length) {
UA_StatusCode rv = UA_STATUSCODE_GOOD;
UA_ByteString *out_data = *decrypted_data;
unsigned long *pSlotList = NULL;
unsigned long slotID = 0;
unsigned long int ulSlotCount = 0;
unsigned long hSession;
CK_SESSION_INFO sessionInfo;
unsigned long hObject = 0;
unsigned char *ptr_encrypted_data;
unsigned long encrypted_data_length = 0;
unsigned char *data_clear;
unsigned long clear_data_length = 0;
unsigned long declen = 16;
unsigned char iv[iv_data->length];
UA_Boolean key_object_found = UA_FALSE;
CK_OBJECT_CLASS key_class = CKO_SECRET_KEY;
CK_KEY_TYPE key_type = CKK_AES;
CK_ATTRIBUTE attrTemplate[] = {
{CKA_CLASS, &key_class, sizeof(key_class)},
{CKA_KEY_TYPE, &key_type, sizeof(key_type)},
{CKA_LABEL, (void *)label, strlen(label)}
};
if (iv_data && iv_data->length > 0 && iv_data->data) {
rv = (UA_StatusCode)C_Initialize(NULL_PTR);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_Initialize failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
} else {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"The initializtion vector is not valid");
goto cleanup;
}
rv = (UA_StatusCode)C_GetSlotList(CK_TRUE, NULL, &ulSlotCount);
if ((rv == UA_STATUSCODE_GOOD) && (ulSlotCount > 0)) {
pSlotList = (unsigned long*)UA_malloc(ulSlotCount * sizeof (unsigned long));
if (pSlotList == NULL) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"System error: Unable to allocate memory");
goto cleanup;
}
rv = (UA_StatusCode)C_GetSlotList(CK_TRUE, pSlotList, &ulSlotCount);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"GetSlotList failed: Unable to get slot list = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
} else {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"GetSlotList failed: Unable to get slot count = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
for (unsigned long int i = 0; i < ulSlotCount; i++) {
slotID = pSlotList[i];
if (slotID == slotNum) {
CK_TOKEN_INFO token_info;
rv = (UA_StatusCode)C_GetTokenInfo(slotID, &token_info);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_GetTokenInfo failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
break;
}
}
rv = (UA_StatusCode)C_OpenSession(slotID, CKF_SERIAL_SESSION | CKF_RW_SESSION, (CK_VOID_PTR) NULL, NULL, &hSession);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_OpenSession failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
rv = (UA_StatusCode)C_GetSessionInfo(hSession, &sessionInfo);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_GetSessionInfo failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
if(sessionInfo.state != CKS_RW_USER_FUNCTIONS) {
/* Logs a user into a token */
rv = (UA_StatusCode)C_Login(hSession, CKU_USER, pin, strlen((const char *)pin));
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_Login failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
}
rv = (UA_StatusCode)C_FindObjectsInit(hSession, attrTemplate, sizeof(attrTemplate)/sizeof (CK_ATTRIBUTE));
if (rv == UA_STATUSCODE_GOOD) {
key_object_found = find_object_by_label(hSession, label, &hObject);
if (key_object_found == UA_FALSE){
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Error: key object not found");
goto cleanup;
}
rv = (UA_StatusCode)C_FindObjectsFinal(hSession);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_FindObjectsFinal failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
} else {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_FindObjectsInit failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
for (size_t i=0; i < iv_data->length; i++) {
iv[i] = *((CK_BYTE *)(iv_data->data + i));
}
CK_MECHANISM mechanism = {CKM_AES_CBC, iv, sizeof(iv)};
encrypted_data_length = in_data->length;
clear_data_length = encrypted_data_length;
ptr_encrypted_data = (CK_BYTE *)(UA_malloc(encrypted_data_length * sizeof(CK_BYTE)));
memset(ptr_encrypted_data, 0, encrypted_data_length);
memcpy(ptr_encrypted_data, (CK_BYTE *)(in_data->data), in_data->length);
rv = (UA_StatusCode)C_DecryptInit(hSession, &mechanism, hObject);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_DecryptInit failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
data_clear = (CK_BYTE *)UA_malloc(clear_data_length * sizeof(CK_BYTE));
memset(data_clear, 0, clear_data_length);
unsigned long part_number = 0;
while (rv == UA_STATUSCODE_GOOD && part_number * 16 < clear_data_length - 16) {
rv = (UA_StatusCode)C_DecryptUpdate(hSession, ptr_encrypted_data + part_number * 16,
16, &data_clear[part_number*16], &declen);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_Decryptupdate failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
part_number++;
}
rv = (UA_StatusCode)C_DecryptFinal(hSession, &data_clear[part_number *16], &declen);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY,
"Error from tpm2_pkcs11 - refer https://github.com/tpm2-software/tpm2-pkcs11/blob/master/src/pkcs11.h "
"C_DecryptFinal failed = 0x%.8lX", (long unsigned int)rv);
goto cleanup;
}
if (orginal_data_length > clear_data_length)
orginal_data_length = 0;
if (out_data->data) {
UA_free(out_data->data);
out_data->length = 0;
}
out_data->data = (UA_Byte *)UA_malloc(orginal_data_length);
memcpy(out_data->data, data_clear, orginal_data_length);
out_data->length = orginal_data_length;
C_Logout(hSession);
C_CloseSession(hSession);
C_Finalize(NULL);
cleanup:
if (data_clear)
UA_free(data_clear);
if (pSlotList)
UA_free(pSlotList);
if (ptr_encrypted_data)
UA_free(ptr_encrypted_data);
return rv;
}
static UA_StatusCode
decrypt(unsigned long slotNum, unsigned char *pin, char *label,
UA_ByteString *in_data, UA_ByteString **decrypted_data) {
UA_StatusCode rv = UA_STATUSCODE_GOOD;
/* For decrypt
Calculate the HMAC of the output without the 32 byte (256 bit) md_value
Check the calcualted md with the md from the input
if they match continue to decrypt the input */
unsigned char *md_value;
unsigned int md_len;
unsigned int expected_md_len = 32;
UA_ByteString *enc_data = UA_ByteString_new();
UA_ByteString *iv_data = UA_ByteString_new();
/* Get the HMAC */
get_MAC(in_data->data, in_data->length - expected_md_len, &md_value, &md_len);
if (memcmp(in_data->data + in_data->length - expected_md_len, md_value, md_len) == 0) {
/* Get the iv that was appended to the encrypted data
find the iv. It was packed in the first 16 bytes of the last 56 in the file */
UA_ByteString_allocBuffer(iv_data, 16);
uint8_t * ptr_in_data = (uint8_t *)(in_data->data);
memcpy(iv_data->data, ptr_in_data + in_data->length - sizeof(uint64_t) - expected_md_len - iv_data->length, iv_data->length);
/* Find the data length in output */
uint64_t clear_data_length;
clear_data_length = *((uint64_t *)(ptr_in_data + in_data->length - sizeof(uint64_t) - expected_md_len));
/* Remove the extra 56 bytes that were added at the end of the encrypted data */
UA_ByteString_allocBuffer(enc_data, in_data->length - sizeof(uint64_t) - expected_md_len - iv_data->length);
memcpy(enc_data->data, ptr_in_data, enc_data->length);
rv = (UA_StatusCode)decrypt_data(slotNum, pin, label, enc_data, decrypted_data, iv_data, clear_data_length);
if(rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "decrypt_data failed");
}
UA_ByteString_delete(iv_data);
UA_ByteString_delete(enc_data);
} else {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "HMAC does not match");
return EXIT_FAILURE;
}
if(md_value) OPENSSL_free(md_value);
return rv;
}
static void
usage(char *progname) {
printf("usage: %s [uri] [device] [encryptionKey] [signingkey] [slotId] [userPin] [keyLabel]\n", progname);
}
int main(int argc, char **argv) {
UA_StatusCode rv = UA_STATUSCODE_GOOD;
UA_ByteString encrypt_in_data = UA_BYTESTRING_NULL;
UA_ByteString sign_in_data = UA_BYTESTRING_NULL;
UA_ByteString *encrypt_out_data = NULL;
UA_ByteString *sign_out_data = NULL;
char *encryptionKeyFile = NULL;
unsigned char *userpin = NULL;
char *signingKeyFile = NULL;
char *keyLabel = NULL;
unsigned long slotId = 0;
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/")};
if (argc > 1) {
if (strcmp(argv[1], "-h") == 0) {
usage(argv[0]);
return EXIT_SUCCESS;
} else if (strncmp(argv[1], "opc.udp://", 10) == 0) {
networkAddressUrl.url = UA_STRING(argv[1]);
} else if (strncmp(argv[1], "opc.eth://", 10) == 0) {
transportProfile =
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp");
if (argc != 8) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Error: Provide uri, interface name, encryptionKey, signingkey, slotId, userpin, and keyLabel");
return EXIT_FAILURE;
}
} else {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Error: unknown URI");
return EXIT_FAILURE;
}
networkAddressUrl.url = UA_STRING(argv[1]);
networkAddressUrl.networkInterface = UA_STRING(argv[2]);
encryptionKeyFile = argv[3];
signingKeyFile = argv[4];
slotId = (unsigned long)atoi(argv[5]);
userpin = (unsigned char*)argv[6];
keyLabel = argv[7];
}
else {
usage(argv[0]);
return EXIT_SUCCESS;
}
encrypt_in_data = loadFile(encryptionKeyFile);
if(encrypt_in_data.length == 0) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Unable to load encryption file %s", encryptionKeyFile);
return EXIT_FAILURE;
}
sign_in_data = loadFile(signingKeyFile);
if(sign_in_data.length == 0) {
UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Unable to load signing file %s", signingKeyFile);
return EXIT_FAILURE;
}
encrypt_out_data = (UA_ByteString *)UA_malloc(sizeof(UA_ByteString));
encrypt_out_data->data = NULL;
encrypt_out_data->length = 0;
sign_out_data = (UA_ByteString *)UA_malloc(sizeof(UA_ByteString));
sign_out_data->data = NULL;
sign_out_data->length = 0;
rv = decrypt(slotId, userpin, keyLabel, &encrypt_in_data, &encrypt_out_data);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Decrypt failed for encryption key");
return EXIT_FAILURE;
}
rv = decrypt(slotId, userpin, keyLabel, &sign_in_data, &sign_out_data);
if (rv != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SECURITYPOLICY, "Decrypt failed for signing key");
return EXIT_FAILURE;
}
signingKey[UA_AES128CTR_SIGNING_KEY_LENGTH] = *(UA_Byte*)sign_out_data->data;
encryptingKey[UA_AES128CTR_KEY_LENGTH] = *(UA_Byte*)encrypt_out_data->data;
UA_ByteString_clear(&encrypt_in_data);
UA_ByteString_clear(&sign_in_data);
if (sign_out_data) {
if (sign_out_data->data)
UA_free(sign_out_data->data);
UA_free(sign_out_data);
}
if (encrypt_out_data) {
if (encrypt_out_data->data)
UA_free(encrypt_out_data->data);
UA_free(encrypt_out_data);
}
return run(&transportProfile, &networkAddressUrl);
}

View File

@ -521,4 +521,4 @@ int main(int argc, char** argv)
free(iv_data);
}
return 0;
}
}