mbedTLSのコーディング


[こちら](https://qiita.com/tom_S/items/62696a10a414443e49eb)の記事の続きになります。
上記のリンク先の記事ではmbedTLSの概要と設定に関して説明したので、
本記事では初期化、ハンドシェイク、通信におけるコーディングと呼び出すAPIの説明をいたします。


## 初期化のソースコード

mbedTLSの初期化時に呼び出すAPIを下記にまとめました。
```C
void mbedTLSInit(void)
{
    int len;
    mbedtls_net_context server_fd;
    const uint8_t *pers = (uint8_t *)("ssl_client");

    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctr_drbg;
    mbedtls_ssl_context ssl;
    mbedtls_ssl_config conf;
    mbedtls_x509_crt cacert;

    /* ここでLwIP初期化。DHCPのIPアドレス取得まで行う  */
    
    mbedtls_ssl_init(&ssl);
    mbedtls_ssl_config_init(&conf);
    mbedtls_x509_crt_init(&cacert);
    mbedtls_ctr_drbg_init(&ctr_drbg);


    mbedtls_entropy_init(&entropy);
    len = strlen((char *)pers);
    if (mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *) pers, len) != 0)
    {
        return; /* エラー */
    }


    /* 証明書の初期化(自身の鍵・証明書設定) */
    /* mbedtls_test_cas_pem、mbedtls_test_cas_pem_lenはcert.hにて定義されている */
    if (mbedtls_x509_crt_parse(&cacert, (const unsigned char *) mbedtls_test_cas_pem, mbedtls_test_cas_pem_len) < 0)
    {
        return; /* エラー */
    }
}
```

使用しているAPIを順に説明していきます。

```C
void mbedtls_ssl_init( mbedtls_ssl_context *ssl )
```

|  項目1  |    項目2    | 説明 |
| ------------- |  -------------------- | -------------------- |
| 機能 |              | 引数で指定した構造体をmemsetにより全てのパラメータを0で初期化 |
| 引数 | ssl  | mbedTLS管理用構造体。実体はユーザ側で持つ |

```C
void mbedtls_ssl_config_init( mbedtls_ssl_config *conf )
```

|  項目1  |    項目2    | 説明 |
| ------------- |  -------------------- | -------------------- |
| 機能 |              | 引数で指定した構造体をmemsetにより全てのパラメータを0で初期化 |
| 引数 | conf  | mbedTLS設定用構造体。実体はユーザ側で持つ |

```C
void mbedtls_x509_crt_init( mbedtls_x509_crt *crt )
```

|  項目1  |    項目2    | 説明 |
| ------------- |  -------------------- | -------------------- |
| 機能 |              | 引数で指定した構造体をmemsetにより全てのパラメータを0で初期化 |
| 引数 | crt  | mbedTLSの証明書管理用構造体。実体はユーザ側で持つ |

```C
void mbedtls_ctr_drbg_init( mbedtls_ctr_drbg_context *ctx )
```

|  項目1  |    項目2    | 説明 |
| ------------- |  -------------------- | -------------------- |
| 機能 |              | 引数で指定した構造体をmemsetにより全てのパラメータを0で初期化 |
| 引数 | ctx | mbedTLSのCTR_DRBG(AESとカウンタによる擬似乱数生成用モジュール)管理用の構造体。実体はユーザ側で持つ |

```C
void mbedtls_entropy_init( mbedtls_entropy_context *ctx )
```

|  項目1  |    項目2    | 説明 |
| ------------- |  -------------------- | -------------------- |
| 機能 |              | 引数で指定した構造体をmemsetで初期化。乱数生成時に呼ばれるコールバックの設定 |
| 引数 | ctx | mbedTLSのエントロピー(乱数の予測不可能性を担保するもの)管理用の構造体。実体はユーザ側で持つ |
```C
int mbedtls_ctr_drbg_seed( mbedtls_ctr_drbg_context *ctx,
                   int (*f_entropy)(void *, unsigned char *, size_t),
                   void *p_entropy,
                   const unsigned char *custom,
                   size_t len )
```

|  項目1  |    項目2    | 説明 |
| ------------- |  -------------------- | -------------------- |
| 機能 |  | シードの初期化とエントロピー(乱数)取得用関数の登録。再シード取得用のパラメータ設定 |
| 引数 | ctx | mbedTLSのCTR_DRBG(AESとカウンタによる擬似乱数生成用モジュール)管理用の構造体。実体はユーザ側で持つ |
|  | f_entropy | ctxのメンバに登録する、エントロピー(乱数)取得用のコールバック |
|  | p_entropy | f_entropyを呼び出す際に引数として指定するポインタ |
|  | custom | シードの値を格納した配列。シードの再設定時に使われる |
|  | len  | customの示す先の配列サイズ |
| 返り値 |  | 正常値は0。それ以外の値はエラー |

```C
int mbedtls_x509_crt_parse(mbedtls_x509_crt* chain,
                                             const unsigned char* buf,
                                             size_t buflen)	
```

|  項目1  |    項目2    | 説明 |
| ------------- |  -------------------- | -------------------- |
| 機能 |  | 証明書をPEM形式かDER形式にパースして取得する。 |
| 引数 | chain | パースする証明書 |
|  | buf | パースして取得した、PEM形式か、DER形式の証明書 |
|  | buflen | bufの示す先の配列サイズ |
| 返り値 |  | 正常値は0。それ以外の値はエラー |

## ハンドシェイク

SSL/TLSのハンドシェイクに関するソースコードについて解説します。初期化にて設定した「mbedtls_ssl_config」、「mbedtls_ctr_drbg_context」、「mbedtls_ssl_context」、「mbedtls_x509_crt」を使用するので、これらの変数は静的な変数と定義する、グローバルな変数として保持しておくなどの対応が必要です。

```C
void mbedTLS_handShake(mbedtls_ssl_config conf, mbedtls_ctr_drbg_context ctr_drbg, mbedtls_ssl_context ssl, 
mbedtls_x509_crt cacert)
{
    mbedtls_net_context server_fd;


    /* 通信相手と接続(暗号通信はしない)。net_socket.cでユーザーが実装する関数 */
    if (mbedtls_net_connect(&server_fd, SERVER_NAME, SERVER_PORT, MBEDTLS_NET_PROTO_TCP) != 0)
    {
        return; /* エラー */
    }
    /* クライアントとしてmbedTLSを設定 */
    if (mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT) != 0)
    {
        return; /* エラー */
    }


    /* 通信モードをconfに設定。MBEDTLS_SSL_VERIFY_OPTIONALなので、エラー内容を「mbedtls_ssl_get_verify_result」で確認する必要がある */
    mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_OPTIONAL);


    /* mbedtls_x509_crt_parseで設定した証明書・鍵をconfに設定 */
    mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL);


    /* 乱数生成にCTR-DRBG用の関数「mbedtls_ctr_drbg_random」を呼び出すようにconfに設定 */
    mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);


    /* confに設定したパラメータを反映 */
    if (mbedtls_ssl_setup(&ssl, &conf) != 0)
    {
        return; /* エラー */
    }


    /* HOST名指定 */
    if (mbedtls_ssl_set_hostname(&ssl, "localhost") != 0)
    {
        return; /* エラー */
    }


    /* データの送受信に使う関数を「mbedtls_net_send」「mbedtls_net_recv」にて設定。server_fdは送受信関数呼び出し時に引数として指定される */
    mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL);


    /* TLSの4WAYハンドシェイクを実施 */
    while ((ret = mbedtls_ssl_handshake(&ssl)) != 0)
    {
        if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE)
        {
            return; /* エラー */
        }
    }


    /* ４WAYハンドシェイクの結果を確認 */
    if (mbedtls_ssl_get_verify_result( &ssl ) != 0)
    {
       return; /* エラー */
    }
}
```

使用しているAPIを順に説明していきます。

```C
int mbedtls_ssl_config_defaults(mbedtls_ssl_config* conf, int endpoint, int transport, 
                                                   int preset)		
```

|  項目1  |    項目2    | 説明 |
| ------------- |  -------------------- | -------------------- |
| 機能 |  | デフォルトのmbedTLSの設定を反映する |
| 引数 | conf | デフォルトの設定を格納する配列のポインタ |
|  | endpoint | クライアントを示す値「MBEDTLS_SSL_IS_CLIENT」かサーバを示す値 「MBEDTLS_SSL_IS_SERVER」を設定する |
|  | transport | TLS通信を示す値「MBEDTLS_SSL_TRANSPORT_STREAM」か、DTLS通信を示す値「MBEDTLS_SSL_TRANSPORT_DATAGRAM」を設定する |
|  | preset | 現状使用されていない。「MBEDTLS_SSL_PRESET_DEFAULT」を指定すれば問題ない |
| 返り値 |  | 正常値は０。それ以外はエラー |

```C
void mbedtls_ssl_conf_authmode(mbedtls_ssl_config* conf, int authmode)	
```

|  項目1  |    項目2    | 説明 |
| ------------- |  -------------------- | -------------------- |
| 機能 |  | 証明書の認証方法を設定 |
| 引数 | conf | コンフィギュレーションを格納する配列のポインタ。既に設定されているものを渡す |
|  | authmode | MBEDTLS_SSL_VERIFY_OPTIONAL、MBEDTLS_SSL_VERIFY_REQUIREDのどちらかを設定する。MBEDTLS_SSL_VERIFY_OPTIONALの場合は認証処理のエラー値を「mbedtls_ssl_get_verify_result」によって取得する。 |

```C
void mbedtls_ssl_conf_ca_chain(mbedtls_ssl_config *conf, mbedtls_x509_crt *ca_chain, mbedtls_x509_crl *ca_crl)
```

|  項目1  |    項目2    | 説明 |
| ------------- |  -------------------- | -------------------- |
| 機能 |  | 証明書を設定 |
| 引数 | conf | コンフィギュレーションを格納する配列のポインタ。関数内で証明書の設定をこの配列に設定される |
|  | ca_chain | 証明書チェーン(ルート証明書から、サーバ証明書、クライアント証明書の連なり)  |
|   |  ca_crt  |  証明書失効リスト。指定する必要がなければNULLを入れる  |


```C
void mbedtls_ssl_conf_rng(mbedtls_ssl_config *conf, int (*f_rng)(void*, unsigned char*, size_t), void *p_rng)
```

|  項目1  |    項目2    | 説明 |
| ------------- |  -------------------- | -------------------- |
| 機能 |  | 乱数生成時に呼ばれる関数を設定する |
| 引数 | conf | コンフィギュレーションを格納する配列のポインタ。f_rng, p_rngを格納する |
|  | f_rng | 乱数生成時に呼ばれる関数ポインタ  |
|   |  p_rng | f_rngが呼び出される際に引数として指定されるポインタ  |

```C
int mbedtls_ssl_setup(mbedtls_ssl_context *ssl, const mbedtls_ssl_config *conf)
```

|  項目1  |    項目2    | 説明 |
| ------------- |  -------------------- | -------------------- |
| 機能 |  | コンテキスト(第一引数の「ssl」)にコンフィギュレーション情報を設定する |
| 引数 | ssl | 設定を反映させるmbedTLSのコンテキスト情報 |
|  | conf | コンフィギュレーションを格納する配列のポインタ |
| 返り値  |  | 正常値は0。メモリ取得エラーが起きた際はMBEDTLS_ERR_SSL_ALLOC_FAILEDが返る |

```C
int mbedtls_ssl_set_hostname(mbedtls_ssl_context *ssl, const char *hostname)
```

|  項目1  |    項目2    | 説明 |
| ------------- |  -------------------- | -------------------- |
| 機能 |  | コンテキスト(第一引数の「ssl」)にhost名を設定する |
| 引数 | ssl | host名を設定するmbedTLSのコンテキスト情報 |
|  | hostname | host名を示す文字列 |
| 返り値  |  | 正常値は0。メモリ取得エラーが起きた際はMBEDTLS_ERR_SSL_ALLOC_FAILEDが返る |

```C
void mbedtls_ssl_set_bio(mbedtls_ssl_context *ssl,
        void *p_bio,
        mbedtls_ssl_send_t *f_send,
        mbedtls_ssl_recv_t *f_recv,
        mbedtls_ssl_recv_timeout_t *f_recv_timeout)
```

|  項目1  |    項目2    | 説明 |
| ------------- |  -------------------- | -------------------- |
| 機能 |  | コンテキスト(第一引数の「ssl」)にhost名を設定する |
| 引数 | ssl | mbedTLSのコンテキスト情報 |
|  | p_bio | f_send, f_recv, f_recv_timeout を呼び出す際に引数で指定するポインタ |
|  | f_send | データ送信時に呼び出される関数 |
|  | f_recv | データ受信時に呼び出される関数(タイムアウト無し) |
|  | f_recv_timeout | データ受信時に呼び出される関数(タイムアウトあり) |

```C
int mbedtls_ssl_handshake(mbedtls_ssl_context *ssl)
```

|  項目1  |    項目2    | 説明 |
| ------------- |  -------------------- | -------------------- |
| 機能 |  | TLSのハンドシェイクを実施する |
| 引数 | ssl | 設定を反映させるmbedTLSのコンテキスト情報 |
| 返り値  |  | 正常値は0。それ以外の値はエラー |


```C
uint32_t mbedtls_ssl_get_verify_result(const mbedtls_ssl_context *ssl)
```

|  項目1  |    項目2    | 説明 |
| ------------- |  -------------------- | -------------------- |
| 機能 |  | TLSのハンドシェイクの実施結果を取得する |
| 引数 | ssl | 設定を反映させるmbedTLSのコンテキスト情報 |
| 返り値  |  | 正常値は0。それ以外の値はエラー |


## 通信

送受信の際のコードについて解説します。
HTTPのGETリクエストを送る例を下記に示します。データの通信に関しては、初期化とハンドシェイクが正常終了した際に使われていたmbedtls_ssl_context型の変数が必要になります。

```C
#define HTTP_GET_REQUEST "GET / HTTP/1.0\r\n\r\n"
int mbedTLS_readWrite(mbedtls_ssl_context* ssl)
{
    int len;
    uint8_t buf[1024];
    int ret;


    snprintf((char *)buf, sizeof(buf) / sizeof(char), HTTP_GET_REQUEST);
    len = strlen((char *) buf);
    while ((ret = mbedtls_ssl_write(ssl, buf, len)) <= 0)
    {
        if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE)
        {
            return ret; /* error */
        }
    }


    do
    {
        len = sizeof(buf) - 1;
        memset(buf, 0, sizeof(buf));
        ret = mbedtls_ssl_read(ssl, buf, len);


        if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE)
        {
            continue;
        }
        if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY)
        {
            break;
        }
        if (ret < 0)
        {
            break;
        }
        if (ret == 0)
        {
            break;
        }
    }
    while(1);


    return ret;
}
```

使用しているAPIを順に説明していきます。

```C
int mbedtls_ssl_write(mbedtls_ssl_context *ssl, const unsigned char *buf, size_t len)
```

|  項目1  |    項目2    | 説明 |
| ------------- |  -------------------- | -------------------- |
| 機能 |  | データの送信を行う |
| 引数 | ssl | mbedTLSのコンテキスト情報 |
|  | buf | 送信データ |
|  | len | 送信データサイズ |
| 返り値  |  | 0以上は送信データサイズ。それ以外のデータはエラー値 |


```C
int mbedtls_ssl_read(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len)
```

|  項目1  |    項目2    | 説明 |
| ------------- |  -------------------- | -------------------- |
| 機能 |  | データの受信を行う |
| 引数 | ssl | mbedTLSのコンテキスト情報 |
|  | buf | 受信データ |
|  | len | 受信データサイズ |
| 返り値  |  | 0以上は受信データサイズ。それ以外のデータはエラー値 |