学习open62541 — [12] 加密(使用mbedTLS)

学习open62541 — [12] 加密(使用mbedTLS)使用mbedTLS进行加密通信。

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

本文主要讲述OPC UA Client和OPC UA Server之间如何加密通信,这是个很重要的功能。在之前的系列文章里,都没有使用加密,比较明显的是Server启动时的打印,如下,
在这里插入图片描述
提示没有安全策略,可能会造成泄密。

另外,在使用UaExpert连接Server时,Server左侧图标是个红色的锁,而且这个锁是打开的,表示没有安全策略。
在这里插入图片描述
以上这些都表示未加密。

本文通过mbedTLS实现加密通信,环境如下,

  • OS:Debian10,Ubuntu也是一样
  • open62541版本:v1.1.6
  • mbedTLS版本:2.26.0

open62541在v1.1.1版本之后开始支持OpenSSL加密,可以参考这篇文章,OpenSSL在桌面端应用广泛,mbedTLS主要用在嵌入式领域,桌面端也可以使用。


一 生成自签名证书和私匙

想实现加密通信,就需要自签名证书和私匙,所以首先要生成它们。如果了解HTTPS的话就比较好理解,如果不了解也没问题,按照操作做就可以了。

使用open62541自带工具生成

在open62541源码根目录下的tools/certs目录里,有2个文件,
在这里插入图片描述
可以使用这个python脚本去生成证书和私匙,但需要预先安装一个python模块 — netifaces,输入以下命令安装,

pip3 install netifaces

Jetbrains全家桶1年46,售后保障稳定

PS:使用pip3是因为我们会使用python3去运行这个脚本。

安装完netifaces后,我们先看下这个脚本的帮助信息,输入以下命令,

python3 create_self-signed.py  -h

帮助信息如下,
在这里插入图片描述
其中-u是最重要的参数,用于指定证书的URI值,这个值后面会讲到,代码里需要设置成相同值。

验证加密通信需要server和client,它们都需要证书和私匙。首先生成server的证书,执行下面的命令去生成,

python3 create_self-signed.py ./ -u urn:open62541.server.application -c server

接着生成client的证书,

python3 create_self-signed.py ./ -u urn:open62541.client.application -c client

这样就会在当前目录下生成server_cert.der,server_key.der,client_cert.der,client_key.der这4个文件
在这里插入图片描述

可以使用如下命令来查看server证书的URI参数,

openssl x509 -in server_cert.der -inform der -noout -text

如下,
在这里插入图片描述

URI值和前面-u参数后的值是一样的。同理,可以查看client证书里URI值。

最后创建工程目录,然后把证书拷贝出来,
在这里插入图片描述
encryption_mbedtls即使我们的工程目录,读者可以根据需求随意创建。


二 编译mbedTLS

实现加密功能需要依赖mbedTLS库,这个库可以给软件产品加入加密和 SSL/TLS 功能,在嵌入式领域用的比较多。官网是https://tls.mbed.org/,可以去网站上下载,本文使用版本为2.26.0

下载下来后,cd到其源码目录,然后按如下步骤操作,

  • 新建build目录并cd进入
  • 执行cmake .. && make
  • 源码目录下的include目录和build目录下的library目录拷贝出来(build目录下也有include目录,里面是符号链接,所以不需要)
  • 清理library目录,只保留三个库文件,最后结构如下,
    在这里插入图片描述

三 编译open62541

1. 配置

在open62541源码目录下的CMakeLists.txt里找到以下4个option,

  • UA_ENABLE_AMALGAMATION
  • UA_ENABLE_ENCRYPTION
  • UA_ENABLE_ENCRYPTION_OPENSSL
  • UA_ENABLE_ENCRYPTION_MBEDTLS

把第1,2和4改为ON,第3改为OFF

2. 查找mbedTLS

打开open62541源码目录下的tools/cmake/FindMbedTLS.cmake,有如下内容,

#check environment variable
if("$ENV{MBEDTLS_FOLDER_INCLUDE}")
    set(MBEDTLS_FOLDER_INCLUDE "$ENV{MBEDTLS_FOLDER_INCLUDE}")
