組み込み向け SSL の CRL を試してみる

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

##概要
[wolfSSL](https:/https://www.wolfssl.jp/)のCRL機能について紹介します。CRLとは、有効期限よりも前に失効した証明書の一覧(CRL: Certificate Revocation List)を読み込み、相手方から示された証明書が該当しないかを確認する機能です。今回は、CRLに関連したAPIと、その典型的な使用例をサンプルのサーバーとクライアントプログラムで紹介します。

使用するCRL関連の API として、CRL機能を有効・無効にするためのAPI、CRLファイルを読み込むためのAPI、そしてCRLが一つも見つからない場合に呼び出されるコールバック関数を登録するためのAPIの３つを使用します。

|指定単位|API名|
|---|:---:|
|コンテクスト|wolfSSL_CTX_EnableCRL<br>wolfSSL_CTX_LoadCRL<br>wolfSSL_CTX_SetCRL_Cb
|セッション|wolfSSL_EnableCRL<br>wolfSSL_LoadCRL<br>wolfSSL_SetCRL_Cb
表１．CRLに関連するAPI

CRLファイルを読み込むAPI、wolfSSL_xxxLoadCRL はファイルシステムの無しの場合、"wolfSSL_xxxLoadCRLBuffer" のように "Buffer"が後ろに付く形で存在します。

wolfSSL に同梱される /certs フォルダーには、CRLの実験用ファイルも収められており、今回はこれを利用します。

|証明書ファイル| 説明 |
|---|---|
|server-revoked-cert.pem|テスト用失効済みサーバー証明書|
|server-revoked-key.pem|サーバー証明書に対応する秘密鍵|
|/crl/crl.pem|失効済み証明書一覧|
表2 使用する証明書とCRLファイル

動作確認では、wolfSSL に同梱されるサンプルのクライアント・サーバーを動かしてみます。実験として、証明書とCRLファイルの中身を見て使用する証明書が失効リストに入っていることを確認し、失効リストの登録されていない証明書では成功することを確認します。また、追加実験として、CRL機能を有効にした状態で、外部のサーバーにアクセスしてみます。

今回の記事を読むにあたっては、下記の記事を必要な場合に参照ください。
- [wolfSSLのクイックスタート(コマンド編)](https://qiita.com/kojo1/items/3140b107fd7f0ccdf061)
- [TCP SocketプログラムをSSL/TLSに](https://qiita.com/kojo1/items/ae434073567a6a08d972)
- [wolfSSLの相互認証API](https://qiita.com/kj1/items/a8d02371964fef04a454)
- [ブラウザを使ってCA証明書を持ってくる](https://qiita.com/kj1/items/9fb7e0f6e2eebef935d5)

##CRLファイルに関連するAPI使用例

SSLセッションの指定単位で CRL を使用する場合、TLS接続開始までの処理の流れは次のようになります。

1. SSLコンテクスト、SSLセッションの確保 
2. 証明書・秘密鍵のロード 
3. TCPパケット接続に必要な設定
4. **CRL**リストの読み込み
5. TLS 接続開始 

コンテクスト単位で指定する場合でも、上記のステップと似通った流れになります。CRL は相手方証明書の検証の一部として実施されるので、TLS接続開始前に、クライアントの場合はwolfSSL_connect API の呼び出し、サーバーの場合は wolfSSL_accept API の呼び出し前に CRLの設定を行う必要があります。


下記はサンプルのクライアントプログラムのソースコードの抜粋で、典型的な使用例になります。

```c
/* /wolfssl/wolfssl/test.h */
#ifdef HAVE_CRL

static WC_INLINE void CRL_CallBack(const char* url)
{
    printf("CRL callback url = %s\n", url);
}

#endif

/* ./example/client/client.c */
#define crlPemDir  "./certs/crl"
...
/* SSLコンテクストの確保 */
ctx = wolfSSL_CTX_new(method(NULL));
    if (ctx == NULL)
        err_sys("unable to get ctx");

...
/* SSLセッションオブジェクト確保 */
ssl = wolfSSL_new(ctx);
    if (ssl == NULL) {
        wolfSSL_CTX_free(ctx); ctx = NULL;
        err_sys("unable to get SSL object");
    }
....
#ifdef HAVE_CRL
    if (disableCRL == 0 && !useVerifyCb) {
    #ifdef HAVE_IO_TIMEOUT
        wolfIO_SetTimeout(DEFAULT_TIMEOUT_SEC);
    #endif
        /* CRLを有効 */
        if (wolfSSL_EnableCRL(ssl, WOLFSSL_CRL_CHECKALL) != WOLFSSL_SUCCESS) {
            wolfSSL_free(ssl); ssl = NULL;
            wolfSSL_CTX_free(ctx); ctx = NULL;
            err_sys("can't enable crl check");
        }
        /* ./certs/crl フォルダー内の *.pem ファイルを読み込む*/
        if (wolfSSL_LoadCRL(ssl, crlPemDir, WOLFSSL_FILETYPE_PEM, 0)
                                                           != WOLFSSL_SUCCESS) {
            wolfSSL_free(ssl); ssl = NULL;
            wolfSSL_CTX_free(ctx); ctx = NULL;
            err_sys("can't load crl, check crlfile and date validity");
        }
        /* CRLが一つも見つからない場合に呼び出すコールバック関数の登録 */
        if (wolfSSL_SetCRL_Cb(ssl, CRL_CallBack) != WOLFSSL_SUCCESS) {
            wolfSSL_free(ssl); ssl = NULL;
            wolfSSL_CTX_free(ctx); ctx = NULL;
            err_sys("can't set crl callback");
        }
    }
#endif
...
 /* TLS接続開始　*/
 ret = wolfSSL_connect(ssl);
 if (ret != WOLFSSL_SUCCESS) {
     err = wolfSSL_get_error(ssl, 0);

```


##サンプルを動かしてみる

以下の説明では、Linux, MacOS, WidnowsのWSL, CygwinやminGWなどのコマンド環境を想定しています。

1 [wolfSSLのクイックスタート(コマンド編)](https://qiita.com/kojo1/items/3140b107fd7f0ccdf061)を参考に wolfSSL のソースコードをダウンロードします。

2 ビルドとインストール
コンフィグレーションに "--enable-crl"オプションを指定し、CRL機能をwolfSSL に組み込みます。

```
$cd /path/to/wolfssl

$./configure --enable-crl
...
$make check
...
$sudo make install
...
```
3　サンプルのサーバープログラムの実行
サンプルのサーバーを起動します。サーバの場合、外部からアクセス可能となるように"-b"でバインドを指定します。また、クライアント認証をしない場合は"-d"を指定します。"-c" で失効済みになっている証明書を指定します。"-k" で証明書に対応する秘密鍵を指定します。

```
$ ./examples/server/server -b -d -c ./certs/server-revoked-cert.pem -k ./certs/server-revoked-key.pem
```

4　サンプルのクライアントプログラムの実行
別ターミナルを開き、サンプルのクライアントを実行します。

```
$cd /path/to/wolfssl
$ ./examples/client/client
```

5 実行結果の確認

サーバー側の実行結果

```
$ ./examples/server/server -b -d -c ./certs/server-revoked-cert.pem -k ./certs/server-revoked-key.pem 
SSL_accept error -308, error state on socket
wolfSSL error: SSL_accept failed
```
クライアント側の実行結果

```
$ ./examples/client/client
wolfSSL_connect error -361, CRL Cert revoked
wolfSSL error: wolfSSL_connect failed
```

WireSharkでパケットをモニターすると証明書が無効であり、その警告が出ていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683403/61dea8b9-e63d-eb7b-7168-b269ad9083f2.png)

### コールバック関数の呼び出しについて

wolfSSL_SetCRL_Cb で登録するコールバック関数は、下記ののシグネチャを期待します。

```
void (*CbMissingCRL)(const char* url) 
```

コールバック関数が呼び出されるのは、CRL機能が有効にもかかわらず、検証を行おうとする証明書の発行元がリストされていない場合です。また呼び出されるタイミングは、相手方の証明書を検証実行時で、上記　WireShark　図ではサーバーから証明書が提示されクライアントがサーバー認証を行う際が該当します。

相手方の証明書にCRL配布ポイント（CRL Distribution points) のTLS拡張が存在する場合、その配布ポイントURLをコールバック関数の引数に引き渡します。



##実験１：未失効の証明書を指定する

今回使用した証明書(server-revoked-cert.pem)とCRLファイル(crl.pem)には、テキスト形式の情報が入っているのでファイルの中身をそのまま閲覧することが出来ます。下記の図は、ファイルの中身を示したものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683403/9cace188-9551-b54d-0b37-e4860362f382.png)
使用したサーバー証明書のシリアル番号は”２”で、CRLファイルにそのシリアル番号が失効した日とともに記述されていることが分かります。

試しに、サンプルのサーバープログラムで失効済み証明書ではなくデフォルトで使用するサーバー証明書を使うように実行してみるとSSLハンドシェークが成功することを確認できます。これは、デフォルトのサーバー証明書はCRLファイルにリストされていないためです。

サーバー側

```
$ ./examples/server/server -b -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 wolfssl!
```
クライアント側

```
$ ./examples/client/client
SSL version is TLSv1.2
SSL cipher suite is TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
SSL curve name is SECP256R1
I hear you fa shizzle!
```

## 実験２：外部サーバーにアクセスしてみる

次に外部サーバー[Yahoo](www.yahoo.co.jp)にサンプルのクライアントからCRL機能を有効にしてアクセスしてみます。

```
$ ./examples/client/client -h www.yahoo.co.jp -p 443 -g -A ./certs/Cyber_Baltimore.pem 
CRL callback url = http://sureseries-crl.cybertrust.ne.jp/SureServer/ctjpubcag3/cdp.crl
wolfSSL_connect error -362, CRL missing, not loaded
wolfSSL error: wolfSSL_connect failed
```

CRLが読み込まれていないためにエラーとなります。コールバック関数が呼び出され、URLが表示されます。

```
CRL callback url = http://sureseries-crl.cybertrust.ne.jp/SureServer/ctjpubcag3/cdp.crl
```

URLから CRL ファイルをダウンロードしフォルダーに配置することでメッセージは表示されなくなります。次のステップでCRLをダウンロードし、再度、アクセスし確認します。

#### CRLをダウンロード

先ほどのURLからファイルをダウンロードします。拡張子 crl は、DER形式ですので、pem 形式に変換しておきます。

```
openssl crl -inform der -in cdp.crl -outform pem -out cdp.pem 
```

#### 発行元の証明書を読み込む

wolfSSLではCRLの読み込み時に発行元の署名を検証します。そのため、予め発行元の証明書を読み込んでおく必要があります。www.yahoo.co.jpの発行元は Cybertrust Japan Public CA G3 ですので、その証明書が必要になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683403/532b2213-d935-4211-4989-682c67817c19.png)

複数の証明書を読み込むには、[ブラウザを使ってCA証明書を持ってくる](https://qiita.com/kj1/items/9fb7e0f6e2eebef935d5)を参考に、予め別々にPEM形式でダウンロードした証明書ファイルを結合して一つのファイルにします。

```
$ cat ./certs/Cybertrust.pem ./certs/BaltimoCA.pem >> ./certs/Cyber_Baltimore.pem
```

#### 再度アクセス
CRLファイル配置し、再度、アクセスしてみます。

```
$ ./examples/client/client -h www.yahoo.co.jp -p 443 -g -A ./certs/Cyber_Baltimore.pem 
SSL version is TLSv1.2
SSL cipher suite is TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
SSL curve name is SECP256R1
SSL connect ok, sending GET...
HTTP/1.0 404 Not Found on Accelerator
Date: Sun, 13 Sep 2020 05:00:30 GMT
Via: http/1.1 edge1632.img.bbt.yahoo.co.jp (ApacheTrafficServer [c s f ])
Server: ATS
Cache-Control: no-store
Content-Type: text/html
Content-Language: en
X-Frame-Options: SA

```
今度は成功します。


##まとめ
今回は wolfSSL のCRL関連APIを使ってCRL機能を使用する典型的な方法を紹介しました。詳細なドキュメント、製品情報については、wolfSSLの下記を参照してください。

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