サーバ認証のための証明書をクライアントに組み込む

**wolfTips: IoTデバイスのセキュリティライブラリ wolfSSL を使いこなすためのヒント集**
　　　　　　　　　　　　　　**--- 鍵、証明書編 ---**

##概要

[ブラウザを使ってCA証明書を持ってくる](https://qiita.com/kojo1/items/9fb7e0f6e2eebef935d5)で証明書をブラウザを使って簡易に持ってくる方法について解説し、持ってきた証明書を wolfSSL Example クライアントプログラムやOpenSSL を使ってサーバー認証を行いました。[wolfSSLの相互認証API](https://qiita.com/kojo1/items/a8d02371964fef04a454)では相互認証用APIの種類について見ました。

今回は、wolfSSLを使用しクライアントプログラムを作成する前提で、証明書組み込みAPIの使用方法について見てみます。IoTデバイスによっては、ファイルシステムを利用できる場合、利用できない場合があります。その各々について証明書の組み込みためのAPI使用法を解説します。

その他、wolfSSL の使用方法については下記記事を参照ください。
- [wolfSSLのクイックスタート(コマンド編)](https://qiita.com/kojo1/items/3140b107fd7f0ccdf061)
- [TCP SocketプログラムをSSL/TLSに](https://qiita.com/kojo1/items/ae434073567a6a08d972)

##補足 2020.09.20
サンプルのコードを GitHub から取得するように変更しました。

##ファイルシステムが利用できる場合
ファイルシステムが利用できる場合、wolfSSL_CTX_load_verify_locations() 又は wolfSSL_CTX_load_verify_locations_ex() API を使用します。wolfSSL API の中で、"_ex()" がついたAPIは、元々あったAPIの引数が拡張された場合に使用されます。

関数はPEM形式のルートCA証明をSSLコンテクストにロードします。ロードされた証明書はwolfSSL内で信頼されたルートCA証明書として取り扱われ、SSL/TLSハンドシェーク中に対向の証明書検証に使用されます。引数 file にはロードするファイルを指定します。引数 path には、ロードしたい一つ以上のルートCA証明書を配置したフォルダーのパスを指定します。指定されたフォルダー中の全てのCA証明書をロードします。


```
int wolfSSL_CTX_load_verify_locations(WOLFSSL_CTX * ctx, const char *file, const char *path)

int wolfSSL_CTX_load_verify_locations_ex(WOLFSSL_CTX * ctx, const char *file, const char *path, unsigned int flags )
```
**ctx**  : wolfSSL_CTX_new()で作成した、SSLコンテクストのポインタ
**file** : PEM形式のCA証明書を含むファイル名へのポインタ
**path** : PEM形式のCA証明書を１つ以上含むフォルダーのパス
**flags** : 次の論理和
*WOLFSSL_LOAD_FLAG_IGNORE_ERR* : 全てのエラーを無視。ただし、指定したフォルダーが存在しない、又は一つもファイルが読み込まれなかった場合はエラーとなります。
*WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY*：証明書のロード時の日付に関するエラーを無視します。
*WOLFSSL_LOAD_FLAG_PEM_CA_ONLY*：ファイルが .pem 拡張子を持たない、又は“BEGIN CERTIFICATE” ヘッダーを持たない場合でもエラーを無視します。

##ファイルシステムが利用できない場合
ファイルシステムが利用できない場合、wolfSSL_CTX_load_verify_buffer () API を使用します。この関数もルートCA証明書をSSLコンテクストにロードします。ファイル読み込みの関数との違いは、ファイルの代わりにバッファを引数として取るところです。バッファは、そのサイズと共に関数に引き渡す必要があります。形式は、WOLFSSL_FILETYPE_ASN1(DER形式) か WOLFSSL_FILETYPE_PEM のどちらかを指定します。PEM形式の場合は、証明書チェーンをロード可能です。複数のルートCA証明書をロードする場合は、複数回関数を呼び出します。

```
int wolfSSL_CTX_load_verify_buffer (WOLFSSL_CTX * ctx, const unsigned char *in,long sz, int format)
```
**ctx** : wolfSSL_CTX_new()で作成した、SSLコンテクストのポインタ
**certBuff**: PEM形式のCA証明書を含むバッファへのポインタ
**sz**: PEM形式のCA証明書を１つ以上含むフォルダー
**format**: *WOLFSSL_FILETYPE_ASN1(DER形式)*　又は、*WOLFSSL_FILETYPE_PEM* を指定


##コード例
複数のCA証明書を一括で読み込むクライアントサンプルです。./certsフォルダ以下の証明書を読み込み、azure、aws、ローカルのサーバーに順次接続します。./certs フォルダは、実行ファイルと同一フォルダ下のにあるものとします。ローカルのサーバーは、[wolfSSLのクイックスタート(コマンド編)](https://qiita.com/kojo1/items/3140b107fd7f0ccdf061)にあるサーバーサンプルを予め起動しておきます。

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

実行例です。

```
$ ls ./certs
StarfieldClass2Cert.cer  azure_microsoft_com.cer  ca-cert.pem ca-ecccert.pem

$ ./wolfssl_sample_client 
Loaded certs files...OK!
please hit any key to connect azure.microsoft.com
>
HTTP/1.1 400 Bad Request
Content-Type: text/html; charset=us-ascii
Date: Wed, 19 Aug 2020 06:07:39 GMT
Connection: close
Content-Length: 326
....

please hit any key to connect aws.amazon.com
>
HTTP/1.1 400 Bad Request
Server: CloudFront
Date: Wed, 19 Aug 2020 06:08:19 GMT
Content-Type: text/html
Content-Length: 915
.....
please hit any key to connect 127.0.0.1
>
I hear you fa shizzle!

```

<details>
 <summary>サンプルコード（ここをクリックしてください）</summary>
 <div>
  処理の内容は次の通りです。

  1. TLSを使用時の動的パラメータをおさえておくためのコンテクスト WOLFSSL_CTX を一つ確保
  2. wolfSSL_CTX_load_verify_locations_ex() にフォルダー指定でルートCA証明書をロード
  3. TLS接続のためのディスクリプタを一つ確保し、確保してあるソケットを登録
  4. 接続先サーバのIPアドレス、ポートを設定し、connectにてTCP接続
  5. wolfSSL_connect()を呼び出し、TLSハンドシェークを開始
  6. wolfSSL_write()でメッセージを送信 wolfSSL_read()でメッセージを受信
  7. 切断処理、SSL ディスクリプタを解放
  8. コンテクスト WOLFSSL_CTX を解放
  
 </div>
<div>

```c
static const char *TARGETSERVER[3] =
{ 
    "azure.microsoft.com",
    "aws.amazon.com",
    "127.0.0.1",
};
static const word16 TARGETSERVER_PORT[3] =
{
    443, 443, 11111
};

/* This function is to load cert file from ./certs/ folder. */
/* And then try to connect to azure.micosoft.com,           */
/* aws.amazon.com and local servers                         */
void client_certfolder_test()
{
    SSL_CTX*    ctx    = 0;
    SSL*        ssl    = 0;
    SOCKET_T sockfd = 0;
    char reply[1024+1];
    char buffer[WOLFSSL_MAX_ERROR_SZ];
    int ret = 0, err = 0;
    int sendSz;
    int i;

    /*1. create wolfSSL context */
    ctx    = SSL_CTX_new(SSLv23_client_method());
    /*2. load root ca files in cert/ folder */
    if (wolfSSL_CTX_load_verify_locations_ex(ctx, NULL, "./certs/",
                 WOLFSSL_LOAD_FLAG_IGNORE_ERR) != WOLFSSL_SUCCESS) {
        err_sys("can't load ca file in folder");
        wolfSSL_CTX_free(ctx);
        exit(-1);
    } else {
        printf("Loaded certs files...OK!\n");
    }
    
    /*  connect each target */
    i = 0;
    while(i < 3)
    {
        printf("please input any key to connect %s\n", TARGETSERVER[i]);
        printf(">");
        getchar();

        /*3. create new wolfSSL object */
        ssl = SSL_new(ctx);

        /*4. connect to peer */
        tcp_connect(&sockfd, TARGETSERVER[i], TARGETSERVER_PORT[i], ssl);
        /* This function assigns a file descriptor (fd) as 
        the input/output facility for the SSL connection. */
        wolfSSL_set_fd(ssl, sockfd);

        do {
            err = 0; /* Reset error */
            /*5. call wolfSSL_connect() to start TLS handshake */
            ret = wolfSSL_connect(ssl);
            if (ret != WOLFSSL_SUCCESS) {
                err = wolfSSL_get_error(ssl, 0);
            }
        } while (err == _WANT_WRITE);

        if (ret != WOLFSSL_SUCCESS) {
            printf("SSL_connect error %d, %s\n", err,
                ERR_error_string(err, buffer));
            err_sys("SSL_connect failed");
        }
        /*6. send message to server and print reply from server */
        do {
            err = 0; /* reset error */
            ret = wolfSSL_write(ssl, "hello\n", 6);
            if (ret <= 0) {
                err = wolfSSL_get_error(ssl, 0);
            }
        } while (err == _WANT_WRITE);
        
        if (ret != 6) {
            printf("SSL_write msg error %d, %s\n", err,
                 ERR_error_string(err, buffer));
            err_sys("SSL_write failed");
        }
        
        {
            do {
                err = 0; /* reset error */
                ret = wolfSSL_read(ssl, reply, sizeof(reply)-1);
                if (ret <= 0) {
                    err = wolfSSL_get_error(ssl, 0);
                }
            } while (err == _WANT_WRITE);
        
            if (ret > 0) {
                reply[ret] = 0;
                printf("%s", reply);
                sendSz -= ret;
            }
            else {
                 printf("SSL_read msg error %d, %s\n", err,
                     ERR_error_string(err, buffer));
                 err_sys("SSL_read failed");
            }
        }
        /*7. disconnect from peer */
        wolfSSL_shutdown(ssl);
        wolfSSL_free(ssl);
        close(sockfd);
        ssl = NULL;
        i++;
        printf("\n");
    }
    /*8. clean up SSL context object */
    wolfSSL_CTX_free(ctx);
}
```
</div>
</details>

サンプルコードは、[こちらの GitHub ](https://github.com/wolfssl-jp/examples) からクローンすることができます。Certs1というフォルダーに含まれます。

       ```
       $ git clone https://github.com/wolfssl-jp/examples
       Cloning into 'examples'...
       remote: Enumerating objects: 29, done.
       remote: Counting objects: 100% (29/29), done.
       remote: Compressing objects: 100% (25/25), done.
       remote: Total 29 (delta 4), reused 23 (delta 2), pack-reused 0
       Unpacking objects: 100% (29/29), done.
       
       $ ls /path/to/examples/
       Cert1  Cert2  README.md
       
       $ cd /path/to/examples/Cert1
       
       $ ls
       Makefile  certs  sample_client.c  sample_client.h
       $ make
       gcc -c -g -Wall -MMD -MP sample_client.c
       gcc -o wolfssl_sample_client sample_client.o -lwolfssl 
      ```

aws.amazon.com 及び azure.microsoft.com のルートCA証明書は[ブラウザを使ってCA証明書を持ってくる](https://qiita.com/kojo1/items/9fb7e0f6e2eebef935d5)を参考にダウンロードし、./certs　フォルダーに配置してください。コンパイルには Linux など、make が使える環境を想定しています。

wolfSSL は[wolfSSLのクイックスタート(コマンド編)](https://qiita.com/kojo1/items/3140b107fd7f0ccdf061)を参考に予めインストールしておきます。wolfSSLをビルドする際には、Server Name Indication のオプションを有効にします。

```
$./configure --enable-sni
```




##まとめ
今回は wolfSSL のAPIを使ってルートCA証明書をロードする方法を紹介しました。ファイルシステムの有り無し、ファルダー指定からの一括読み込みなど、IoTデバイスの用途にあわせてご利用下さい。詳細なドキュメント、製品情報については、wolfSSLの下記を参照してください。wolfSSLは、ソフトウェアの内容は同じですが無償のオープンソース版と有償商用版のデュアルライセンスとなっています。製品への組み込みの際は商用ライセンス契約が必要です。

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