endif()
if("$ENV{MBEDTLS_FOLDER_LIBRARY}")
    set(MBEDTLS_FOLDER_LIBRARY "$ENV{MBEDTLS_FOLDER_LIBRARY}")
endif()

CMake变量MBEDTLS_FOLDER_INCLUDE和MBEDTLS_FOLDER_LIBRARY分别用于存放mbedTLS的头文件和库文件。这些语句的意思是从环境变量里查找mbedTLS,由于我们是单独编译的,并没有在环境变量里设置其位置,所以需要进行修改。如果不修改的话,可以参考这篇文章

把上节中mbedTLS的存放目录添加进来就行了,如下,

#check environment variable
if("$ENV{MBEDTLS_FOLDER_INCLUDE}")
    set(MBEDTLS_FOLDER_INCLUDE "$ENV{MBEDTLS_FOLDER_INCLUDE}")
else()
    set(MBEDTLS_FOLDER_INCLUDE "/home/wh/work/opcua/encryption_mbedtls/mbedtls/include")
endif()
if("$ENV{MBEDTLS_FOLDER_LIBRARY}")
    set(MBEDTLS_FOLDER_LIBRARY "$ENV{MBEDTLS_FOLDER_LIBRARY}")
else()
    set(MBEDTLS_FOLDER_LIBRARY "/home/wh/work/opcua/encryption_mbedtls/mbedtls/library")
endif()

这里使用的是绝对路径,也可以使用相对路径,简单测试一下就行了。

当然,也可以不修改FindMbedTLS.cmake,在执行cmake命令时指定一下这2个变量的值就行了,下一步会讲。

3. 编译

按如下步骤操作,

  1. cd到open62541源码目录下,新建build目录并cd进入
  2. 执行cmake .. && make
  3. 把open62541.h和bin下的libopen62541.a拷贝到工程目录下的open62541目录里,
    在这里插入图片描述

如果第2步没有修改FindMbedTLS.cmake,那么执行cmake命令时就要如下这样,

cmake -DMBEDTLS_FOLDER_INCLUDE=/home/wh/work/opcua/encryption_mbedtls/mbedtls/include -DMBEDTLS_FOLDER_LIBRARY=/home/wh/work/opcua/encryption_mbedtls/mbedtls/library  .. && make

四 验证加密通信

这里使用代码和UaExpert来验证加密通信。

1. 使用代码验证

这里的测试代码使用open62541自带的example代码,但是有点小坑需要填一下。

client.c代码如下,配置URI的地方是新加的(第47~48行),要和证书里的URI值一样

/* 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];

    /* Load certificate and private key */
    UA_ByteString certificate = loadFile(argv[2]);
    UA_ByteString privateKey  = loadFile(argv[3]);

    /* Load the trustList. Load revocationList is not supported now */
    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#Basic128Rsa15");
    UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey,
                                         trustList, trustListSize,
                                         revocationList, revocationListSize);
    
    // 填坑的地方,非常重要
    UA_String_deleteMembers(&cc->clientDescription.applicationUri);
    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;
}

