セキュアエレメント搭載のM5StackでAzure IoT Hubへ接続してみた

IoT

はじめに

こんにちは。CTO室のringoです。
M5StackとAWS IoTの記事は多く見かけるのですが、Azure IoTについての記事は少ないなと思ったので、セキュアエレメントが搭載されたM5Stack Core2 for AWSでAzure IoTへの接続を試してみました。
この記事で説明するポイントは2つあります。
1つ目は搭載されているセキュアエレメント内の秘密鍵を使用してデバイスの認証を行う点です。
ESP32でAzure IoT HubにアクセスするサンプルがMicrosoftから公開されているのですが、そのままだとデバイスの認証情報(トークンや証明書や秘密鍵)をプログラムにハードコードしています。ハードコードしてしまうと悪意のある攻撃者がデバイスからプログラムを読み出し、簡単に解析されてしまう危険性があります。鍵が盗まれることを防ぐには安全なハードウェア内に秘密鍵を保管することが有効です。
2つ目のポイントは、IoT HubのCA証明書を用いたデバイス認証の仕組みをM5Stackで利用している点です。

デバイスをAzure IoT Hubへアクセスする方法については以下の順序で説明します。

  1. CA証明書を作成しIoT Hubへ登録する
  2. Azure IoT Hub へデバイスを登録する
  3. ESP32でCSRを作成して出力
  4. デバイス証明書を作成する
  5. デバイス証明書を提示してアクセス

必要なモノ

M5Stack Core2 for AWSについて簡単に説明すると、Microchip製のセキュアエレメントATECC608Aが搭載されたM5Stack Core2です。MCUにはESP32マイコンが搭載されており、タッチパネル、LED、振動モーター、RTCといったモジュールが一体になっているIoTデバイスです。

Azure IoT Hubについては無料枠内で実行できる範囲なのでフリーのAzureアカウントで問題ないです。

CA証明書をAzure IoT Hubへ登録する

Azure IoT Hubにデバイスを登録する方法はいくつかあるのですが、今回はCA証明書を使用したデバイス認証について説明します。
Azure IoT Hubでは事前にCA証明書を登録しておくことで、登録されたCAから発行された証明書を持つデバイスを認証することができます。こうすることでIoTデバイスのプロビジョニングを簡略化することができます。

実際の手順を解説します。

Opensslでルート証明書を作成

まずはIoT Hubへ登録するCA証明書を作成します。

openssl genrsa -out testCA.key 2048
openssl req -new -key testCA.key -out testCA.csr \
        -subj "/C=JP/ST=Tokyo/O=GSHD/CN=testCA"
openssl x509 -req -in testCA.csr -signkey testCA.key -sha256 -days 1024 -out testCA.pem -outform PEM

1行目で秘密鍵の生成、2行目でCSRを作成、4行目でPEM形式で証明書を作成しています。

Azure IoT Hubに登録

Azureポータルを開き、IoT Hubから自分のIoT Hubを選択します。
セキュリティ設定 → 証明書から追加を選択し、CA証明書をアップロードします。

証明書の状態を”アップロード時に確認済みに設定する”にチェックを入れ、保存を選択します。
証明書一覧にアップロードしたCA証明書が追加されたらCA証明書の登録は完了です。

今回の記事ではOpenSSLを使用して自己認証局を作成し俗に言うオレオレ証明書を使用していますが、実際の運用では公的な認証局が発行した証明書を利用することが望ましいです。

Azure IoT Hubへデバイスを登録する

AzureポータルのIoT Hubからデバイス管理 → デバイスからを選択し、デバイスの追加を選択します。

デバイスIDの入力が求められるので任意のデバイス名を入力します。ここではm5stackcore2forawsとしました。
認証の種類を”X.509 CA署名済み”を選択し、保存を選択するとデバイスの登録は完了です。

デバイス証明書を作成する

最初に作成したCA証明書で署名したデバイス証明書を作成する必要があるのですが、ATECC608Aはセキュアエレメントであり、秘密鍵を取り出すことはできません。そこでESP32でATECC608A内部の秘密鍵を元にしたCSRを作成するプログラムを動かします。そして出力されたCSRにCA証明書で署名することでデバイス証明書を作成します。

プロジェクトの用意

使用するプログラムはMicrosoftが提供しているFreeRTOSのサンプルを使います。
Azure IoT samples using Azure IoT middleware for FreeRTOS

