LoginSignup
1
1

More than 1 year has passed since last update.

open62541 について

Last updated at Posted at 2022-08-01

証明書について

open62541 下のtools/certs を使用して証明書を作成して確認していきます。

セキュリティ方式と証明書

 セキュリティモードやセキュリティモードがNone以外で通信する場合は、相互にアプリケーション証明書を交換しておく必要があります。
 OPCサーバー側の信頼リストにはOPCクライアントのアプリケーション証明書が、OPCクライアント側の信頼リストにはOPCサーバーのアプリケーション証明書が格納されていることを確認してください。
 アプリケーション証明書の交換を行わずに通信を行うと、互いのアプリケーション証明書は拒否リストに格納されます。拒否リストから信頼リストに移動させてください。

証明書と秘密鍵を生成します。

# デフォルトのパラメータでServerの証明書と秘密鍵を生成します。
python3 create_self-signed.py .  
# カスタムURIのパラメータでクライアントの証明書と秘密鍵を生成します。
python3 create_self-signed.py -u urn:open62541.client.application -c client .

中身を確認。

# サーバー証明書
openssl x509 -in server_cert.der -inform der -noout -text
# クライアント証明書
openssl x509 -in client_cert.der -inform der -noout -text

ソースコードで確認

Examplesを編集し、ClientとServerを作成し確認。
ここにない関数はExamplesで探してください。

Client

重要な点は”UA_SecurityPolicy_None”と"cc->clientDescription.applicationUri”。
暗号化は"cc->securityPolicyUri"で指定されている256Sha256を使用します。

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

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

Server

URIを構成する場所はconfig->applicationDescription.applicationUriです。
これは、サーバー証明書の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;
    }

    /* サーバーの証明書と秘密鍵をロードする */
    UA_ByteString certificate = loadFile(argv[1]);
    UA_ByteString privateKey  = loadFile(argv[2]);

    /* 信頼リストをロード */
    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;
}

確認

サーバを起動して、クライアントで確認します。

$~ ./server server_cert.der server_key.der client_cert.der
$~ ./client opc.tcp://127.0.0.1:4840 client_cert.der client_key.der /server_cert.der

証明書ファイルがPEMの場合

この UA_ClientConfig_setDefaultEncryption 関数を使用すると

ua_openssl_basic128rsa15.c
ua_openssl_basic256.c
ua_openssl_basic256sha256.c

の中では、

UA_OpenSSL_LoadLocalCertificate
UA_OpenSSL_LoadCertificateからUA_OpenSSL_LoadDerCertificate、PEM_read_bio_X509が呼ばれる。UI_process、read_stringが呼ばれ、コンソールでパスワードを入力するような処理(ReadConsoleA)が呼ばれる 。ただしコンソールアプリ以外は、コンソールが出ないのでハングアップする可能性あり。

UA_Policy_New_Context

では同様にUA_OpenSSL_LoadPrivateKeyが呼ばれる。その中でPEM_read_bio_PrivateKeyが呼ばれるがコールバック関数指定がNULLのため、コンソールでパスワードを入力するような処理(ReadConsoleA)が呼ばれる 。ただしコンソールアプリ以外は、コンソールが出ないのでハングアップする可能性あり。

image.png

参考。
https://www.codetd.com/ja/article/12511112

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1