server.c代码如下,配置URI的地方是新加的(第64~71行),要和证书里的URI值一样

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

    /* Load certificate and private key */
    UA_ByteString certificate = loadFile(argv[1]);
    UA_ByteString privateKey  = loadFile(argv[2]);

    /* Load the 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);
      
    // 填坑的地方,非常重要
    UA_String_deleteMembers(&config->applicationDescription.applicationUri);                                                  
    config->applicationDescription.applicationUri = UA_STRING_ALLOC("urn:open62541.server.application");
    for (size_t i = 0; i < config->endpointsSize; ++i)
    { 
   
        UA_String_deleteMembers(&config->endpoints[i].server.applicationUri);
        config->endpoints[i].server.applicationUri = UA_String_fromChars("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;
}

common.h代码如下,

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

这三个文件拷贝到test目录下的src目录里,
在这里插入图片描述

在test目录下生成CMakeLists.txt并新建bin和build目录,CMakeLists.txt内容如下,

cmake_minimum_required(VERSION 3.5)

project(demoOpen62541)

set (EXECUTABLE_OUTPUT_PATH  ${PROJECT_SOURCE_DIR}/bin)

add_definitions(-std=c99)

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

find_library(OPEN62541_LIB libopen62541.a HINTS ${PROJECT_SOURCE_DIR}/open62541)
find_library(MBEDCRYPTO_LIB libmbedcrypto.a HINTS ${PROJECT_SOURCE_DIR}/mbedtls/library)
find_library(MBEDTLS_LIB libmbedtls.a HINTS ${PROJECT_SOURCE_DIR}/mbedtls/library)
find_library(MBEDX509_LIB libmbedx509.a HINTS ${PROJECT_SOURCE_DIR}/mbedtls/library)

add_executable(server ${PROJECT_SOURCE_DIR}/src/server.c)
target_link_libraries(server ${OPEN62541_LIB}  ${MBEDCRYPTO_LIB } ${MBEDTLS_LIB} ${MBEDX509_LIB})

add_executable(client ${PROJECT_SOURCE_DIR}/src/client.c)
target_link_libraries(client ${OPEN62541_LIB}  ${MBEDCRYPTO_LIB } ${MBEDTLS_LIB} ${MBEDX509_LIB})

最后整体工程结构如下,
在这里插入图片描述

cd到build目录下执行cmake .. && make,最后在bin目录下成功生成client和server,
在这里插入图片描述
先运行server,

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

再运行client,

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

这个client的功能就是获取系统时间,最后client端打印时间如下,说明运行成功
在这里插入图片描述
从client端的打印可以看出加密方式是Basic128Rsa15,和代码中选择的是一样的。
在这里插入图片描述
这里再简要说下安全策略的选择,在client端的配置结构体里有4个元素是专门用来设置安全策略的,

typedef struct { 
   
	// ... ...
	
    UA_MessageSecurityMode securityMode;  /* None, Sign, SignAndEncrypt. The * default is invalid. This indicates * the client to select any matching * endpoint. */
    UA_String securityPolicyUri; /* SecurityPolicy for the SecureChannel. An * empty string indicates the client to select * any matching SecurityPolicy. */
	
	// ... ...
	
	/* Available SecurityPolicies */
    size_t securityPoliciesSize;
    UA_SecurityPolicy *securityPolicies;

	// ... ...
} UA_ClientConfig;

securityMode元素有以下选择,可以选择不加密 or 签名 or 签名+加密,

/** * MessageSecurityMode * ^^^^^^^^^^^^^^^^^^^ * The type of security to use on a message. */
typedef enum { 
   
    UA_MESSAGESECURITYMODE_INVALID = 0,
    UA_MESSAGESECURITYMODE_NONE = 1,
    UA_MESSAGESECURITYMODE_SIGN = 2,
    UA_MESSAGESECURITYMODE_SIGNANDENCRYPT = 3,
    __UA_MESSAGESECURITYMODE_FORCE32BIT = 0x7fffffff
} UA_MessageSecurityMode;

securityPolicyUri有以下三个选择,表示加密方式有三种:Basic128Rsa15,Basic256和Basic256Sha256

UA_STRING_ALLOC("http://opcfoundation.org/UA/SecurityPolicy#Basic128Rsa15");
UA_STRING_ALLOC("http://opcfoundation.org/UA/SecurityPolicy#Basic256");
UA_STRING_ALLOC("http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256");

而对于securityPoliciesSize和securityPolicies,则是由函数UA_ClientConfig_setDefaultEncryption()根据安全证书里的内容对其进行填充,细节可以看该函数的源码。

2. 使用UaExpert验证

先按照上面那样把OPC UA Server运行起来,

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

然后打开UaExpert,使用UaExpert进行连接的操作方法可以参照这篇文章,这个时候我们可以看到有7个endpoint,
在这里插入图片描述
红色那把锁就是我们之前一直使用的。笔图案的endpoint是指只有签名功能,闭合的锁表示既有签名又有加密,加密方式有三种:Basic128Rsa15,Basic256和Basic256Sha256。

