Learn open62541 --- [33] Encryption (using OpenSSL)

Starting from the open62541 v1.1 version, OpenSSL is supported for encryption. The previous version only supports mbedTLS encryption. mbedTLS is more suitable to run on embedded devices, and it can also run on the desktop side; while OpenSSL is more widely used on the desktop side.

OpenSSL is basically installed by default under Linux system, which is more convenient to use, and it is also very simple to install OpenSSL under Windows.

This article mainly describes how to use OpenSSL for encrypted communication.


One download and compile open62541

Go to https://github.com/open62541/open62541/releases to download the release source code of v1.1.1 (the latest release version at the time of writing this article is 1.1.1) and
Insert picture description here
choose either zip or tar.gz format. Download the zip format here, and then put it in the Linux virtual machine for decompression. I use debian10, and ubuntu is the same.

Find and open CMakeLists.txt in the open62541 source directory, find the following 4 options,

  • UA_ENABLE_AMALGAMATION
  • UA_ENABLE_ENCRYPTION
  • UA_ENABLE_ENCRYPTION_OPENSSL
  • UA_ENABLE_ENCRYPTION_MBEDTLS

Change the value of the first 3 option from OFF to ON, and the last one keeps the default OFF value, thus enabling the OpenSSL function. Then create a new build directory under the open62541 source code directory , cd to enter, and execute the following command to compile,

cmake ..
make

Here you must pay special attention to cmake ..the search and print of OpenSSL during runtime. I print the following.
Insert picture description here
Different systems or openssl versions, the print will vary.

After compiling, we take out open62541.h and open62541.c (you can also take open62541.h and libopen62541.a, libopen62541.a is in the build/bin directory)


Two generate certificate and private key

Encryption requires the use of certificates and private keys. About how to implement encryption with certificates and private keys, you can read this article , which is easy to understand. Here use the tool that comes with open62541 to generate, the tool is located under tools/certs/,
Insert picture description here
this tool needs to install a python library netifaces, use the following command to install,

pip3 install netifaces

Then run python3 create_self-signed.py -hto view the help information, as follows,
Insert picture description here
here you need to generate 2 pairs of certificates and private keys, one for the server and one for the client. Use the following command to generate,

# 生成Server的证书和私匙,使用默认参数
python3 create_self-signed.py .  

# 生成Client的证书和私匙,自定义URI参数和名称
python3 create_self-signed.py -u urn:open62541.client.application -c client .

In this way, we can see two pairs of certificates and private keys in the tool/certs directory.
Insert picture description here
You can use the following command to view the URI parameters of the certificate. This parameter is more critical.

# 查看Server证书
openssl x509 -in server_cert.der -inform der -noout -text

The URI is printed as follows, and
Insert picture description here
then look at the client's certificate,

# 查看Client证书
openssl x509 -in client_cert.der -inform der -noout -text

The URI is printed as follows. In
Insert picture description here
this way, the certificate and private key are generated, save them for later use.


Three-verification encrypted communication

1. Code Verification

Need a server.c and a client.c, use open62541's own example, make certain modifications

client.c is as follows. The more important ones are lines 47 and 50. The encryption strategy uses 256Sha256, which is specified in line 41.

/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
 * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */

#include <stdlib.h>

#include "common.h"


#define MIN_ARGS 4

