TCP SocketプログラムをSSL/TLSに

###wolfSSLサンプルコード：SSL/TLS編
###概要

BSD Socketを使ってC言語で記述されたTCPクライアントのプログラムをベースに[wolfSSL](https://www.wolfssl.jp/products/wolfssl)を使ってTLSクライアント化するための追加、修正点をみていきます。次に、そのTLSクライアントプログラムを実際にコンパイル、実行させてその動作をみてみます。

以下の説明では、Linux, MacOS, WidnowsのWSL, CygwinやminGWなどのコマンド環境を想定しています。wolfSSLライブラリーの入手、インストールは[wolfSSLのクイックスタート(コマンド編)](https://qiita.com/kojo1/items/3140b107fd7f0ccdf061)を参照してください。

###1. サンプルコードを入手する

wolfSSLのGithubからサンプルコード一式をダウンロードし、TLS関連のサンプルのある wolfssl-examples/tls の下に移動します。

```
$ git clone https://github.com/wolfssl/wolfssl-examples
$ cd wolfssl-examples/tls
$ ls
Makefile			client-tls-ecdhe.c		client-tls.c			server-tls-epoll-threaded.c
README.md			client-tls-nonblocking.c	memory-tls.c			server-tls-nonblocking.c
client-tcp.c			client-tls-perf.c		server-tcp.c			server-tls-threaded.c
client-tls-bio.c		client-tls-pkcs12.c		server-tls-callback.c		server-tls-verifycallback.c
client-tls-cacb.c		client-tls-resume.c		server-tls-ecdhe.c		server-tls.c
client-tls-callback.c		client-tls-writedup.c		server-tls-epoll-perf.c
```


###2. TCP Socketクライアント

まずは出発点となるBSDソケットを使ったTCPクライアントのソースコード client-tcp.c を概観してみます。このプログラムでは、main関数の中でサーバへの接続、１往復のメッセージ送・受信を実行します。処理の流れは次の通りです。

1) socketを一つ確保
2) 接続先サーバのIPアドレス、ポートを設定し、connectにてTCP接続
3) 標準入力からのメッセージをサーバに送信
4) サーバからのメッセージを受信し、標準出力にプリント
5) リソースの開放

```
int main(int argc, char** argv)
{
    ...

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        fprintf(stderr, "ERROR: failed to create the socket\n");
        return -1;
    }
    ...
    servAddr.sin_family = AF_INET;             /* using IPv4      */
    servAddr.sin_port   = htons(DEFAULT_PORT); /* on DEFAULT_PORT */
   if (inet_pton(AF_INET, argv[1], &servAddr.sin_addr) != 1) {
        fprintf(stderr, "ERROR: invalid address\n");
        return -1;
    }
    if (connect(sockfd, (struct sockaddr*) &servAddr, sizeof(servAddr))
        == -1) {
        fprintf(stderr, "ERROR: failed to connect\n");
        return -1;
    }

    fgets(buff, sizeof(buff), stdin);
    if (write(sockfd, buff, len) != len) {
        fprintf(stderr, "ERROR: failed to write\n");
        return -1;
    }
    ...

    if (read(sockfd, buff, sizeof(buff)-1) == -1) {
        fprintf(stderr, "ERROR: failed to read\n");
        return -1;
    }
    printf("Server: %s\n", buff);

    close(sockfd); 
}
```

###3. SSL/TLS化する

2のプログラムをTLS通信化するには、以下の手順で処理を追加、変更します。完成したプログラムは client-tls.c にあります。

1) ヘッダーファイルとライブラリ初期化
wolfSSLのビルドオプションの格納されたヘッダーファイル wolfssl/options.h と wolfSSLの定義ヘッダーファイル wolfssl/ssl.h をインクルードします。mainの冒頭でライブラリの初期化関数 wolfSSL_Init()を呼び出します。

```
#include <wolfssl/options.h>
#include <wolfssl/ssl.h>

int main(int argc, char** argv)
{
    wolfSSL_Init();
```

TLSを使用時の動的パラメータをおさえておくためのコンテクスト WOLFSSL_CTX を一つ確保します。


```
    WOLFSSL_CTX* ctx;
...
    if ((ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())) == NULL) {
        fprintf(stderr, "ERROR: failed to create WOLFSSL_CTX\n");
        return -1;
    }
```

第一アーギュメントで接続に使用するTLSプロトコルのバージョンを指定します。wolfSSLv23_client_method を指定するとクライアント、サーバ間で合意できる最新のプロトコルで接続します。

|バージョン| API名 |
|---|---|
|TLS 1.1|wolfTLSv1_1_client_method|
|TLS 1.2|wolfTLSv1_2_client_method|
|TLS 1.3|wolfTLSv1_3_client_method|
|最新バージョン|wolfSSLv23_client_method|


確保したコンテクストにサーバ認証のためのPEM形式のCA証明書を登録します。一つの証明書ファイルのみの場合はファイルパスを第二アーギュメントに、複数の証明書ファイルの場合は格納しているディレクトリパスを第三アーギュメントに指定します。使用しないアーギュメントはNULLとします。

```

#define CERT_FILE "../certs/ca-cert.pem"
...

    if (wolfSSL_CTX_load_verify_locations(ctx, CERT_FILE, NULL)
        != SSL_SUCCESS) {
        fprintf(stderr, "ERROR: failed to load %s, please check the file.\n",
                CERT_FILE);
        return -1;
    }
```

クライアント認証を行いたい場合は、次のようにコンテクストに証明書、秘密鍵ファイルを登録します。

