ESP32上でwolfSSLを使って暗号処理してみる

# はじめに

このところ、ESP32のセキュリティ機能を調べていましたが、今回は他社ライブラリを載せてセキュリティ機能を実装する実験をしてみました。
使うのは[wolfSSL](https://www.wolfssl.com/)社の暗号ライブラリ。SSLの他、様々な暗号処理やセキュリティ機能が提供されているようです。
SSLについてはESP-IDF自身でも実装できるので、S/MIMEなどで使われるPKCS#7 EnvelopedDataをESP32上で作ってみました。小さな組込みデバイスでこんなことができるなんて、ちょっと感動しました。

【今回の環境】

- ボード：ESP32-DevKitC-32D
- 開発環境：Windows 10 + ESP-IDF v4.0.1
- 追加ライブラリ：wolfSSL v4.5.0

# ライブラリの入手とセットアップ

ライブラリは以下URLからダウンロード。
https://www.wolfssl.jp/download/

ESP-IDFで使う方法については以下に解説がありました。
https://www.wolfssl.com/docs/espressif/

まず、`<wolfssl-root>\IDE\Espressif\ESP-IDF\setup.sh`を使って必要なファイルをコピーするのですが、自身のWindowsはbashを使える環境を用意してなかったので、同様の処理をするバッチファイルを作ってコピーしました。

```wolfSSL-Setup.bat
if NOT EXIST "wolfcrypt" (
    echo Move to wolfSSL top folder and retry. Aborted.
    exit /B
)

set BASEDIR=%CD%
set WOLFSSL_ESPIDFDIR=%BASEDIR%\IDE\Espressif\ESP-IDF
set WOLFSSLLIB_TRG_DIR=%IDF_PATH%\components\wolfssl
set WOLFSSLEXP_TRG_DIR=%IDF_PATH%\examples\protocols

REM ===============================================
REM Remove/Create directories
rmdir /S/Q %WOLFSSLLIB_TRG_DIR%
mkdir      %WOLFSSLLIB_TRG_DIR%
mkdir      %WOLFSSLLIB_TRG_DIR%\src
mkdir      %WOLFSSLLIB_TRG_DIR%\wolfcrypt\src
mkdir      %WOLFSSLLIB_TRG_DIR%\wolfssl
mkdir      %WOLFSSLLIB_TRG_DIR%\test
mkdir      %WOLFSSLLIB_TRG_DIR%\include

REM ===============================================
REM copying ... files in src/ into $WOLFSSLLIB_TRG_DIR/src
xcopy /F/Y %BASEDIR%\src\*.c %WOLFSSLLIB_TRG_DIR%\src\
xcopy /E/F/Y %BASEDIR%\wolfcrypt\src\*.c %WOLFSSLLIB_TRG_DIR%\wolfcrypt\src\
xcopy /E/F/Y %BASEDIR%\wolfcrypt\src\*.i %WOLFSSLLIB_TRG_DIR%\wolfcrypt\src\
xcopy /E/F/Y %BASEDIR%\wolfcrypt\src\port %WOLFSSLLIB_TRG_DIR%\wolfcrypt\src\port\
xcopy /E/F/Y %BASEDIR%\wolfcrypt\test %WOLFSSLLIB_TRG_DIR%\wolfcrypt\test\
xcopy /E/F/Y %BASEDIR%\wolfcrypt\benchmark %WOLFSSLLIB_TRG_DIR%\wolfcrypt\benchmark\
xcopy /E/F/Y %BASEDIR%\wolfssl\*.h %WOLFSSLLIB_TRG_DIR%\wolfssl\
xcopy /E/F/Y %BASEDIR%\wolfssl\wolfcrypt %WOLFSSLLIB_TRG_DIR%\wolfssl\wolfcrypt\

REM ===============================================
REM user_settings.h
xcopy /F/Y %WOLFSSL_ESPIDFDIR%\user_settings.h %WOLFSSLLIB_TRG_DIR%\include\
copy /Y    %WOLFSSL_ESPIDFDIR%\dummy_config_h %WOLFSSLLIB_TRG_DIR%\include\config.h

REM ===============================================
REM  unit test app
xcopy /E/F/Y %WOLFSSL_ESPIDFDIR%\test %WOLFSSLLIB_TRG_DIR%\test\
copy /Y      %WOLFSSL_ESPIDFDIR%\libs\CMakeLists.txt %WOLFSSLLIB_TRG_DIR%\
copy /Y      %WOLFSSL_ESPIDFDIR%\libs\component.mk %WOLFSSLLIB_TRG_DIR%\

REM ===============================================
REM Benchmark program
rmdir /S/Q %WOLFSSLEXP_TRG_DIR%\wolfssl_benchmark\
mkdir      %WOLFSSLEXP_TRG_DIR%\wolfssl_benchmark\main\
copy /Y      %BASEDIR%\wolfcrypt\benchmark\benchmark.c %WOLFSSLEXP_TRG_DIR%\wolfssl_benchmark\main\
xcopy /E/F/Y %WOLFSSL_ESPIDFDIR%\examples\wolfssl_benchmark %WOLFSSLEXP_TRG_DIR%\wolfssl_benchmark\

REM ===============================================
REM Crypt Test program
rmdir /S/Q %WOLFSSLEXP_TRG_DIR%\wolfssl_test\
mkdir      %WOLFSSLEXP_TRG_DIR%\wolfssl_test\main\
copy /Y      %BASEDIR%\wolfcrypt\test\test.c %WOLFSSLEXP_TRG_DIR%\wolfssl_test\main\
xcopy /E/F/Y %WOLFSSL_ESPIDFDIR%\examples\wolfssl_test %WOLFSSLEXP_TRG_DIR%\wolfssl_test\

REM ===============================================
REM TLS Client program
rmdir /S/Q %WOLFSSLEXP_TRG_DIR%\wolfssl_client\
mkdir      %WOLFSSLEXP_TRG_DIR%\wolfssl_client\main\include\
xcopy /E/F/Y %WOLFSSL_ESPIDFDIR%\examples\wolfssl_client %WOLFSSLEXP_TRG_DIR%\wolfssl_client\

REM ===============================================
REM TLS Server program
rmdir /S/Q %WOLFSSLEXP_TRG_DIR%\wolfssl_server\
mkdir      %WOLFSSLEXP_TRG_DIR%\wolfssl_server\main\include\
xcopy /E/F/Y %WOLFSSL_ESPIDFDIR%\examples\wolfssl_server %WOLFSSLEXP_TRG_DIR%\wolfssl_server\
```

これによって、wolfSSLのソースコードがESP-IDFフォルダの`components\wolfssl`に、サンプルコードが`examples\protocols\wolfssl_*`にコピーされます。

続いて、コピー後のファイル`components\wolfssl\include\user_settings.h`に以下を追記。

```
#define WOLFSSL_ESPIDF
#define WOLFSSL_ESPWROOM32 
```

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

`idf.py build`してみたところ、以下のwarningが多数発生

```
In file included from C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/wolfssl/wolfssl/wolfcrypt/port/Espressif/esp32-crypt.h:45,
                 from C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/wolfssl/wolfssl/wolfcrypt/sha.h:73,
                 from C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/wolfssl/wolfssl/wolfcrypt/hash.h:35,
                 from C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/wolfssl/wolfcrypt/src/md5.c:40:
C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/esp32/include/rom/ets_sys.h:1:2: warning: #warning rom/ets_sys.h is deprecated, please use esp32/rom/ets_sys.h instead [-Wcpp]
 #warning rom/ets_sys.h is deprecated, please use esp32/rom/ets_sys.h instead
```

原因はwolfSSLの以下のファイル。
`components/wolfssl/wolfssl/wolfcrypt/port/Espressif/esp32-crypt.h`

この中に以下の記述があります。

```components/wolfssl/wolfssl/wolfcrypt/port/Espressif/esp32-crypt.h
#if ESP_IDF_VERSION_MAJOR >= 4 && ESP_IDF_VERSION_MINOR >= 1
#include <esp32/rom/ets_sys.h>
#else
#include <rom/ets_sys.h>
#endif
```

今回使ったESP-IDF v4.0.1では、`ESP_IDF_VERSION_MAJOR=4`, `ESP_IDF_VERSION_MINOR=0`が設定されます。なので`<rom/ets_sys.h>`がincludeされるのですが、ESP-IDFとしては`<esp32/rom/ets_sys.h>`を使って欲しいらしく、includeした`<rom/ets_sys.h>`内のwarningが表示されたことによるもの。
そのため、wolfSSLの`esp32-crypt.h`内の`#if`行を以下のように修正。同じファイル内に同様の記述が他に2行あるので、合わせて修正。

```components/wolfssl/wolfssl/wolfcrypt/port/Espressif/esp32-crypt.h
#if ESP_IDF_VERSION_MAJOR >= 4 && ESP_IDF_VERSION_MINOR >= 0
```

ビルドできたので`idf.py flash`したところ、こんな結果が出ました。ちゃんと動いているようです。

```
arg:-lng 0
------------------------------------------------------------------------------
 wolfSSL version 4.5.0
------------------------------------------------------------------------------
wolfCrypt Benchmark (block bytes 1024, min 1.0 sec each)
RNG                  1 MB took 1.011 seconds,    1.473 MB/s
AES-128-CBC-enc      6 MB took 1.004 seconds,    5.909 MB/s
AES-128-CBC-dec      5 MB took 1.004 seconds,    5.228 MB/s
AES-192-CBC-enc      6 MB took 1.000 seconds,    5.908 MB/s
AES-192-CBC-dec      5 MB took 1.000 seconds,    5.225 MB/s
AES-256-CBC-enc      6 MB took 1.001 seconds,    5.902 MB/s
AES-256-CBC-dec      5 MB took 1.001 seconds,    5.219 MB/s
AES-128-GCM-enc    375 KB took 1.006 seconds,  372.763 KB/s
AES-128-GCM-dec    375 KB took 1.006 seconds,  372.763 KB/s
AES-192-GCM-enc    375 KB took 1.021 seconds,  367.287 KB/s
AES-192-GCM-dec    375 KB took 1.021 seconds,  367.287 KB/s
AES-256-GCM-enc    375 KB took 1.029 seconds,  364.431 KB/s
AES-256-GCM-dec    375 KB took 1.029 seconds,  364.431 KB/s
ARC4                 8 MB took 1.001 seconds,    7.561 MB/s
RABBIT               5 MB took 1.002 seconds,    5.385 MB/s
3DES               450 KB took 1.031 seconds,  436.469 KB/s
MD5                 14 MB took 1.001 seconds,   13.756 MB/s
SHA                 14 MB took 1.000 seconds,   14.478 MB/s
SHA-256             14 MB took 1.001 seconds,   14.366 MB/s
SHA-512             16 MB took 1.000 seconds,   16.479 MB/s
HMAC-MD5            14 MB took 1.001 seconds,   13.609 MB/s
HMAC-SHA            14 MB took 1.000 seconds,   14.209 MB/s
HMAC-SHA256         14 MB took 1.001 seconds,   14.097 MB/s
HMAC-SHA512         16 MB took 1.000 seconds,   15.942 MB/s
PBKDF2             704 bytes took 1.044 seconds,  674.330 bytes/s
RSA     2048 public         52 ops took 1.005 sec, avg 19.327 ms, 51.741 ops/sec
RSA     2048 private         4 ops took 1.054 sec, avg 263.500 ms, 3.795 ops/sec
ECC      256 key gen         4 ops took 1.262 sec, avg 315.500 ms, 3.170 ops/sec
ECDHE    256 agree           4 ops took 1.260 sec, avg 315.000 ms, 3.175 ops/sec
ECDSA    256 sign            4 ops took 1.273 sec, avg 318.250 ms, 3.142 ops/sec
ECDSA    256 verify          2 ops took 1.220 sec, avg 610.000 ms, 1.639 ops/sec
CURVE  25519 key gen         3 ops took 1.279 sec, avg 426.333 ms, 2.346 ops/sec
CURVE  25519 agree           4 ops took 1.702 sec, avg 425.500 ms, 2.350 ops/sec
ED     25519 key gen        47 ops took 1.013 sec, avg 21.553 ms, 46.397 ops/sec
ED     25519 sign           42 ops took 1.018 sec, avg 24.238 ms, 41.257 ops/sec
ED     25519 verify         26 ops took 1.008 sec, avg 38.769 ms, 25.794 ops/sec
Benchmark complete
```

# PKCS#7 EnvelopedDataを作ってみる

## プログラム

wolfSSLのマニュアルはこちらにありますが、
[wolfSSL Manual](https://www.wolfssl.com/docs/wolfssl-manual/)
API Referenceは記述が少なかったり内容が古かったりするようで、あまり参考にならず。
なので、githubにあるサンプルプログラムを参考にしました。
[wolfSSL Example Applications](https://github.com/wolfSSL/wolfssl-examples)

EnvelopedDataでくるむ中身は、以前に作った温度センサの値を取得するプログラムを流用してその値を使います。
[ESP32で温度と湿度をAWS IoTへ送ってグラフ化してみた](https://qiita.com/hisashij/items/77dcc3082cc41b3ed6d4)

本当は、S/MIMEフォーマットにしてメールで送れたりすると良かったのですが、SMTP周りのコードを書くのが面倒だったので、ESP-IDF付属のサンプルを使ってHTTPでダウンロードする形にしました。

以下は、PKCS#7 EnvelopedDataを作るところと、できたファイルをHTTP Responseで返す部分。これをESP-IDF付属のサンプル`examples\protocols\http_server\simple`に組み込みました。

```EnvelopedData.c
#include "wolfssl/wolfcrypt/pkcs7.h"

extern const uint8_t testcert_der_start[] asm("_binary_testcert_der_start");
extern const uint8_t testcert_der_end[] asm("_binary_testcert_der_end");
static byte pkcs_der[3000];

static int encodeEnvelopedData(char* msg, byte* out, word32 outSz)
{
    int    ret;
    PKCS7* pkcs7;
    RNG    rng;

    ret = wc_InitRng(&rng);
    if (ret != 0){
        printf("wc_InitRng() failed, ret = %d\n", ret);
        return -1;
    }

    pkcs7 = wc_PKCS7_New(NULL, INVALID_DEVID);
    if (pkcs7 == NULL) {
        printf("wc_PKCS7_New() failed\n");
        return -1;
    }

    pkcs7->rng            = &rng;
    pkcs7->content        = (byte*)msg;
    pkcs7->contentSz      = strlen(msg);
    pkcs7->contentOID     = DATA;
    pkcs7->encryptOID     = AES256CBCb;

    /* add recipient using RSA certificate (KTRI type) */
    ret = wc_PKCS7_AddRecipient_KTRI(pkcs7, testcert_der_start,
        (testcert_der_end - testcert_der_start), 0);
    if (ret < 0) {
        printf("wc_PKCS7_AddRecipient_KTRI() failed, ret = %d\n", ret);
        wc_PKCS7_Free(pkcs7);
        return -1;
    }

    /* encode EnvelopedData, returns size */
    ret = wc_PKCS7_EncodeEnvelopedData(pkcs7, out, outSz);
    if (ret <= 0) {
        printf("ERROR: wc_PKCS7_EncodeEnvelopedData() failed, ret = %d\n", ret);
        wc_PKCS7_Free(pkcs7);
        return -1;

    } else {
        printf("Successfully encoded EnvelopedData bundle\n");
    }

    wc_PKCS7_Free(pkcs7);

    return ret;
}

/* An HTTP GET handler */
static esp_err_t enveloped_get_handler(httpd_req_t *req)
{
    int dataSz;
    char msg[100];

    sprintf(msg, "Temperature: %.2f, Humidity: %.2f", readTemp(), readHumid());

    if (0 >= (dataSz = encodeEnvelopedData(msg, pkcs_der, sizeof(pkcs_der)))) {
    	return ESP_FAIL;
	}

    httpd_resp_set_hdr(req, "Content-Type", "binary/octet-stream");
    httpd_resp_set_hdr(req, "Content-Disposition", "attachment; filename=test.p7m");
    httpd_resp_send(req, (char*)pkcs_der, dataSz);

    if (httpd_req_get_hdr_value_len(req, "Host") == 0) {
        ESP_LOGI(TAG, "Request headers lost");
    }
    return ESP_OK;
}

static const httpd_uri_t enveloped = {
    .uri       = "/enveloped",
    .method    = HTTP_GET,
    .handler   = enveloped_get_handler,
};
```

EnvelopedDataを作る際のRecipient証明書は、OpenSSLで適当に作成。プロジェクトフォルダの`certs/testcert.der`にDER形式で保存。対応するPrivate Keyは後で使います。
`CMakefile.txt`内の`idf_component_register`に`EMBED_FILES ${project_dir}/certs/testcert.der`を追加して、アプリに組み込みます。

また、PKCS#7を使う際は`HAVE_PKCS7`および関連する値をdefineしておく必要があるそうなので、`CMakeList.txt`に以下を追記します（これをしておかないと、必要関数がコンパイルされずリンク時にエラーになる）。

```CMakeList.txt
add_compile_options(-DHAVE_PKCS7)
add_compile_options(-DHAVE_AES_KEYWRAP)
add_compile_options(-DHAVE_X963_KDF)
add_compile_options(-DWOLFSSL_AES_DIRECT)
```

## ビルド

すんなりとは行かず、wolfSSLのソースをいくつか修正しました。以下、正しい対処方法なのかわかりませんが、記録として載せておきます。

### snprintfでコンパイルエラー

```
C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/wolfssl/wolfcrypt/src/asn.c: In function 'GetAsnTimeString':
C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/wolfssl/wolfcrypt/src/asn.c:6261:23: error: '%02d' directive output may be truncated writing between 2 and 11 bytes into a region of size between 5 and 12 [-Werror=format-truncation=]
                   "%4d%02d%02d%02d%02d%02dZ", year, mon, day, hour, mini, sec);
                       ^~~~
C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/wolfssl/wolfcrypt/src/asn.c:6261:19: note: directive argument in the range [-2147483647, 2147483647]
                   "%4d%02d%02d%02d%02d%02dZ", year, mon, day, hour, mini, sec);
                   ^~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/wolfssl/wolfssl/wolfcrypt/asn.h:37,
                 from C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/wolfssl/wolfcrypt/src/asn.c:68:
C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/wolfssl/wolfssl/wolfcrypt/types.h:497:31: note: 'snprintf' output between 16 and 68 bytes into a destination of size 16
             #define XSNPRINTF snprintf
C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/wolfssl/wolfcrypt/src/asn.c:6260:9: note: in expansion of macro 'XSNPRINTF'
         XSNPRINTF((char *)gt_str, ASN_GENERALIZED_TIME_SIZE,
         ^~~~~~~~~
C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/wolfssl/wolfcrypt/src/asn.c:6240:28: error: '%02d' directive output may be truncated writing between 2 and 11 bytes into a region of size between 0 and 10 [-Werror=format-truncation=]
                   "%02d%02d%02d%02d%02d%02dZ", year, mon, day, hour, mini, sec);
                            ^~~~
In file included from C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/wolfssl/wolfssl/wolfcrypt/asn.h:37,
                 from C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/wolfssl/wolfcrypt/src/asn.c:68:
C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/wolfssl/wolfssl/wolfcrypt/types.h:497:31: note: 'snprintf' output between 14 and 60 bytes into a destination of size 14
             #define XSNPRINTF snprintf
C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/wolfssl/wolfcrypt/src/asn.c:6239:9: note: in expansion of macro 'XSNPRINTF'
         XSNPRINTF((char *)utc_str, ASN_UTC_TIME_SIZE,
         ^~~~~~~~~
```

`-Wno-error=format-truncation=`では解消しなかったので、ソースを直接修正（snprintfの書込み先サイズを拡張）。

```components\wolfssl\wolfcrypt\src\asn.c
int GetAsnTimeString(void* currTime, byte* buf, word32 len)
{
/* (snip) */
        char utc_str[ASN_UTC_TIME_SIZE+16];
/* (snip) */
        XSNPRINTF((char *)utc_str, ASN_UTC_TIME_SIZE+16,
                  "%02d%02d%02d%02d%02d%02dZ", year, mon, day, hour, mini, sec);
/* (snip) */
        char gt_str[ASN_GENERALIZED_TIME_SIZE+16];
/* (snip) */
        XSNPRINTF((char *)gt_str, ASN_GENERALIZED_TIME_SIZE+16,
                  "%4d%02d%02d%02d%02d%02dZ", year, mon, day, hour, mini, sec);
/* (snip) */
}
```

### 変数の初期化漏れエラー

```
C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/wolfssl/wolfcrypt/src/pkcs7.c: In function 'wc_PKCS7_DecodeEncryptedData':
C:/IoT/ESP-IDF/ESP-IDF-v4.0.1/components/wolfssl/wolfcrypt/src/pkcs7.c:12206:42: error: 'padLen' may be used uninitialized in this function [-Werror=maybe-uninitialized]
                 ret = encryptedContentSz - padLen;
                       ~~~~~~~~~~~~~~~~~~~^~~~~~~~
```

`-Wno-error=maybe-uninitialized`
では解消しなかったので、ソースを直接修正（変数宣言時に初期化、2ヶ所）。

```components/wolfssl/wolfcrypt/src/pkcs7.c
    byte padLen = 0;
```

## PKCS#7 encoding実験

ビルドできたので`idf.py flash`で書込み。
`idf.py monitor`にてMSP32のIPを確認。

```
I (10142) tcpip_adapter: sta ip: 192.168.0.8, mask: 255.255.255.0, gw: 192.168.0.1
I (10142) example_connect: Connected to <SSID>
I (10142) example_connect: IPv4 address: 192.168.0.8
I (10152) example: Starting server on port: '80'
I (10162) example: Registering URI handlers
```

PCのブラウザで`http://192.168.0.8/enveloped`に接続すると、`test.p7m`がダウンロードされました。

まずは、`OpenSSL`の`asn1parse`コマンドでデータ構造を確認。ちゃんとPKCS#7 EnvelopedDataになっているようです。もちろんデータはAES 256bitで暗号化されているので読めません。

```
> openssl.exe asn1parse -i -inform DER -in test.p7m
    0:d=0  hl=4 l= 508 cons: SEQUENCE
    4:d=1  hl=2 l=   9 prim:  OBJECT            :pkcs7-envelopedData
   15:d=1  hl=4 l= 493 cons:  cont [ 0 ]
   19:d=2  hl=4 l= 489 cons:   SEQUENCE
   23:d=3  hl=2 l=   1 prim:    INTEGER           :00
   26:d=3  hl=4 l= 388 cons:    SET
   30:d=4  hl=4 l= 384 cons:     SEQUENCE
   34:d=5  hl=2 l=   1 prim:      INTEGER           :00
   37:d=5  hl=2 l= 104 cons:      SEQUENCE
   39:d=6  hl=2 l=  99 cons:       SEQUENCE
   41:d=7  hl=2 l=  11 cons:        SET
   43:d=8  hl=2 l=   9 cons:         SEQUENCE
   45:d=9  hl=2 l=   3 prim:          OBJECT            :countryName
   50:d=9  hl=2 l=   2 prim:          PRINTABLESTRING   :JP
   54:d=7  hl=2 l=  26 cons:        SET
   56:d=8  hl=2 l=  24 cons:         SEQUENCE
   58:d=9  hl=2 l=   3 prim:          OBJECT            :organizationName
   63:d=9  hl=2 l=  17 prim:          UTF8STRING        :Test Organization
   82:d=7  hl=2 l=  33 cons:        SET
   84:d=8  hl=2 l=  31 cons:         SEQUENCE
   86:d=9  hl=2 l=   3 prim:          OBJECT            :organizationalUnitName
   91:d=9  hl=2 l=  24 prim:          UTF8STRING        :Test Organizational Unit
  117:d=7  hl=2 l=  21 cons:        SET
  119:d=8  hl=2 l=  19 cons:         SEQUENCE
  121:d=9  hl=2 l=   3 prim:          OBJECT            :commonName
  126:d=9  hl=2 l=  12 prim:          UTF8STRING        :Test Root CA
  140:d=6  hl=2 l=   1 prim:       INTEGER           :07
  143:d=5  hl=2 l=  13 cons:      SEQUENCE
  145:d=6  hl=2 l=   9 prim:       OBJECT            :rsaEncryption
  156:d=6  hl=2 l=   0 prim:       NULL
  158:d=5  hl=4 l= 256 prim:      OCTET STRING      [HEX DUMP]:8AD2004F298A004CE9981C21F6D7EF0289A98D7963706AA28E65A25A6990BAB2440210CCE3B7D063694C9A898BF36D657A4BB16D73460E267F12EEB1AEB4CA60D6DFA8CD407C584F33EB7D1F5B89326A1A7078015A5A17EDAC08E0BEACFEF236F14AF6AFE0701F2009CEB1AD897FD783659D7A426A8E1A691A77B1F0981379E4CBD71563782572643BA42511B990A9AB45EAF4ADC3047770CC96D34C50D1E93BA04FC7117E6AE825BB83276AF33B1218A404672916D052E58F66FD36412D812F39845E794BAA349A853CED5DA4CE35EBEC7CD4E9FF737443DFBB2F336790439F5B091919495468CA9C6F680F9283C83DF755879498059AE70A6481F6BCC6AF39
  418:d=3  hl=2 l=  92 cons:    SEQUENCE
  420:d=4  hl=2 l=   9 prim:     OBJECT            :pkcs7-data
  431:d=4  hl=2 l=  29 cons:     SEQUENCE
  433:d=5  hl=2 l=   9 prim:      OBJECT            :aes-256-cbc
  444:d=5  hl=2 l=  16 prim:      OCTET STRING      [HEX DUMP]:26FADBEA4DC07CD294062AB03ED6F99B
  462:d=4  hl=2 l=  48 prim:     cont [ 0 ]
```

次に、`OpenSSL`の`SMIME`コマンドで中を見てみます。

```
> openssl.exe smime -decrypt -inform DER -in test.p7m -recip testcert.cer -inkey testcert.key
Temperature: 30.34, Humidity: 52.28
```

復号して中を見ることができました！

ちゃんと機能しているようです。このライブラリがあればいろいろなデバイスで暗号処理できそうです。