int main(int argc, char* argv[]) {
    
    
    if(argc < MIN_ARGS) {
    
    
        UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Arguments are missing. The required arguments are "
                     "<opc.tcp://host:port> "
                     "<client-certificate.der> <client-private-key.der> "
                     "[<trustlist1.der>, ...]");
        return EXIT_FAILURE;
    }

    const char *endpointUrl = argv[1];

    /* 加载client的证书和私匙 */
    UA_ByteString certificate = loadFile(argv[2]);
    UA_ByteString privateKey  = loadFile(argv[3]);

    /* 加载trustList. revocationList目前还不支持 */
    size_t trustListSize = 0;
    if(argc > MIN_ARGS)
        trustListSize = (size_t)argc-MIN_ARGS;
    UA_STACKARRAY(UA_ByteString, trustList, trustListSize);
    for(size_t trustListCount = 0; trustListCount < trustListSize; trustListCount++)
        trustList[trustListCount] = loadFile(argv[trustListCount+4]);

    UA_ByteString *revocationList = NULL;
    size_t revocationListSize = 0;

    UA_Client *client = UA_Client_new();
    UA_ClientConfig *cc = UA_Client_getConfig(client);
    cc->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT;
    cc->securityPolicyUri = UA_STRING_ALLOC("http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256");
    UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey,
                                         trustList, trustListSize,
                                         revocationList, revocationListSize);
    
    // 给安全策略None添加证书信息,去除运行时不匹配的警告
    UA_SecurityPolicy_None(cc->securityPolicies, certificate, &cc->logger);
    
    // 填坑的地方,非常重要,URI需要保证和证书里的URI一致
    cc->clientDescription.applicationUri = UA_STRING_ALLOC("urn:open62541.client.application");
    
    UA_ByteString_clear(&certificate);
    UA_ByteString_clear(&privateKey);
    for(size_t deleteCount = 0; deleteCount < trustListSize; deleteCount++) {
    
    
        UA_ByteString_clear(&trustList[deleteCount]);
    }

    /* Secure client connect */
    cc->securityMode = UA_MESSAGESECURITYMODE_SIGNANDENCRYPT; /* require encryption */
    UA_StatusCode retval = UA_Client_connect(client, endpointUrl);
    if(retval != UA_STATUSCODE_GOOD) {
    
    
        UA_Client_delete(client);
        return EXIT_FAILURE;
    }

    UA_Variant value;
    UA_Variant_init(&value);

    /* NodeId of the variable holding the current time */
    const UA_NodeId nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME);
    retval = UA_Client_readValueAttribute(client, nodeId, &value);

    if(retval == UA_STATUSCODE_GOOD &&
       UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_DATETIME])) {
    
    
        UA_DateTime raw_date  = *(UA_DateTime *) value.data;
        UA_DateTimeStruct dts = UA_DateTime_toStruct(raw_date);
        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "date is: %u-%u-%u %u:%u:%u.%03u\n",
                    dts.day, dts.month, dts.year, dts.hour, dts.min, dts.sec, dts.milliSec);
    }
    
    /* Clean up */
    UA_Variant_clear(&value);
    UA_Client_delete(client);
    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

The content of server.c is as follows, the place to configure the URI is on line 64, which must be the same as the URI value in the server certificate

/* 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 2019 (c) Kalycito Infotech Private Limited
 *
 */


#include <signal.h>
#include <stdlib.h>

#include "common.h"


UA_Boolean running = true;
static void stopHandler(int sig) {
    
    
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c");
    running = false;
}

int main(int argc, char* argv[]) {
    
    
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);

    if(argc < 3) {
    
    
        UA_LOG_FATAL(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Missing arguments. Arguments are "
                     "<server-certificate.der> <private-key.der> "
                     "[<trustlist1.der>, ...]");
        return EXIT_FAILURE;
    }

    /* 加载server的证书和私匙 */
    UA_ByteString certificate = loadFile(argv[1]);
    UA_ByteString privateKey  = loadFile(argv[2]);

    /* 加载trustlist */
    size_t trustListSize = 0;
    if(argc > 3)
        trustListSize = (size_t)argc-3;
    UA_STACKARRAY(UA_ByteString, trustList, trustListSize);
    for(size_t i = 0; i < trustListSize; i++)
        trustList[i] = loadFile(argv[i+3]);

    /* Loading of a issuer list, not used in this application */
    size_t issuerListSize = 0;
    UA_ByteString *issuerList = NULL;

    /* Loading of a revocation list currently unsupported */
    UA_ByteString *revocationList = NULL;
    size_t revocationListSize = 0;

    UA_Server *server = UA_Server_new();
    UA_ServerConfig *config = UA_Server_getConfig(server);

    UA_StatusCode retval =
        UA_ServerConfig_setDefaultWithSecurityPolicies(config, 4840,
                                                       &certificate, &privateKey,
                                                       trustList, trustListSize,
                                                       issuerList, issuerListSize,
                                                       revocationList, revocationListSize);
      
    // 填坑的地方,非常重要,URI需要保证和证书里的URI一致                                                
    config->applicationDescription.applicationUri = UA_STRING_ALLOC("urn:open62541.server.application");
    
    UA_ByteString_clear(&certificate);
    UA_ByteString_clear(&privateKey);
    for(size_t i = 0; i < trustListSize; i++)
        UA_ByteString_clear(&trustList[i]);
    if(retval != UA_STATUSCODE_GOOD)
        goto cleanup;

    retval = UA_Server_run(server, &running);

 cleanup:
    UA_Server_delete(server);
    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

The content of common.h is as follows, the function is to provide the function loadFile() to read the certificate and private key,

/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
 * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */

#include "open62541.h"


/* loadFile parses the certificate file.
 *
 * @param  path               specifies the file name given in argv[]
 * @return Returns the file content after parsing */
