組み込み向け SSL の OCSP Stapling を使ってみる

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

##概要
[wolfSSL](https:/https://www.wolfssl.jp/)のOCSP Stapling 機能について紹介します。OCSP Stapling とは、サーバーが認証局などのOCSPレスポンダへ事前に問い合わせを行い、そのキャッシュされた結果を証明書と共にクライアントに提示する機能です。

OCSP Stapling では事前にサーバーが証明書の認証情報を取得することで、OCSPクライアントはOCSPレスポンダへの通信が不要にな負荷の低減ができます。また証明書の有効・無効を即時的に確認できるという利点があります。

今回使用するAPIは、OCSPStapling 機能を有効にするためのAPI、及び OCSP Stapling の動作を指定する API になります。

|指定単位|API名|
|---|:---:|
|コンテクスト|wolfSSL_CTX_EnableOCSPStapling<br>wolfSSL_CTX_UseOCSPStapling<br>wolfSSL_CTX_UseOCSPStaplingV2
|セッション|wolfSSL_EnableOCSPStapling<br>wolfSSL_UseOCSPStapling<br>wolfSSL_UseOCSPStaplingV2
表１．OCSP Staplingに関連するAPI

OCSP Stapling には、2つのバージョンがあり、[RFC6066(Certificate Status Request TLS Extension)](https://tools.ietf.org/html/rfc6066) で定義されたものと、[RFC6969(Transport Layer Security (TLS) Certificate Status Version 2 Extension)](https://tools.ietf.org/html/rfc6961)で定義されたものがあります。Version 2 では、サーバー証明書の発行元の証明書を含む、複数証明書の状態（Status）を確認できるように拡張が行われています。ただし、TLS 1.3では、サーバーのOCSP情報は、関連付けられた証明書を含む Certificate メッセージ拡張で運ばれるため、Version 2 は非推奨となっています(参照 [RFC6446 Section 4.4.2.1](https://tools.ietf.org/html/rfc8446))。

wolfSSL の API では、Version 1 の動作指定に、wolfSSL_xxx_UseOCSPStapling() を、Version 2 の動作指定に、wolfSSL_xxx_UseOCSPStaplingV2()　を使用します。

動作確認では、wolfSSL に同梱されるサンプルのクライアント・サーバーを使って OCSP Stapling 機能については外部のサーバーと通信を行ってみます。OCSP Stapling V2 機能についてはローカルのサーバーと通信を行ってその動きを見ていきます。

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

1. SSLコンテクスト、SSLセッションの確保
2. 証明書・秘密鍵のロード
3. OCSP Stapling を有効化
4. OCSP Stapling の動作指定
4. TCPパケット接続に必要な設定
5. TLS 接続開始

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

下記はサンプルのクライアントプログラムからの抜粋になります。

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

#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) || \
    defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2)
    if (statusRequest) {
        if (version == 4 &&
            (statusRequest == OCSP_STAPLINGV2 || \
             statusRequest == OCSP_STAPLINGV2_MULTI)) {
            err_sys("Cannot use OCSP Stapling V2 with TLSv1.3");
        }
        /* OCSP Stapling 機能を有効にします */
        if (wolfSSL_CTX_EnableOCSPStapling(ctx) != WOLFSSL_SUCCESS)
            err_sys("can't enable OCSP Stapling Certificate Manager");

        switch (statusRequest) {
        #ifdef HAVE_CERTIFICATE_STATUS_REQUEST
            case OCSP_STAPLING:
                /* OCSP Stapling 機能を指定します。クライアントプログラムでは "-W 1" を指定します。 */
                if (wolfSSL_UseOCSPStapling(ssl, WOLFSSL_CSR_OCSP,
                               WOLFSSL_CSR_OCSP_USE_NONCE) != WOLFSSL_SUCCESS) {
                    wolfSSL_free(ssl); ssl = NULL;
                    wolfSSL_CTX_free(ctx); ctx = NULL;
                    err_sys("UseCertificateStatusRequest failed");
                }
            break;
        #endif
        #ifdef HAVE_CERTIFICATE_STATUS_REQUEST_V2
            case OCSP_STAPLINGV2:
                /* OCSP Stapling V2 の機能を指定します。クライアントプログラムでは "-W 2" を指定します。 */
                /* 第2引数で、複数証明書の状態を確認するかを指定します。                                */
                /* WOLFSSL_CSR2_OCSP は、一つ（相手方の証明書）の状態を確認します。                    */
                if (wolfSSL_UseOCSPStaplingV2(ssl,
                    WOLFSSL_CSR2_OCSP, WOLFSSL_CSR2_OCSP_USE_NONCE)
                                                           != WOLFSSL_SUCCESS) {
                    wolfSSL_free(ssl); ssl = NULL;
                    wolfSSL_CTX_free(ctx); ctx = NULL;
                    err_sys("UseCertificateStatusRequest failed");
                }
            break;
            case OCSP_STAPLINGV2_MULTI:
                /* OCSP Stapling V2 の機能を指定します。クライアントプログラムでは "-W 3" を指定します。 */
                /* 第2引数で、複数証明書の状態を確認するかを指定します。                                */
                /* WOLFSSL_CSR2_OCSP_MULTI は、複数の状態を確認します。　　　　                        */
                if (wolfSSL_UseOCSPStaplingV2(ssl,
                    WOLFSSL_CSR2_OCSP_MULTI, 0)
                                                           != WOLFSSL_SUCCESS) {
                    wolfSSL_free(ssl); ssl = NULL;
                    wolfSSL_CTX_free(ctx); ctx = NULL;
                    err_sys("UseCertificateStatusRequest failed");
                }
            break;
        #endif
            default:
                err_sys("Invalid OCSP Stapling option");
        }

        wolfSSL_CTX_EnableOCSP(ctx, 0);
    }
#endif
...
   /* TLS接続開始　*/
   ret = wolfSSL_connect(ssl);
   if (ret != WOLFSSL_SUCCESS) {
       err = wolfSSL_get_error(ssl, 0);
...
   /* 終了処理 */
   ret = wolfSSL_shutdown(ssl);
   ...
   /* SSL　セッションオブジェクトの解放 */
    wolfSSL_free(ssl); ssl = NULL;
   /* TCPソケットのクローズ処理 */
    CloseSocket(sockfd);
   /* コンテクストオブジェクトの解放*/
   wolfSSL_CTX_free(ctx); ctx = NULL
```

## OCSP Stapling 動作を確認

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

OCSP Stapling の動きをサンプルのクライアントとサーバーを使って確認します。サンプルのクライアントでは、"-W" オプションで OCSP Stapling 機能を有効にします。"-W" オプションの引数は、 "1"　で OCSP Stapling、"2" で OCSP Stapling V2 拡張を使ってサーバー証明書状態の確認、"3" で OCSP Stapling V2 のサーバー証明書、発行元を含む、複数証明書の状態を確認します。

まず、OCSP Stapling　機能を有効にし、外部のサーバーに接続してみます。次に OCSP Stapling V2 の機能をローカルのサーバーを使って確認します。OCSP Stapling V2 では、有効なサーバー証明書と失効済みのサーバー証明書を使ってみます。失効済みサーバー証明書を使用する際には、複数の証明書の状態を確認しているところを見てみます。

### ビルドとインストール

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

2. コンフィグレーションに "--enable-ocspstapling"及び"--enable-ocspstapling2"を指定し、OCSP Stapling機能をwolfSSL に組み込みます。また、アクセスするサーバーを指定するために SNI (Server Name Indication) 機能を有効にします。

```
$cd /path/to/wolfssl

$ ./configure --enable-ocspstapling2 --enable-ocspstapling --enable-sni
...
$make check
...
$sudo make install
...
```

### OCSP Stapling 機能を有効にし、外部サーバーへアクセス

サンプルのクライアントプログラムでOCSP Stapling機能を有効にし、外部サーバー azure.microsoft.com へアクセスしてみます。引数に "-W 1" を指定します。

```
$ ./examples/client/client -h azure.microsoft.com -p 443 -A ./certs/azure_microsoft_com.cer -g -S azure.microsoft.com -W 1
```

この時のTLS通信をWireSharkで確認します。

クライアントとOCSPレスポンダ間の通信はなく、`Client Hello` に status_request TLS拡張が入っていることが確認できます。また、サーバーは応答として、`Certificate Status`を送信しています。証明書の状態は good であることが分かります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683403/9836c20c-1460-01b9-9fa9-ef28d38d620d.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683403/bb5c344d-81e8-0613-4123-2be171ece193.png)

### OCSP Stapling V2 機能を有効にし、ローカルのサーバーに接続

wolfSSL に同梱される下記の証明書を利用して、OCSP　Stapling V2 の動きを確認します。サーバー証明書は、有効なもの（server3-cert.pem)と失効済み（server4-cert.pem)を使用します。OCSP レスポンダは、`openssl`の機能を利用します。

|証明書/鍵|メモ|
|:--|:--|
|./certs/ocsp/root-ca-cert.pem|CARoot証明書|
|./certs/ocsp/server3-cert.pem|サーバー証明書3 シリアル番号：７(有効）|
|./certs/ocsp/server3-key.pem|サーバー証明書3に対応する秘密鍵|
|./certs/ocsp/server4-cert.pem|サーバー証明書4　シリアル番号：8（失効済み）|
|./certs/ocsp/server4-key.pem|サーバー証明書4に対応する秘密鍵|
|./certs/ocsp/ocsp-responder-cert.pem|OCSPレスポンダ証明書1|
|./certs/ocsp/ocsp-responder-key.pem|OCSPレスポンダ証明書1に対応する秘密鍵|

#### OCSP レスポンダを起動

サーバー証明書３とサーバー証明書４には、それぞれ下記のOCSPレスポンダがありますので、各々を起動します。

|アドレス|用途|
|:--|:--|
|127.0.0.1:22222|サーバー証明書3、サーバー証明書4のOCSPレスポンダ|
|127.0.0.1:22220|CARoot証明書、中間CA証明書のOCSPレスポンダ|

1. OCSP レスポンダーをローカルで起動

ターミナルを一つ開き、CARoot証明書、中間CA証明書のOCSPレスポンダを起動します。

```
$ cd /path/to/wolfssl

$ openssl ocsp -port 22220 -nmin 1 -index certs/ocsp/index-ca-and-intermediate-cas.txt -rsigner certs/ocsp/ocsp-responder-cert.pem -rkey certs/ocsp/ocsp-responder-key.pem -CA certs/ocsp/root-ca-cert.pem
```

別ターミナルを開き、サーバー証明書3、サーバー証明書4のOCSPレスポンダを起動します。

```
$ cd /path/to/wolfssl

$ openssl ocsp -port 22222 -nmin 1 -index ./certs/ocsp/index-intermediate2-ca-issued-certs.txt -rsigner ./certs/ocsp/ocsp-responder-cert.pem -rkey ./certs/ocsp/ocsp-responder-key.pem -CA ./certs/ocsp/intermediate2-ca-cert.pem
```

#### 有効なサーバー証明書を OCSP Stapling V2 で確認

 1 サンプルのサーバーを起動
ターミナルを開き、有効なサーバー証明書を指定しサンプルのサーバーを起動します。"-i"を指定し繰り返し実行します。

```
$ cd /path/to/wolfssl

$ ./examples/server/server -c certs/ocsp/server3-cert.pem -k certs/ocsp/server3-key.pem -p 11111 -i
```

 2 サンプルのクラインを起動
ターミナルを開き、サンプルのサーバーを2回起動します。

```
$ cd /path/to/wolfssl

$ ./examples/client/client -A certs/ocsp/root-ca-cert.pem -W 2 -p 11111

$ ./examples/client/client -A certs/ocsp/root-ca-cert.pem -W 2 -p 11111
```

この時のTLS通信を WireShark で確認します。WireSharkeは、Analyze -> Decode As でこのポートをSSLとして登録しておきます。

"Client Hello" に status_request_v2 TLS 拡張が入っています。サーバーはOCSP通信を使って、OCSPレスポンダに確認しています。その結果が、Certificate Status として返信されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683403/ca5f6791-95d1-26db-aa60-219f72c02987.png)

2度目のクライアント接続を見てみると、最初の接続で行われた OCSP レスポンダとの通信がなく、キャッシュされた情報が使われていることが分かります。次回のアクセスは、OCSPレスポンダの Response メッセージに含まれる next Update に依ります。next Update より以前のアクセスの場合、キャッシュされた情報が使用されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683403/81b56940-f286-fbd2-565b-40b537fe5d5e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683403/e7e7947a-c360-654e-8859-c4544238e085.png)

#### 失効済みのサーバー証明書を OCSP Stapling V2 で確認

   1 サンプルのサーバーを起動
ターミナルを開き、失効済みサーバー証明書を指定し、サンプルのサーバーを起動します。

```
$ cd /path/to/wolfssl

$ $ ./examples/server/server -c certs/ocsp/server4-cert.pem -k certs/ocsp/server4-key.pem -p 11111
```

   2 サンプルのクラインを起動
ターミナルを開き、サンプルのサーバーを起動します。"-W 3" を指定します。

```
$ cd /path/to/wolfssl

$ ./examples/client/client -A certs/ocsp/root-ca-cert.pem -W 3 -p 11111
```

この時のTLS通信を WireShark で確認します。WireSharkeは、Analyze -> Decode As でこのポートをSSLとして登録しておきます。

"Client Hello" に status_request_v2 TLS 拡張があり、Typeとして OCSP Multi を指定しています。サーバーはOCSP通信を使って、OCSPレスポンダに各証明書の状態を確認しています。その結果が、Certificate Status として返信されています。サーバー証明書は失効している（revoke)のでTLS通信は失敗します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683403/fe45ed6c-6d31-5496-4b0b-981f36fe69ca.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683403/b3d598cd-b4b9-1e7f-dc24-05ebb8816a1f.png)


##まとめ
今回は wolfSSL のOCSP Stapling 機能を紹介しました。OCSP Stapling V2 に対応しているサーバーが少ないようでした。そのため、ローカルでサンプルのサーバーを使って確認を行いました。

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

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