概要
このシリーズでは、製品開発に関わるエンジニアが製品組み込みのためのセキュリティ機能に関するプログラミングを進めるにあたり、サンプルプログラムを通して必要な知識を具体的に理解することを目指します。
シリーズ第1回ではTLSプロトコルとプログラミングの概要、また第2回ではDTLSについてみてきました。第1回、第2回では読者が簡単に試してみることができるようにLinuxで動作するサンプルを使いました。今回はいよいよ組み込み向けMCUと開発環境でTLSのようなセキュリティプロトコルを動作させるにはどのようにしたら良いのか、具体的にみていくことにします。
説明では、Renesas社製評価ボード RX72N Envision Kit とIDE環境 e2Studioを使用しますが、できるだけその他の環境でもほぼ同様に動作させることができるように説明していきたいと思います。
また、組み込み向けTLSライブラリとしては wolfSSL を使って 実際のTLS 通信を見てみます。
サンプルプログラムの概要
この記事では、次の3つのサンプルプログラムを中心に説明します。これらは後述のサンプルプロジェクトに格納されていて、マクロの指定で機能を切り替えることが出来るようになっています。
1. Simple_tcp
このプロジェクトは、TLSによるセキュリティの無い簡単なTCP通信を行うクライアントとサーバーのサンプルプログラムです。このサンプルプログラムではRenesas t4tinyによるTCP通信を利用していますが、読者の手慣れているTCPプラットフォームでもほぼ同様のプログラムが動作するはずです。
2. Simple_tls
このプロジェクトは、TLSによるセキュアな通信を行うクライアントとサーバーのサンプルプログラムです。1. Simple_tcpとほぼ同じ通信をTLSレイヤーで行うので、両者を比較すれば、既存のTCPプログラムをTLSプログラム化するために必要なことを理解することができます。
3. Simple_tls_tsip
最近のハイエンドMCUでは暗号アクセラレータや特別なセキュリティ機能を搭載したものが多数あります。このプロジェクトではそうした機能の例としてRenesas RXファミリーに搭載されているTSIP (Trusted Secure IP Driver) を利用してTLSをさらに安全、高速化するための手法について見ていきます。
Renesas RX72Nサンプルプログラム
2.1 必要となるソフトウェア・ハードウェア
テストプログラムを動作させるために必要となるソフトウェア及びハードウェアは下記となります。
- e2studio Windows版
- wolfSSL
- Renesas RX72N Envision Kit
使用するサンプルプログラムは、最新版のwolfSSLに同封されています。wolfSSLは wolfSSL Githubサイトからcloneし入手します。
2.2 e2studioでプロジェクトを開く
さっそくプログラムを e2studio で開いてコンパイルしてみます。プロジェクトは基盤になる wolfSSLライブラリとサンプルプロジェクトの入った2つになります。
- インストール済みの e2studio を起動します。
- [ファイル・システムからプロジェクトを開く..]を選択し、インポート元に<wolfSSL Root Folder>\IDE\Renesas\e2studio\RX72N\EnvisionKit\Simple を指定します。フォルダー指定から[Simple]のチェックボックスを外し、[終了]ボタンをクリックします。
- [test]と[wolfssl]の二つのプロジェクトが開かれたことを確認します。
2.3 wolfSSL ライブラリの構成
wolfSSLプロジェクトのソースファイルは次の表のようなフォルダーで構成されています。
wolfSSL | ||
src | TLS/SSLプロトコル層のための処理 | |
wolfcrypt/src | 暗号アルゴリズム層のための処理 | |
wolfcrypt/srcport | 各社暗号エンジンドライバー port/Renesas: TSIPドライバのための処理 |
wolfSSL プロジェクトのヘッダーファイルは次の表のようなフォルダーで構成されています。
#include | TLS/SSLプロトコル層のためのAPIや構造体の定義 |
#include | 暗号アルゴリズム層のためのAPIや構造体の定義 |
2.4 サンプルプログラムの構成
2.4.1 user_settings.hヘッダーファイル
wolfSSL ライブラリの挙動の変更は、e2studio のようにIDEでconfigure コマンドが利用できない環境では user_settings.h というファイルを使用します。[test]プロジェクトに user_setting.hが存在します。このファイルでwolfSSLの機能の有効・無効を指定しています。このヘッダーファイルは wolfSSL ライブラリプロジェクトでもインクルードされています。例えば、
#define WOLFSSL_TLS13
は、TLS 1.3 のサポートを有効にしています。個々のオプションなど詳細については、ユーザマニュアル2.4 非標準環境でのビルドを参照ください。
https://wolfssl.jp/fwd_wolfssl-usermanual-jp-2019015/
2.4.2 デモプログラム機能の切り替え
デモプログラムは、<test>/src/wolfssl_simple_demo.h にあるマクロ定義の有効・無効でテストプログラムのタイプを切り替えるようになっています。マクロ名の指定によって、対応するソースファイルが有効化されます。次の表にマクロ定義名毎の機能を示します。以下では特に断りが無い限り、次表で示されるマクロ定義の1つが有効で他のマクロ定義は無効になっている状態でプログラムを実行します。
マクロ名 | ソースファイル名 [test プロジェクト]-src | 機能 |
---|---|---|
CRYPT_TEST | test/test.c | 暗号アルゴリズム毎のテスト |
BENCHMARK | test/benchmark.c | 暗号アルゴリズム毎のベンチマーク |
SIMPLE_TCP_CLIENT | client/simple_tcp_client.c | T4-Tiny を使った簡単なTCPクライアント |
SIMPLE_TLS_CLIENT | client/simple_tls_tsip_client.c | wolfSSLとT4-Tinyを使った簡単なTLSクライアント |
SIMPLE_TLS_TSIP_CLIENT | cclient/simple_tls_tsip_client.c | wolfSSL、T4-Tiny とTSIP を使った TLSクライアント |
SIMPLE_TCP_SERVER | server/simple_tcp_server.c | T4-Tiny を使った簡単なサーバー |
SIMPLE_TLS_SERVER | server/simple_tls_server.c | wolfSSLとT4-Tinyを使った簡単なTLSサーバー |
2.5 プロジェクトをコンパイルする
2.5.1 スマートコンフィグレータ
最初に必要なソースファイルをスマートコンフィグレータで生成します。生成されるコードは[wolfSSL]プロジェクトでも参照することから、最初にこのステップを行います。[test]プロジェクトに含まれるtest.cfgファイルをクリックし、スマートコンフィグレータを起動し[コンポーネント]タブを開きます。「コードの生成」ボタンをクリックしコードを生成します。必要なコンポーネントは登録されているので、単に「コード生成」を行うだけで必要なコードが生成されます。
2.5.2 wolfSSL をコンパイル
[wolfSSL]プロジェクトを選択し、右クリック→プロジェクトのビルドでwolfSSLをビルドします。
2.5.3 暗号アルゴリズムプログラムのコンパイル
Testプロジェクトは、<test>/src/wolfssl_simple_demo.h のマクロ名でデモプログラム機能を切り替えます。デフォルトでは、#define CRYPT_TEST が有効になっています。最初に暗号アルゴリズムのテストを行いたいので、#define CRYPT_TEST が有効になっていることを確認します。wolfSSLのビルドと同様に[test]プロジェクトを選択し、右クリック→プロジェクトのビルドから[test]プロジェクトをビルドします。
サンプルプログラムを実行してみる
3.1 wolfCrypt テスト実行
wolfCrypt はwolfSSLライブラリの基盤となっている暗号アリゴリズムのライブラリです。最初に wolfCrypt Test を実行し モジュール生成と、パソコンとMCUの接続が正しくおこなわれていること確認します。
3.1.1 プログラム実行
右クリック→デバック→Renesas GDB Hardware Debugging を選択しバイナリをMCUへダウンロードします。実行確認のために、Renesas Debug Virtual Console を開き出力を確認します。次のように出力されればOKです。
Start wolfCrypt Test ------------------------------------------------------------------------------ wolfSSL version 5.5.0 ------------------------------------------------------------------------------ error test passed! MEMORY test passed! base64 test passed! asn test passed! RANDOM test passed! MD5 test passed! SHA test passed! SHA-256 test passed! SHA-512 test passed! Hash test passed! … ECC buffer test passed! CURVE25519 test passed! logging test passed! time test passed! mutex test passed! memcb test passed! crypto callback test passed! Test complete End wolfCrypt Test
3.2 TCPクライアントプログラムの実行
暗号アルゴリズムテストが正常に終了したら、いよいよサンプルプログラムを実行してみます。まずは、セキュリティのないTCPだけのプログラムです。
3.2.1 マクロの切り替え
表1を参考に SIMPLE_TCP_CLIENT が有効になっていることを確認します。
3.2.2 対向サーバー
実際にサンプルクライアントでTCP通信を実行させるには通信相手となるサーバーが必要です。対向サーバーは手持ちのPCなど適当な環境で動作させます。
対向のサーバーには、こちらの ソースコードを利用します。コンパイルには、gccを利用し次のようにコンパイルします。
$ gcc server-tcp.c -o server-tcp
3.2.3 サンプルプログラムのビルドと実行
対向サーバーのIP アドレスと接続ポートを確認し、<test>/src/simple_tcp.c のSIMPLE_TLSSEVER_IPとSIMPLE_TLSSERVER_PORTを更新し、テストプログラムをビルドします。まず、サーバー側を起動し、次にクライアント側を起動します。
このとき、できれば wireshark を立ち上げて、パケットの様子も見てみましょう。後程、TLSの場合と比較することが出来ます。
Renesas Debug Virtual Consoleに
Received : I hear ya fa shizzle!
と表示されれば正しく動作しています。
サーバー側には
$ ./server-tcp Waiting for a connection... Client connected successfully Client: Hello Server
と表示されていると思います。
取得したWireshark のデータを覗いてみます。
TCPサーバー(192.168.10.10)からTCPクライアント(192.168.10.33)にASCIIデータが送信されていることが分かります。
3.3 TLSクライアントプログラムの実行
続いてセキュリティ機能を有効にしたTLSクライアントを実行します。
3.3.1 マクロの切り替え
表1を参考に SIMPLE_TLS_CLIENT が有効になっていることを確認します。
3.3.2 対向サーバー
対向サーバーにも wolfSSL を利用します。「 はじめてのTLS」の「3.3 コンパイル、実行させてみる」を参照しダウンロードします。configure コマンドは、次を指定しコンパイルします。
$ cd$ ./autogen.sh $ ./configure $ make
対向サーバーの実行には下記コマンドを使用しTLS1.3で通信します。
$ ./examples/server/server -v 4 -b -i -c ./certs/server-ecc.pem -k ./certs/ecc-key.pem -A ./certs/client-ecc-cert.pem
3.3.3 ビルドと実行
対向サーバーのIP アドレスと接続ポートを確認し、<test>/src/simple_tcp.c のSIMPLE_TLSSEVER_IPとSIMPLE_TLSSERVER_PORTを更新します。wolfSSL及びテストプログラム及びビルドします。wolfSSL も再コンパイルが必要になります。先ほどと同じくサーバー、クライアントの順に実行します。
Renesas Debug Virtual Consoleに
cipher : TLS13-AES128-GCM-SHA256 Received: I hear you fa shizzle!
と表示されれば正しく動作しています。
サーバー側には
SSL version is TLSv1.3 SSL cipher suite is TLS_AES_128_GCM_SHA256 SSL curve name is SECP256R1 Client message: Hello Server
と表示されます。
この時のパケットも覗いてみましょう。
Server Hello 以降のパケットはApplication Dataとして暗号化されていて解読できません。
3.4 TSIP TLS クライアントプログラムの実行
次にTSIP ドライバを使用したプログラムを実行します。TLSプロトコルによるセキュリティ機能を維持ししたままハンドシェークの実行速度が向上していることを確認します。
TSIP(Trusted Secure IP) は、暗号鍵をチップ固有の「ユニークID」と組み合わせて元の暗号鍵を見破ることができない「鍵生成情報」として使用します。これにより暗号鍵が盗まれた場合でも情報の流出を防ぐことができます。暗号エンジンには、AES-128, AES-256 等に対応し、真正乱数発生器も搭載します。
3.4.1 マクロの切り替え
表1を参考に SIMPLE_TLS_TSIP_CLIENT が有効になっていることを確認します。
3.4.2 対向サーバー
対向サーバーは、 3.3.2と同じものを使用します。対向サーバーの実行には3.3.2と同じコマンドを使用します。
3.4.3 ビルドと実行
対向サーバーのIP アドレスと接続ポートを確認し、<test>/src/simple_tcp.c のSIMPLE_TLSSEVER_IPとSIMPLE_TLSSERVER_PORTを更新しますwolfSSL及びテストプログラム及びビルドします。wolfSSL も再コンパイルが必要になります。先ほどと同じくサーバー、クライアントの順に実行します。
Renesas Debug Virtual Consoleには3.3.3と同じ内容が出力されます。
TSIPが有効である場合とそうでない場合を Wireshark でキャプチャした結果をもとにその効果を確認します。この場合、おおよそ半分程度にハンドシェークの時間が短縮されていることが分かります。この値は測定環境で異なります。また繰り返し測定するとばらつきがでる可能性があることに注意してください。
3.5 TCPサーバープログラムの実行
サンプルプロジェクトでは、サーバー機能についても同様にビルド・実行ができるようになっています。マクロの指定、ビルド、実行方法はクライアント側の説明を参照ください。
TCPソケットプログラムからTLSプログラムへの移行
ここからはTCP通信を行うプログラムをTLSに対応していきます。ソースコードを見るとマクロ定義やエラー処理などがあるため一見複雑に見えますが、俯瞰してみることで処理を単純化して説明していきます。
4.1 TCPクライアントプログラムとTLSクライアントプログラムの比較
始めに簡単なTCP通信を行うクライアントプログラムを見てみましょう。処理の流れを単純化すると 図2のような処理の流れになっています。実際のソースコードは、プロジェクト内のsimple_tcp.c を参照してください。
これをTLS通信に対応せるには次図に示されているように7つの処理(黄色枠)を追加します。実際のソースコードはプロジェクト内の simple_tls_tsip.c を参照ください。マクロ定義によるソースコードが無効になっている箇所を覗くと次の7つの処理が TCPプログラムに追加されています。
TLSライブラリのAPI関数など少し詳細を見ていきます。”simple_tls_tsip.c” の冒頭部分のインクルードディレクティブ部分で特徴的なのは下記です。
#include <wolfssl/ssl.h>
このヘッダーファイルにはTLSプログラムで使用するAPI関数、構造体の定義が含まれています。次に追加されたAPI関数について見ていきます。
ライブラリの初期化
TLSプログラムでは最初に下記関数を呼び出してライブラリを初期化する必要があります。
wolfSSL_Init()
TLSコンテクスト管理構造体の確保
TLS一連の接続処理(コンテクスト)を管理するための構造体の確保を行います。
client_ctx = wolfSSL_CTX_new(wolfSSLv23_client_method_ex((void *)NULL))
wolfSSL_CTX_new()関数を用いてコンテクストを確保する際の引数にはTLS接続時のプロトコルバージョンを指定します。”simple_tls_tsip.c”では、wolfSSLv23_client_method_exを用いています。この指定では、クライアント・サーバー間でサポートする最も高いバージョンでTLS接続を行います。例えば、サーバーがTLS1.3をサポートしている場合、クライアントは自身もサポートする最も高いバージョンTLS1.3でTLS通信を行います。
ピア認証のための準備
”simple_tls_tsip.c”では、下記関数を用いてサーバー認証のめのクライアント側でCA証明書をTLSコンテクストにロードしています。
wolfSSL_CTX_load_verify_buffer()
上記のAPI関数はファイルシステムが用意されていな場合などに用いるAPI関数で、これ以外にもファイルを指定しロードするAPI関数などがあります。
自ノード認証のための準備
先ほどの相手側(サーバー)認証に続いて、自身(クライアント)の認証のための準備を行います。”simple_tls_tsip.c”では、下記関数を用いてクライアント認証のめのクライアント証明書及び秘密鍵をTLSコンテクストへのロードを行います。
wolfSSL_CTX_use_certificate_chain_buffer_format() wolfSSL_CTX_use_PrivateKey_buffer()
”simple_tls_tsip.c”では、署名検証に用いる鍵のタイプがECC鍵かRSA鍵かでロードする証明書をマクロ定義、USE_ECC_CERTの有無で切り分けています。デフォルトでは、USE_ECC_CERTが有効になることに注意してください。
メッセージ送受信用のCallBack関数の登録
使用するTCP スタックによってはIOの処理に独自の処理が必要となります。その場合は、CallBack関数を登録しその関数内で独自の処理を行います。”simple_tls_tsip.c”内でT4-Tiny の各々の送受信関数は下記のCallBack関数で行います。
my_IORecv() my_IOSend()
これらの関数をTLSコンテクストに登録し、メッセージ送受信の際にライブラリより呼び出されるようにします。プログラム内では
wolfSSL_SetIORecv(client_ctx, my_IORecv); wolfSSL_SetIOSend(client_ctx, my_IOSend);
で登録を行っています。
TLSオブジェクト生成、CallBackコンテクストの登録、サーバーにTLS接続
ここからは1つのTLS接続を管理するためのTLS構造体を下記で生成します。
wolfSSL_new(ctx)
次にメッセージ送受信用のCallBack関数に渡すコンテクストを設定します。”simple_tls_tsip.c”では、T4-Tinyで利用するID情報を渡しています。
wolfSSL_SetIOReadCtx() wolfSSL_SetIOWriteCtx()
いよいよ、TLSサーバーへ接続します。下記関数内でTLSサーバーへの接続要求から一連のTLSハンドシェークを行っています。
wolfSSL_connect(ssl)
メッセージの送受信
メッセージの送受信は、
wolfSSL_write() wolfSSL_read()
で行います。Write関数は接続先へ暗号化されたアプリケーションメッセージを送信します。正常終了時には、指定したメッセージ長と同じ値を返します。Read関数は、接続先から指定された最大長以下のアプリケーションメッセージを受信しバッファーに復号後、格納します。正常終了時には、受信したメッセージのバイト数を返します。
接続の切断とリソースの解放
最後に使用したリソースを解放します。
wolfSSL_shutdown() wolfSSL_free() wolfSSL_CTX_free()
リソースを解放する際には、確保したときの逆の順番で解放します。
4.2 TLSクライアントプログラムとTLS+TSIPクライアントプログラムの比較
次にRenesas TSIP を有効にした際のプログラムについて見ていきます。TLSクライアントに 図4のように6つの処理(黄色枠)が追加されています。
署名済みCA証明書のロード
TSIPは、CA証明書に対して署名検証を行うことでCA証明書の真偽性を事前に検証します。そのため、RSA2048 PSS with SHA256 で署名されたCA証明書のデータを次のAPI関数で予めロードしておきます。
tsip_inform_cert_sign()
TSIP鍵のロード
上記署名された証明書の検証用の鍵もロードしCA証明書の検証時にTSIPドライバに通知します。そのため事前にTSIP鍵も事前にロードしておきます。
tsip_inform_user_keys_ex()
TSIP CallBack 関数をTLSコンテクストに登録
TLSハンドシェーク中にTSIPドライバ独自の処理を行うために、TLSコンテクストに呼び出してもらう必要があります。
tsip_set_callbacks()
引数に 確保したTLSコンテクストを引き渡します。API関数内部では、TLSコンテクストに対して、TSIPドライバ独自の処理を行うためにCallBack関数の登録を行います。TLSハンドシェーク中に適当なタイミングでそれらのCallBack関数が呼び出されます。
暗号スイートの選択
TSIPドライバでサポートされているTLS暗号スイートは下記になります。
TLS1.2:
TLS_RSA_WITH_AES_128_CBC_SHA
TLS_RSA_WITH_AES_256_CBC_SHA
TLS_RSA_WITH_AES_128_CBC_SHA256
TLS_RSA_WITH_AES_256_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
TLS1.3:
TLS_AES_128_GCM_SHA256
TLS_AES_128_CCM_SHA256
TSIPドライバを使用するようにこれらの暗号スイートを選択式に下記API関数で設定しTLS接続を行います。
wolfSSL_CTX_set_cipher_list()
EC曲線の選択
TLS1.3接続のために、TSIPドライバがサポートする SECP256R1を選択します。
wolfSSL_CTX_UseSupportedCurve()
最後にTLSサーバーへの接続前にTSIP用のCallBack関数間でデータを共有する管理構造体を下記API関数にて渡します。
tsip_set_callback_ctx()
4.3 TCPサーバープログラムとTLSサーバープログラム
続いてサーバープログラムの実装を見ていきます。API関数名の違いはありますが、ここまで見てきたクライアントと大きな違いはありません。
TLSコンテクスト管理構造体の確保とピア認証の準備
TLSコンテクスト管理構造体の確保には、クライアントと異なりサーバー用の wolfSSLv23_server_method_ex ()を指定します。
/* TLSコンテクストの確保 */ wolfSSL_CTX_new(wolfSSLv23_server_method_ex((void *)NULL))) /* サーバー証明書と秘密鍵のロード */ wolfSSL_CTX_use_certificate_buffer() wolfSSL_CTX_use_PrivateKey_buffer()
仮にこのサーバーと先ほどのクライアントを通信させると両者とも最も高いバージョンで接続することになります。またサーバーは自身の証明のためにサーバー証明書と秘密鍵をロードします。
TLS接続受付とメッセージの送受信
TLSサーバーでは、下記の関数を使ってTLS通信の接続要求を待ちます。
wolfSSL_accept()
メッセージの送受信にはクライアントと同じく
wolfSSL_write() wolfSSL_read()
を使用します。
まとめ
以上、組み込みシステムの環境下で利用するテストプログラムの実例をRenesas 社 RX72Nの例で紹介しました。wolfSSLのオープンソース版にはRenesas 社 e2studio 以外の多くの統合開発環境やMCUのサンプルプログラムが含まれています。これらのサンプルには暗号処理の基本的動作を確認するものから、実際のTLSクライアント・サーバープログラムまで多くの参考例が収められおり、評価段階から使用することができます。
組み込みでのwolfSSL使用をご計画の際には、商用版ライセンスへの移行が可能です。
今後発生する脆弱性対応などは、wolfSSLが提供するサポートパッケージに含まれますので、安心してビジネスユースでお使いいただけます。
wolfSSLは、wolfSSL Inc. が独自に開発し、権利関係の一切を有するTLSライブラリです。wolfSSLに関するご質問、お問い合わせは info@wolfssl.jp宛お送りください。