```
#define CLI_CERT         "../certs/client-cert.pem"
#define CLI_KEY          "../certs/client-key.pem"

    ...

    if (wolfSSL_CTX_use_certificate_chain_file(ctx, CLI_CERT) != SSL_SUCCESS) {
        printf("Error loading %s. Please check the file.\n", cert);
        return EXIT_FAILURE;
    }

    if (wolfSSL_CTX_use_PrivateKey_file(ctx, CLI_KEY, SSL_FILETYPE_PEM)
                                     != SSL_SUCCESS) {
        printf("Error loading %s. Please check the file.\n", key);
        return EXIT_FAILURE;
    }
```

次に、TLS接続のためのディスクリプタを一つ確保し、確保してあるソケットを登録します。wolfSSLのライブラリがハンドシェークなどのTCP通信を行う時にはこのソケットが使用されます。

```
   WOLFSSL*     ssl;

    if ((ssl = wolfSSL_new(ctx)) == NULL) {
        fprintf(stderr, "ERROR: failed to create WOLFSSL object\n");
        return -1;
    }

    wolfSSL_set_fd(ssl, sockfd);
```

wolfSSL_connectにて、TCP接続してあるサーバに対してTLS接続のためのハンドシェークを行ます。

```
   if (wolfSSL_connect(ssl) != SSL_SUCCESS) {
        fprintf(stderr, "ERROR: failed to connect to wolfSSL\n");
        return -1;
    }
```

TCP write, read関数はそれぞれwolfSSL_write, wolfSSL_readに変更し、TLSレイヤーのメッセージ通信を行うようにします。

```
    if (wolfSSL_write(ssl, buff, len) != len) {
    ...

    if (wolfSSL_read(ssl, buff, sizeof(buff)-1) == -1) {
```

通信が終わったら資源を開放します。

```
    wolfSSL_free(ssl); 
    wolfSSL_CTX_free(ctx); 
    wolfSSL_Cleanup(); 
    close(sockfd); 
```

###4. コンパイルする

ここでは、すでにwolfSSLライブラリがビルド、インストールされている前提で説明します。wolfSSLライブラリのビルド、インストールについては[wolfSSLクイックスタート](https://qiita.com/kojo1/items/3140b107fd7f0ccdf061)を参照してください。

以下のようにインクルードパスとライブラリを指定してgccコマンドでコンパイルします。

```
$ cd wolfssl-examples/tls
$ gcc -o client-tls client-tls.c -I/usr/local/include -Os -L/usr/local/lib -lm -lwolfssl
```

wolfssl-examples/tlsディレクトリの下でmakeコマンドを発行してディレクトリ下のサンプルプログラム一式をいっぺんにコンパイルすることもできます。

```
$ make
gcc -o client-tcp client-tcp.c -Wall -I/usr/local/include -Os 
gcc -o client-tls-bio client-tls-bio.c -Wall -I/usr/local/include -Os -L/usr/local/lib -lm -lwolfssl
...

gcc -o server-tls server-tls.c -Wall -I/usr/local/include -Os -L/usr/local/lib -lm -lwolfssl
```

###5. 実行させてみる


wolfSSLクイックスタートのビルドディレクトリの方に移動し、サンプルサーバを起動します。サンプルサーバのデフォルトではクライアント認証有となってしまうので、先ほどのクライアント認証用ファイル登録のコードを追加していない場合は、"-d"オプションでクライアント認証無を指定します。

```
$ ./examples/server/server -d
```

クライアント用に別のウインドウを開き、先ほどコンパイルしたTLSクライアントのサンプルプログラムを起動します。メッセージを入力すると、それが送信されサーバからの応答メッセージが出力されます。

```
$ ./client-tls 127.0.0.1
Message for server: hello server
Server: I hear you fa shizzle!
```

サーバ側ウィンドウには使用された暗号化スイートなどの情報とクライアントから送られてきたメッセージが表示されます。

```
$ ./examples/server/server -d
SSL version is TLSv1.2
SSL cipher suite is TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
SSL curve name is SECP256R1
Client message: hello server
```

この時、WiresharkなどでパケットキャプチャーするとTLSパンドシェークとメッセージの送・受信の様子をみることができます。

![sampleTLSclient.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263939/6b6e0026-a989-0c65-525e-b6315daf44a0.jpeg)


###6. まとめ
今回は、wolfSSLを使ったTLSプログラミングについて簡単にまとめてみました。詳細なドキュメント、製品情報については、wolfSSLの下記を参照してください。wolfSSLは、ソフトウェアの内容は同じですが無償のオープンソース版と有償商用版のデュアルライセンスとなっています。製品への組み込みの際は商用ライセンス契約が必要です。

この記事の内容を含めてTLプログラミングについて解説をまとめる機会をいただきました。興味のあるかたはご覧ください。[徹底解剖 TLS 1.3](https://www.amazon.co.jp/%E5%BE%B9%E5%BA%95%E8%A7%A3%E5%89%96-TLS-1-3-%E5%8F%A4%E5%9F%8E-%E9%9A%86/dp/4798171417/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=1WX07IBUINBBR&keywords=%E7%BF%94%E6%B3%B3%E7%A4%BE+TLS&qid=1643343354&sprefix=%E7%BF%94%E6%B3%B3%E7%A4%BE+tls%2Caps%2C177&sr=8-1) (翔泳社から 3/7刊行)。

ドキュメント：https://www.wolfssl.jp/docs/
英語サイト：https://www.wolfssl.com
日本語サイト：https://www.wolfssl.jp
Twitter: https://twitter.com/wolfSSL_Japan