static UA_INLINE UA_ByteString loadFile(const char *const path) 
{
    
    
    UA_ByteString fileContents = UA_STRING_NULL;

    /* Open the file */
    FILE *fp = fopen(path, "rb");
    if(!fp) {
    
    
        errno = 0; /* We read errno also from the tcp layer... */
        return fileContents;
    }

    /* Get the file length, allocate the data and read */
    fseek(fp, 0, SEEK_END);
    fileContents.length = (size_t)ftell(fp);
    fileContents.data = (UA_Byte *)UA_malloc(fileContents.length * sizeof(UA_Byte));
    if(fileContents.data) {
    
    
        fseek(fp, 0, SEEK_SET);
        size_t read = fread(fileContents.data, sizeof(UA_Byte), fileContents.length, fp);
        if(read != fileContents.length)
            UA_ByteString_clear(&fileContents);
    } else {
    
    
        fileContents.length = 0;
    }
    fclose(fp);

    return fileContents;
}

Use cmake to compile, the overall project structure is as follows,
Insert picture description here
the certificate and private key in the certs directory are generated in the second section, and then copied, open62541.h and open62541.c are generated and copied in the first section. The files in the src directory belong to this section.

The content of CMakeLists.txt is as follows,

cmake_minimum_required(VERSION 3.5)

project(encryption_openssl)

set (EXECUTABLE_OUTPUT_PATH  ${PROJECT_SOURCE_DIR}/bin)

add_definitions(-std=c99)

include_directories(${PROJECT_SOURCE_DIR}/open62541)
include_directories(${PROJECT_SOURCE_DIR}/src)

find_package(OpenSSL REQUIRED)

add_executable(server ${PROJECT_SOURCE_DIR}/src/server.c ${PROJECT_SOURCE_DIR}/open62541/open62541.c)
#target_link_libraries(server crypto)
target_link_libraries(server ${OPENSSL_LIBRARIES})


add_executable(client ${PROJECT_SOURCE_DIR}/src/client.c ${PROJECT_SOURCE_DIR}/open62541/open62541.c)
#target_link_libraries(client crypto)
target_link_libraries(server ${OPENSSL_LIBRARIES})

The second parameter in target_link_libraries() is the OpenSSL library, which is found through find_package().

Finally, cd to the build directory and execute it cmake .. && make, and the two elf files, server and client, can be successfully generated.

Then cut to the bin directory, run the server first, and run the following command. The last parameter is the certificate trusted by the server, that is, the certificate of the client.

./server ../certs/server_cert.der ../certs/server_key.der ../certs/client_cert.der

Then run the client, the last parameter is the certificate trusted by the client, that is, the server's certificate,

./client opc.tcp://127.0.0.1:4840 ../certs/client_cert.der ../certs/client_key.der ../certs/server_cert.der

The function of the client is to obtain the system time of the server, which is printed as follows. Note that this is UTC time
Insert picture description here
. You can also see in the printed information that the 256Sha256 encryption strategy has been used.

2. UaExpert verification (operation under windows)

UaExpert is an OPC UA Client, which uses encrypted communication and also requires the use of certificates and private keys. After installing UaExpert for the first time, you will be asked to fill in some information. You can read this article . This information will be used to generate UaExpert's certificate and private key.

If a server trusts a certain client, it needs to know the client's certificate. If a client trusts a certain server, it needs to know the server's certificate. Where is the UaExpert certificate?

Open UaExpert, click Manage Certificates... under Settings, the
Insert picture description here
following interface will pop up, you can see the certificate of UaExpert, select it, and then click Open Certificate Location in the lower right corner,
Insert picture description here
but the trusted/certs directory is opened, and it is empty,
Insert picture description here
we return
Insert picture description here
Go to the upper level 2 directory to the PKI, then open the own/certs directory, you can see the UaExpert certificate,
Insert picture description here
put uaexpert.der in the certs directory under the project directory in the code verification in the previous section (you can use WinSCP to upload Linux virtual machine), and then re-run the server,

./server ../certs/server_cert.der ../certs/server_key.der ../certs/uaexpert.der

At the same time, transfer server_cert.der to the directory where UaExpert stores certificates (WinSCP can also be used), namely PKI/trusted/certs, and
Insert picture description here
finally use UaExpert to connect, opc.tcp://192.168.58.134:4840 under Custom Discovery You can see 7 endpoints.
Insert picture description here
Here, choose Basic256Sha256-Sign & Encrypt to connect. Finally, the connection is successful without any warnings or errors, as follows,
Insert picture description here


Four summary

This article describes how to use OpenSSL for encrypted communication. Now open62541 supports both mbedTLS and OpenSSL encryption, so users can choose according to their needs. OpenSSL can be used on the desktop and mbedTLS can be used on embedded devices, which is very convenient.

If there is something wrong with the writing, I hope to leave a message to correct it, thank you for reading.

Guess you like

Origin blog.csdn.net/whahu1989/article/details/107281639