このリポジトリのdemos/projects/ESPRESSIF/esp32/にあるプロジェクトを使います。

git clone https://github.com/Azure-Samples/iot-middleware-freertos-samples.git
cd iot-middleware-freertos-samples
git submodule update --init --recursive

esp-cryptoauthlibのインクルード

ESP32でATECC608Aを使用するためのライブラリがespressifから公開されているのでそれを使用します。

ESP-CRYPTOAUTHLIB

esp-idfのサンプルにesp-cryptoauthlibのサンプルプロジェクトがあるので、こちらを参考にコーディングします。

espressif/esp-idf/tree/master/examples/peripherals/secure_element/atecc608_ecdsa

プロジェクトにesp-cryptoauthlibを追加します。

cd components
git clone https://github.com/espressif/esp-cryptoauthlib.git

リポジトリをcloneしてきたら idf.py menuconfigコマンドを実行しATECC608Aを使用するための設定を入力します。
”Component config → esp-cryptauthlib” を選択し画像のように設定します。

今回使用したM5Stack Core2 for AWSではATECC608AのピンはSDAが21, SCLが22でした。リビジョンによって違いがあるかもしれないので確認して設定してください。

CSRを作成する

プロジェクトにESP32でCSRを生成するプログラムを追加します。
以下に示すのはmbedTLSでCSRを生成するソースコードです。

static int atca_generate_csr(void)
{

    mbedtls_pk_context pkey;
    int ret;

    unsigned char csr_out[1024];
    const char *common_name = "m5stackcore2foraws";
    /* Convert to an mbedtls key */
    ESP_LOGI(TAG, " Using a hardware private key ...");
    ret = atca_mbedtls_pk_init(&pkey, 0);
    if (ret != 0)
    {
        ESP_LOGI(TAG, " failed !  atca_mbedtls_pk_init returned %02x", ret);
        goto exit;
    }
    ESP_LOGI(TAG, " ok");

    const char *pers = "gen_csr";
    mbedtls_x509write_csr csr;
    mbedtls_ctr_drbg_context ctr_drbg;
    mbedtls_entropy_context entropy;

    /* Generating CSR from the private key */
    mbedtls_x509write_csr_init(&csr);
    mbedtls_x509write_csr_set_md_alg(&csr, MBEDTLS_MD_SHA256);
    mbedtls_ctr_drbg_init(&ctr_drbg);

    ESP_LOGI(TAG, "Seeding the random number generator.");
    mbedtls_entropy_init(&entropy);
    ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *)pers, strlen(pers));
    if (ret != 0)
    {
        ESP_LOGE(TAG, "mbedtls_ctr_drbg_seed returned -0x%04x", -ret);
        goto exit;
    }

    char subject_name[50];
    snprintf(subject_name, sizeof(subject_name), "CN=%s", common_name);
    ret = mbedtls_x509write_csr_set_subject_name(&csr, subject_name);
    if (ret != 0)
    {
        ESP_LOGE(TAG, "mbedtls_x509write_csr_set_subject_name returned %d", ret);
        goto exit;
    }

    memset(csr_out, 0, sizeof(csr_out));
    mbedtls_x509write_csr_set_key(&csr, &pkey);

    ESP_LOGI(TAG, "Generating PEM");
    ret = mbedtls_x509write_csr_pem(&csr, csr_out, sizeof(csr_out), mbedtls_ctr_drbg_random, &ctr_drbg);
    if (ret < 0)
    {
        ESP_LOGE(TAG, "mbedtls_x509write_csr_pem returned -0x%04x", -ret);
        goto exit;
    }
    ESP_LOGI(TAG, "CSR generated.");
    ESP_LOGI(TAG, "Modified CSR : %s", csr_out);
exit:

    mbedtls_x509write_csr_free(&csr);
    mbedtls_ctr_drbg_free(&ctr_drbg);
    mbedtls_entropy_free(&entropy);

    return ret;
}