绿色的Basic256Sha256的安全性最高,这里就连接它。双击这个endpoint,弹出如下界面
在这里插入图片描述
点击红圈中的连接按钮,会弹出如下证书验证界面,
在这里插入图片描述
点击Trust Server Certificate,这样UaExpert验证证书就会通过,并信任这个证书,如下,
在这里插入图片描述
最后,点击右下的Continue按钮进行连接,连接成功,如下图
在这里插入图片描述
UaExpert信任的证书可以在Settings->Manage Certificates…里查看,
在这里插入图片描述
在这里插入图片描述


五 总结

本文主要讲述如何OPC UA Client和OPC UA Server之间如何加密通信,这是个非常重要的功能,而且过程也相对复杂一些,但掌握了这个功能,就可以增加软件产品的安全性。

主要核心参数是证书里的URI,只有当Client端拿到正确的证书才可以和Server建立通信,否则就会被Server拒绝。

如果有写的不对的地方,希望能留言指正,谢谢阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/230741.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 如何搭建传奇服务器_架设传奇需要什么样的服务器

    如何搭建传奇服务器_架设传奇需要什么样的服务器分享一篇技术文章,传奇私服架设教程。教程讲的很详细,就是一个菜鸟都能学会如何架设传奇私服。在管理工具->服务中停止ssdpdiscoveryservic服务一、准备软件DBCommander2000Pro和传奇服务端(什么版本都行)。二、简易安装说明:1、安装设置DBCommander2000Pro1)安装DBCommander2000Pro2)打开控制面板3)打开BDEA…

    2022年10月6日
    3
  • 51单片机毕业设计题目_51单片机经典项目

    51单片机毕业设计题目_51单片机经典项目STC12C5A60S2DS12887单片机毕业设计51单片机项目数码管显示的合成出租车计价器设计Synthetictaximeterdigitaldisplaydesign学生姓名: 学生学号: 10700121专业名称: 电子信息工程指导教师:计算机与信息工程学院2014年6月13日独创性声明本人声明所呈交的毕业设计(论文)是本人在指导教师指导下进行的研究工作和取得的研究成果,除了文中特别加以引用标注之处外,论文中不包含其他人已经发表或撰写过的研究成果,没有伪造数据的

    2022年10月3日
    4
  • [POJ 2976]Dropping tests(0-1分数规划)

    [POJ 2976]Dropping tests(0-1分数规划)

    2022年2月1日
    51
  • matlab中wavedec2函数,[转载]小波滤波器–wavedec2函数

    matlab中wavedec2函数,[转载]小波滤波器–wavedec2函数wavedec2函数:1.功能:实现图像(即二维信号)的多层分解.多层,即多尺度.2.格式:[c,s]=wavedec2(X,N,’wname’)[c,s]=wavedec2(X,N,Lo_D,Hi_D)(我不讨论它)3.参数说明:对图像X用wname小波基函数实现N层分解,这里的小波基函数应该根据实际情况选择,具体办法可以:db1、db2、……db45、haar.输出为c,s.c为各层分…

    2022年6月16日
    76
  • 谷歌浏览器搜索使用指南[通俗易懂]

    谷歌浏览器搜索使用指南[通俗易懂]谷歌浏览器使用指南下载谷歌浏览器使用浏览器时遇到的问题对谷歌浏览器进行配置下载谷歌浏览器可以在谷歌浏览器官网进行下载,网址:https://www.google.cn/intl/zh-CN/chrome/在腾讯电脑管家,进行下载使用浏览器时遇到的问题搜索引擎我们这里先选用百度搜索引擎,如果想使用其他搜索引擎,更换其他的网址即可。1.当你使用谷歌浏览器时你会发现,根本上不了网2.这个时候你不要惊慌,自己在网址框中手动输入:https://www.baidu.com/即可进行上网对谷

    2025年10月20日
    5
  • WPF 实现测量显示文本长度

    WPF 实现测量显示文本长度

    2021年6月14日
    172

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号