11行目でatca_mbedtls_pk_init(&pkey, 0)でATECC608Aの秘密鍵を使うように指示している以外は、mbedTLSでCSRを生成するプログラムと変わりないです。
このときIoT Hubに登録するデバイスIDとデバイス証明書のCN(Common Name)を一致させる必要があるので注意してください。
作成したプログラムを次のコマンドでビルドします。
idf.py –no-ccache -B “C:\espbuild” build
–no-ccacheでディレクトリを指定しているのは、ビルド時にパスが深くなりすぎるとビルドエラーが起きるためです。
ビルドが完了したら以下のコマンドでプログラムをESP32へ書き込みます。
idf.py –no-ccache -B “C:\espbuild” -p “COMポート” flash monitor
書き込みが完了し、M5Stackが再起動するとプログラムが実行されコンソールにCSRが表示されます。
接続情報などを書き込んでないのでこの時点ではIoT Hubへの接続は失敗します。

表示されたCSRをコピペして適当なファイルに保存し、opensslで中身を見ると以下のように表示されました。

openssl req -in m5core2atca.csr -text -noout
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: CN = m5stackcore2foraws
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:62:1d:19:b6:56:89:c4:ed:5e:85:94:32:ff:ef:
                    63:84:b6:c3:69:c3:89:a7:bb:ab:aa:fc:df:fa:30:
                    e0:b5:2c:83:4e:d6:42:5b:f2:35:bc:01:4c:dc:d7:
                    62:63:54:3c:ef:82:1a:21:8c:b0:25:b7:c3:1c:c7:
                    1a:84:7a:b9:86
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        Attributes:
            a0:00
    Signature Algorithm: ecdsa-with-SHA256
         30:45:02:21:00:89:45:6c:eb:cd:dc:73:f5:76:a4:6a:26:82:
         fc:fe:f0:69:17:42:dd:4f:97:b8:83:4d:b0:40:f0:af:b7:01:
         ce:02:20:60:1a:ac:8a:10:a0:01:9b:02:ba:cc:fb:f9:17:0d:
         c2:46:09:5c:94:dc:c8:3f:be:40:a3:2d:46:b7:c2:67:a9

きちんとCSRとして出力されています。

デバイス証明書の作成

作成したCSRを元に作成したCAで署名をします。

openssl x509 -req -in m5core2atca.csr \
        -CA testCA.pem -CAkey testCA.key -CAcreateserial \
        -out m5core2atca.pem -days 365 -sha256

証明書が発行されたか確認のために中身を表示します。

openssl x509 -in m5core2atca.pem -text -noout
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            49:24:79:ad:97:e8:53:50:77:37:57:fe:5d:d2:81:7c:47:95:72:9b
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = JP, ST = Tokyo, O = GSHD, CN = GSHDtestCA
        Validity
            Not Before: Jul 19 16:13:33 2022 GMT
            Not After : Jul 19 16:13:33 2023 GMT
        Subject: CN = m5stackcore2foraws
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:62:1d:19:b6:56:89:c4:ed:5e:85:94:32:ff:ef:
                    63:84:b6:c3:69:c3:89:a7:bb:ab:aa:fc:df:fa:30:
                    e0:b5:2c:83:4e:d6:42:5b:f2:35:bc:01:4c:dc:d7:
                    62:63:54:3c:ef:82:1a:21:8c:b0:25:b7:c3:1c:c7:
                    1a:84:7a:b9:86
                ASN1 OID: prime256v1
                NIST CURVE: P-256
    Signature Algorithm: sha256WithRSAEncryption
         6e:03:6f:74:47:af:90:ba:57:87:c7:31:87:34:e1:ab:b2:69:
         df:9c:2c:15:28:03:50:8b:18:b0:9f:b1:75:e4:a6:ee:73:ba:
         80:b8:21:07:69:fc:71:e2:e3:b3:15:74:b3:8e:e7:c3:31:46:
         67:86:d7:cd:8c:85:f3:d6:1e:cd:bf:1a:8a:a8:2c:39:36:3d:
         d9:5d:d9:d2:1f:bb:10:f9:1c:a8:72:72:e6:13:8c:19:f4:07:
         a2:c2:21:2c:2b:16:35:0c:20:a8:3f:97:d8:2b:6a:32:30:1b:
         02:ad:7d:27:c2:cd:cf:49:8e:b4:29:22:e3:54:37:28:d9:2b:
         62:ec:11:cd:74:8b:df:f9:5c:6f:22:5e:63:53:cc:cb:bf:a9:
         81:a4:48:67:e8:c0:d8:08:95:95:e5:04:0e:75:47:01:7d:a0:
         5e:33:7d:35:cc:81:b7:f0:19:96:ce:a9:98:17:77:bc:b9:56:
         0d:fc:5c:2c:53:f3:68:53:dd:8e:eb:14:25:08:b9:25:25:50:
         c0:ec:f7:6b:d3:91:7b:2f:5c:4d:00:40:6d:7c:ed:fc:dc:8f:
         d8:8a:e3:c7:03:72:70:63:43:85:a2:d1:9b:5e:06:1b:2f:4e:
         c0:24:b4:f7:ef:76:c6:04:30:62:5f:bc:3a:5b:6f:99:fa:3c:
         29:6d:cc:23

ここまででIoT Hubに登録したCAから発行された、ATECC608A内部の秘密鍵に対応するデバイス証明書ができました。

ATECC608A内の秘密鍵を使用してAzure IoT Hubへアクセス

TLS層の実装

やりたいこととしてはATECC608A内の秘密鍵をTLSのクライアント認証で使うことです。そのためにはTLSの処理部分にATECC608Aを使うようにプログラムをカスタマイズする必要があります。

https://github.com/Azure/azure-iot-middleware-freertos
Azure IoT Middleware for FreeRTOSより

ライブラリのアーキテクチャ図を見るとMQTT、TLS、TCP/IP とレイヤーごとに抽象化されています。TLS部分はMbedTLSと書かれているので、実装の方針としては現在のプロジェクトのTLS部分を探し出して、TLS認証時にATECC608Aを使うように実装する。という方向で実現できそうです。

esp32のプロジェクトのTLS がどうなっているかソースコードを読むと、ESP-TLSで実装されています。

iot-middleware-freertos-samples/blob/main/demos/projects/ESPRESSIF/esp32/components/sample-azure-iot/transport_tls_esp32.c

この部分をATECC608Aを使うようにmbedTLSで書き換えると、目的通りセキュアエレメント内の秘密鍵を利用してIoT Hubにアクセスできるようになるはずです。

mbedTLSでの実装は以下のコードを参考に秘密鍵を読み込む部分をATECC608Aを使うように書き換えつつ実装しました。

iot-middleware-freertos-samples/blob/main/demos/common/transport/transport_tls_socket_using_mbedtls.c

ソースコードを全て載せると長くなるので要点だけ説明すると、CSRを作成したときと同じように、秘密鍵の読み込み部分をatca_mbedtls_pk_init(&(pxSslContext->privKey), 0); へと変更し、ATECC608Aを使うようにしました。
該当部分はこちらです。

プロジェクトの設定

esp32のプロジェクトのディレクトリまで移動し、idf.py menuconfig コマンドを実行しWiFiの設定やデバイスID、証明書等を入力します。

Azure IoT middleware for FreeRTOS Sample Configuration内でWiFiのSSID、パスワードを、Azure IoT middleware for FreeRTOS Main Task Configuration内でIoT Hubのホスト名、デバイスID(デバイス証明書のCN)、デバイス証明書を設定します。
デバイス証明書は改行を\nに置き換えた1行の文字列として貼り付けることに注意してください。

プログラムの書き込みと実行

コンソールからの確認だけではなくAzure 上でM5Stackから送信されたデータを確認します。Azure Portalの上部のバーからクラウドシェルを立ち上げ以下のコマンドを実行します。

az iot hub monitor-events –hub-name “your IoT Hub Name”

左側がAzureのクラウドシェルの画面で、右側のTeraTermがM5Stackの出力結果です。サンプルコードそのままなので2回目の送信のtemperatureが0.00ですが、正しくデータは届いていることが確認できました。

コンソールの出力結果です。画像だけだと分かりづらいのですがハンドシェイクの際にATECC608Aから秘密鍵を読み込んで、IoT Hubへアクセスすることができました。

まとめ

CA証明書を用いた認証とセキュアエレメントを用いることで鍵を安全に保管し、デバイスをAzure IoT Hubへ接続できました。一度デバイスからCSRを取り出して、証明書を発行するというのは少し手間だと感じましたが、IoTデバイスで秘密鍵を安全に管理するのはセキュリティ上必要なことなので、もっと簡単に扱える方法がないか試行錯誤したいと思います。

最後に完成したプロジェクトがこちらです。
Connect an ESPRESSIF ESP32 using Azure IoT Middleware for FreeRTOS

参考ページ