ESP32のデバイスでFleetProvisoningを行い、証明書とモノを準備する

# はじめに
AWS IoTではデバイスとクラウドの間でセキュアな通信を行うために、デバイスに個別の認証情報をうめこみ、デバイスを認証するためのいくつかの方法を提供しています。今回はESP32のデバイスを用いて、[FleetProvisoning](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/provision-wo-cert.html) を行ってデバイス証明書と秘密鍵を作成し、デバイスとクラウドの間でセキュアな通信を実現する方法を試していきたいと思います。

# 証明書登録方法の分類
[こちらの資料](https://pages.awscloud.com/rs/112-TZM-766/images/EV_iot-deepdive-aws2_Sep-2020.pdf)に証明書の登録方法が分類されています。以下は資料の抜粋です。
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f38303032382f32373139303934622d633737312d653962612d663835302d6162393835376536643934352e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/0e1711e1-86d4-5fb0-e620-d213554be009.png)
今回試してみるFleet Provisoningは、Amazonが管理するCAを使用して、大量のデバイスが初回接続時に証明書を発行しモノを作成、Policyを紐付ける方法となります。事前に工場等で証明書をデバイスに入れておかなくてもよいというメリットがあります。

# 事前準備
[以前の記事](https://qiita.com/tsugunao/items/e6058df682728a94ef40)で使用した以下の環境を使用してFleet Provisoningを行っていきます。
* [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/release-v4.4/esp32/get-started/index.html)のツールチェーン
* [esp-aws-iot](https://github.com/espressif/esp-aws-iot/tree/release/beta)上のサンプルコード

適当な作業用のディレクトリを作成し、そこに ESP-IDFと esp-aws-iotをCloneしてきます。

## 前提条件のインストール
```
brew install cmake ninja dfu-util
brew install python3
```
## ESP-IDFのインストール
```
cd ~/Work/esp32/v44
git clone -b release/v4.4 --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh esp32
. ~/Work/esp32/v44/esp-idf/export.sh
```
## esp-aws-iot の取得
[こちら](https://github.com/espressif/esp-aws-iot)からesp-aws-iotを取得してきます。
examples以下に[FreetProvisioning のサンプルプログラム](https://github.com/espressif/esp-aws-iot/tree/master/examples/fleet_provisioning/fleet_provisioning_with_csr)があります。
```
cd ~/Work/esp32
git clone -b "release/202210.01-LTS" --recursive https://github.com/espressif/esp-aws-iot
cd esp-aws-iot/examples/fleet_provisioning/fleet_provisioning_with_csr
```

# Fleet Provisioning
 [FleetProvisoning](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/provision-wo-cert.html)には、1）信頼されたユーザーによるプロビジョニングと 2）クレームによるプロビジョニングの2つの方法があります。今回は、2）クレームによるプロビジョニングを行います。この方法では、デバイスにクレーム証明書とプライベートキーを埋め込んでおいて、クレーム証明書にはプロビジョニングだけを行う権限を付与しておいて、そのクレーム証明書を用いて通常使用する証明書を発行してデバイスに保存する、ということを行います。まずはクレーム証明書の作成を行います。
## クレーム証明書の作成とPolicyのアタッチ
クレームのための証明書を作成し、そこに必要な権限を与えるPolicyをアタッチして行きます。AWS IoT Coreのコンソールログインし、左側のメニューの```セキュリティ```> ```ポリシー```> ```ポリシーの作成```をクリックし、ポリシーの作成を行います。ポリシー名を入力し、ポリシーステートメントのところで```JSON```を選択し、[こちらのサンプル](https://github.com/espressif/esp-aws-iot/blob/master/examples/fleet_provisioning/fleet_provisioning_with_csr/example_claim_policy.json)を参照し、```<aws-region>```
, ```<aws-account-id>```, ```<template-name>```の箇所はご自身で使用している値になるように書き換えて設定します。(templateは次で作成しますので作成予定の名前を入れておきます) ![スクリーンショット 2023-06-21 13.59.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/b4449dfb-dce3-b3e7-3ad2-800bd221e1ee.png)
次に、左側のメニューの```セキュリティ```> ```証明書```> ```証明書の追加```>```証明書を作成```をクリックし
証明書として```新しい証明書の自動生成(推奨)```を選択、
証明書のステータスとして```アクティブ```を選択し、作成をクリックします。![スクリーンショット 2023-06-21 14.12.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/926deef2-cf6a-70c0-f671-dc20dcd4ade6.png)
ポップアップが立ち上がり、証明書とキーがダウンロードできるようになるので、デバイス証明書とプライベートキーファイルをローカルにダウンロードしておきます。
![スクリーンショット 2023-06-21 14.15.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/2d3b8b97-dd28-0f02-6232-3d59ef18edb4.png)
証明書が作成されるので、証明書を表示し、先程作成したPolicyをアタッチしておきます。
![スクリーンショット 2023-06-21 14.17.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/6ae927ac-5396-4fd6-d512-20262593f061.png)

## Provisoining Templateの作成
次に、AWS IoT Coreのコンソールから、左側のメニューの```接続```> ```多数のデバイスを接続```> ```プロビジョニングテンプレート```をクリックし、```クレーム証明書を使用したデバイスのプロビジョニング```を選択し、```次へ```をクリックします。
![スクリーンショット 2023-06-21 13.42.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/828d426e-1a70-e508-2b46-2dbee9758b99.png)

 ```プロビジョニングテンプレートステータス```は```アクティブ```に設定し、適切なプロビジョニングテンプレート 名を入力、プロビジョニングロールは、```新しいロールを作成```ボタンからロールを作成します。```クレーム証明書ポリシー```では先程作成したPolicyを選択し、```クレーム証明書```では先ほど作成した証明書を選択し,
```次へ```をクリックします。
![スクリーンショット 2023-06-21 14.26.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/2cde168f-4eb7-e8fe-b67b-7d9e18237970.png)

プロビジョニングアクションのページでは、必要に応じて事前プロビジョニングアクションを設定します。この設定を行うとプロビジョニング時にLambdaが起動されてそこで設定等を行うことができます。モノの自動作成の箇所では、モノのリソースを自動的に作成する、を選択して、モノの名前のプレフィックスを設定します。必要に応じてここで、モノのタイプや属性、グループの設定も可能です。プロビジョニング中にデバイスにKey-Value形式で設定データを渡すことも可能です。 ```次へ```をクリックします。
![スクリーンショット 2023-06-21 14.33.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/5c3d8096-0975-eaaf-d811-5d26ba0ce5f0.png)

最後に、デバイス自身に与えるアクセス許可用のPolicyを選択して設定します。
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f38303032382f64333233356433342d313466622d393833372d373065622d3762313362353866303535372e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/351d050a-d812-2e52-1bb4-c98657273d25.png)

```次へ```をクリックすると全体の確認画面が表示されるので、```テンプレートを作成```をクリックします。

## FleetProvisioning sample プログラムの実行
次に、PC上のTerminalに戻り、fleet provisioning sampleプログラムを実行していきます。
```
cd esp-aws-iot/examples/fleet_provisioning/fleet_provisioning_with_csr
idf.py menuconfig
```
Example Configrationのところで、MQTT BrokerのEndpointの設定を行い、Provisoning Templateの名前を設定する箇所があるので、先程作成したProvisioning Templateの名前を設定します。デバイスのシリアル番号が入れられる箇所があるので、シリアル番号を設定しておきます。また、Example Connection Configrationのところで、ご自身の環境のWiFiの設定を行います。
![スクリーンショット 2023-06-21 14.50.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/ac70308f-e440-1ca7-2852-ce6f4491452f.png)

次に、 ```fleet_provisioning_with_csr/spiffs_image/certs/claim_cert.crt ``` と ```claim_private.key ``` のファイルをクレーム証明書の作成のところで作成しダウンロードしておいたデバイス証明書と秘密鍵に置き換えます。

また、AmazonRoot CAを取得して、fleet_provisioning_with_csr/spiffs_image/certs/root_cert_auth.crt として保存します。
```
wget https://www.amazontrust.com/repository/AmazonRootCA1.pem -O certs/root_cert_auth.crt
```

次に、ESP32のデバイスをUSBに接続し、以下のようにプログラムを実行します。

```
ls /dev/cu.*
/dev/cu.Bluetooth-Incoming-Port	/dev/cu.URT0
/dev/cu.usbserial-14240
idf.py -p /dev/cu.usbserial-14240 flash monitor
```
以下のようにコンソールに表示され、FleetProvisioningが行われます。logを確認すると、証明書が発行され、モノが作成されていることが分かります。

```
Executing action: all (aliases: build)
Running ninja in directory /Users/tsugunao/Work/esp32/esp-aws-iot/examples/fleet_provisioning/fleet_provisioning_with_csr/build
Executing "ninja all"...
[1/5] cd /Users/tsugunao/Work/esp32/esp-aws-iot/examples/fleet_provisioning/f...rovisioning/fleet_provisioning_with_csr/build/fleet_provisioning_with_csr.bin
fleet_provisioning_with_csr.bin binary size 0xe7160 bytes. Smallest app partition is 0x100000 bytes. 0x18ea0 bytes (10%) free.
[3/5] Performing build step for 'bootloader'
[1/1] cd /Users/tsugunao/Work/esp32/esp-aws-iot/examples/fleet_provisioning/fleet_provisioning_with_csr/build/bootloader/esp-idf/esptool_py && /Users/tsugunao/.espressif/python_env/idf4.4_py3.10_env/bin/python /Users/tsugunao/Work/esp32/v44/esp-idf/components/partition_table/check_sizes.py --offset 0x8000 bootloader 0x1000 /Users/tsugunao/Work/esp32/esp-aws-iot/examples/fleet_provisioning/fleet_provisioning_with_csr/build/bootloader/bootloader.bin
Bootloader binary size 0x6440 bytes. 0xbc0 bytes (10%) free.
[5/5] Completed 'bootloader'
Executing action: flash
Running ninja in directory /Users/tsugunao/Work/esp32/esp-aws-iot/examples/fleet_provisioning/fleet_provisioning_with_csr/build
Executing "ninja flash"...
[1/6] cd /Users/tsugunao/Work/esp32/esp-aws-iot/examples/fleet_provisioning/f...rovisioning/fleet_provisioning_with_csr/build/fleet_provisioning_with_csr.bin
fleet_provisioning_with_csr.bin binary size 0xe7160 bytes. Smallest app partition is 0x100000 bytes. 0x18ea0 bytes (10%) free.
[3/6] Performing build step for 'bootloader'
[1/1] cd /Users/tsugunao/Work/esp32/esp-aws-iot/examples/fleet_provisioning/fleet_provisioning_with_csr/build/bootloader/esp-idf/esptool_py && /Users/tsugunao/.espressif/python_env/idf4.4_py3.10_env/bin/python /Users/tsugunao/Work/esp32/v44/esp-idf/components/partition_table/check_sizes.py --offset 0x8000 bootloader 0x1000 /Users/tsugunao/Work/esp32/esp-aws-iot/examples/fleet_provisioning/fleet_provisioning_with_csr/build/bootloader/bootloader.bin
Bootloader binary size 0x6440 bytes. 0xbc0 bytes (10%) free.
[5/6] cd /Users/tsugunao/Work/esp32/v44/esp-idf/components/esptool_py && /usr...s/tsugunao/Work/esp32/v44/esp-idf/components/esptool_py/run_serial_tool.cmake
esptool.py esp32 -p /dev/cu.usbserial-142410 -b 460800 --before=default_reset --after=hard_reset write_flash --flash_mode dio --flash_freq 40m --flash_size 4MB 0x1000 bootloader/bootloader.bin 0x20000 fleet_provisioning_with_csr.bin 0x8000 partition_table/partition-table.bin 0x120000 spiffs_storage.bin
esptool.py v3.3.4-dev
Serial port /dev/cu.usbserial-142410
Connecting....
Chip is ESP32-D0WD-V3 (revision v3.0)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: 8c:4b:14:39:e3:f8
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 460800
Changed.
Configuring flash size...
Flash will be erased from 0x00001000 to 0x00007fff...
Flash will be erased from 0x00020000 to 0x00107fff...
Flash will be erased from 0x00008000 to 0x00008fff...
Flash will be erased from 0x00120000 to 0x0020ffff...
Compressed 25664 bytes to 16188...
Writing at 0x00001000... (100 %)
Wrote 25664 bytes (16188 compressed) at 0x00001000 in 0.8 seconds (effective 271.6 kbit/s)...
Hash of data verified.
Compressed 946528 bytes to 582134...
Writing at 0x00020000... (2 %)
Writing at 0x0002d88d... (5 %)
Writing at 0x0003c325... (8 %)
Writing at 0x00047709... (11 %)
Writing at 0x0005101c... (13 %)
Writing at 0x00057081... (16 %)
Writing at 0x0005c4e9... (19 %)
Writing at 0x00062133... (22 %)
Writing at 0x00067b23... (25 %)
Writing at 0x0006d57b... (27 %)
Writing at 0x00073072... (30 %)
Writing at 0x000788a7... (33 %)
Writing at 0x0007dfd5... (36 %)
Writing at 0x0008380d... (38 %)
Writing at 0x000891c1... (41 %)
Writing at 0x0008e853... (44 %)
Writing at 0x000943c6... (47 %)
Writing at 0x00099a50... (50 %)
Writing at 0x000a034f... (52 %)
Writing at 0x000a5fe0... (55 %)
Writing at 0x000abb50... (58 %)
Writing at 0x000b11c7... (61 %)
Writing at 0x000b64d5... (63 %)
Writing at 0x000bb8a7... (66 %)
Writing at 0x000c0d2f... (69 %)
Writing at 0x000c6414... (72 %)
Writing at 0x000cbccd... (75 %)
Writing at 0x000d17cf... (77 %)
Writing at 0x000d7241... (80 %)
Writing at 0x000dcbee... (83 %)
Writing at 0x000e25f8... (86 %)
Writing at 0x000ebedb... (88 %)
Writing at 0x000f359a... (91 %)
Writing at 0x000f913d... (94 %)
Writing at 0x000fecc1... (97 %)
Writing at 0x001041bd... (100 %)
Wrote 946528 bytes (582134 compressed) at 0x00020000 in 15.5 seconds (effective 489.2 kbit/s)...
Hash of data verified.
Compressed 3072 bytes to 136...
Writing at 0x00008000... (100 %)
Wrote 3072 bytes (136 compressed) at 0x00008000 in 0.1 seconds (effective 463.8 kbit/s)...
Hash of data verified.
Compressed 983040 bytes to 6368...
Writing at 0x00120000... (100 %)
Wrote 983040 bytes (6368 compressed) at 0x00120000 in 6.5 seconds (effective 1210.1 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...
Executing action: monitor
Running idf_monitor in directory /Users/tsugunao/Work/esp32/esp-aws-iot/examples/fleet_provisioning/fleet_provisioning_with_csr
Executing "/Users/tsugunao/.espressif/python_env/idf4.4_py3.10_env/bin/python /Users/tsugunao/Work/esp32/v44/esp-idf/tools/idf_monitor.py -p /dev/cu.usbserial-142410 -b 115200 --toolchain-prefix xtensa-esp32-elf- --target esp32 --revision 0 /Users/tsugunao/Work/esp32/esp-aws-iot/examples/fleet_provisioning/fleet_provisioning_with_csr/build/fleet_provisioning_with_csr.elf -m '/Users/tsugunao/.espressif/python_env/idf4.4_py3.10_env/bin/python' '/Users/tsugunao/Work/esp32/v44/esp-idf/tools/idf.py' '-p' '/dev/cu.usbserial-142410'"...
--- idf_monitor on /dev/cu.usbserial-142410 115200 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
ets Jul 29 2019 12:21:46

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:6624
load:0x40078000,len:15140
ho 0 tail 12 room 4
load:0x40080400,len:3816
0x40080400: _init at ??:?

entry 0x40080698
I (29) boot: ESP-IDF v4.4.4-435-g7641c8ef4f 2nd stage bootloader
I (29) boot: compile time 18:27:11
I (29) boot: chip revision: v3.0
I (34) boot.esp32: SPI Speed      : 40MHz
I (38) boot.esp32: SPI Mode       : DIO
I (43) boot.esp32: SPI Flash Size : 4MB
I (47) boot: Enabling RNG early entropy source...
I (53) boot: Partition Table:
I (56) boot: ## Label            Usage          Type ST Offset   Length
I (64) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (71) boot:  1 storage          WiFi data        01 02 0000f000 00004000
I (79) boot:  2 phy_init         RF data          01 01 00013000 00001000
I (86) boot:  3 factory          factory app      00 00 00020000 00100000
I (93) boot:  4 spiffs_storage   Unknown data     01 82 00120000 000f0000
I (101) boot: End of partition table
I (105) esp_image: segment 0: paddr=00020020 vaddr=3f400020 size=2b660h (177760) map
I (178) esp_image: segment 1: paddr=0004b688 vaddr=3ffb0000 size=031ach ( 12716) load
I (183) esp_image: segment 2: paddr=0004e83c vaddr=40080000 size=017dch (  6108) load
I (187) esp_image: segment 3: paddr=00050020 vaddr=400d0020 size=a3f3ch (671548) map
I (435) esp_image: segment 4: paddr=000f3f64 vaddr=400817dc size=131cch ( 78284) load
I (478) boot: Loaded app from partition at offset 0x20000
I (478) boot: Disabling RNG early entropy source...
I (489) cpu_start: Pro cpu up.
I (490) cpu_start: Starting app cpu, entry point is 0x4008126c
0x4008126c: call_start_cpu1 at /Users/tsugunao/Work/esp32/v44/esp-idf/components/esp_system/port/cpu_start.c:147

I (0) cpu_start: App cpu up.
I (506) cpu_start: Pro cpu start user code
I (506) cpu_start: cpu freq: 160000000
I (506) cpu_start: Application information:
I (510) cpu_start: Project name:     fleet_provisioning_with_csr
I (517) cpu_start: App version:      202210.01-LTS-release-dirty
I (524) cpu_start: Compile time:     Jun 20 2023 09:26:35
I (530) cpu_start: ELF file SHA256:  24f0564ab71591c2...
I (536) cpu_start: ESP-IDF:          v4.4.4-435-g7641c8ef4f
I (542) cpu_start: Min chip rev:     v0.0
I (547) cpu_start: Max chip rev:     v3.99
I (552) cpu_start: Chip rev:         v3.0
I (557) heap_init: Initializing. RAM available for dynamic allocation:
I (564) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (570) heap_init: At 3FFB9A20 len 000265E0 (153 KiB): DRAM
I (576) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (582) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (589) heap_init: At 400949A8 len 0000B658 (45 KiB): IRAM
I (596) spi_flash: detected chip: generic
I (600) spi_flash: flash io: dio
I (605) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (614) FLEET_PROVISIONING_EXAMPLE: [APP] Startup..
I (624) FLEET_PROVISIONING_EXAMPLE: [APP] Free memory: 257900 bytes
I (624) FLEET_PROVISIONING_EXAMPLE: [APP] IDF version: v4.4.4-435-g7641c8ef4f
I (634) FLEET_PROVISIONING_EXAMPLE: Initializing SPIFFS
I (764) FLEET_PROVISIONING_EXAMPLE: Performing SPIFFS_check().
I (5774) FLEET_PROVISIONING_EXAMPLE: SPIFFS_check() successful
I (5784) FLEET_PROVISIONING_EXAMPLE: Partition size: total: 896321, used: 11546
I (5834) wifi:wifi driver task: 3ffc5910, prio:23, stack:6656, core=0
I (5834) system_api: Base MAC address is not set
I (5834) system_api: read default base MAC address from EFUSE
I (5854) wifi:wifi firmware version: 0f80fa0
I (5854) wifi:wifi certification version: v7.0
I (5854) wifi:config NVS flash: enabled
I (5854) wifi:config nano formating: disabled
I (5864) wifi:Init data frame dynamic rx buffer num: 32
I (5864) wifi:Init management frame dynamic rx buffer num: 32
I (5874) wifi:Init management short buffer num: 32
I (5874) wifi:Init dynamic tx buffer num: 32
I (5884) wifi:Init static rx buffer size: 1600
I (5884) wifi:Init static rx buffer num: 10
I (5884) wifi:Init dynamic rx buffer num: 32
I (5894) wifi_init: rx ba win: 6
I (5894) wifi_init: tcpip mbox: 32
I (5904) wifi_init: udp mbox: 6
I (5904) wifi_init: tcp mbox: 6
I (5904) wifi_init: tcp tx win: 5744
I (5914) wifi_init: tcp rx win: 5744
I (5914) wifi_init: tcp mss: 1440
I (5924) wifi_init: WiFi IRAM OP enabled
I (5924) wifi_init: WiFi RX IRAM OP enabled
I (5934) example_connect: Connecting to HG8045-9653-bg...
I (5934) phy_init: phy_version 4670,719f9f6,Feb 18 2021,17:07:07
I (6044) wifi:mode : sta (8c:4b:14:39:e3:f8)
I (6044) wifi:enable tsf
I (6044) example_connect: Waiting for IP(s)
I (8454) wifi:new:<6,0>, old:<1,0>, ap:<255,255>, sta:<6,0>, prof:1
I (9194) wifi:state: init -> auth (b0)
I (9204) wifi:state: auth -> assoc (0)
I (9204) wifi:state: assoc -> run (10)
I (9224) wifi:connected with HG8045-9653-bg, aid = 1, channel 6, BW20, bssid = a4:c7:de:81:18:a4
I (9224) wifi:security: WPA2-PSK, phy: bgn, rssi: -56
I (9224) wifi:pm start, type: 1

I (9424) wifi:AP's beacon interval = 102400 us, DTIM period = 1
I (9524) wifi:<ba-add>idx:0 (ifx:0, a4:c7:de:81:18:a4), tid:0, ssn:0, winSize:64
I (10234) esp_netif_handlers: example_connect: sta ip: 192.168.1.219, mask: 255.255.255.0, gw: 192.168.1.1
I (10234) example_connect: Got IPv4 event: Interface "example_connect: sta" address: 192.168.1.219
I (10824) example_connect: Got IPv6 event: Interface "example_connect: sta" address: fe80:0000:0000:0000:8e4b:14ff:fe39:e3f8, type: ESP_IP6_ADDR_IS_LINK_LOCAL
I (10824) example_connect: Connected to example_connect: sta
I (10834) example_connect: - IPv4 address: 192.168.1.219
I (10834) example_connect: - IPv6 address: fe80:0000:0000:0000:8e4b:14ff:fe39:e3f8, type: ESP_IP6_ADDR_IS_LINK_LOCAL
I (10854) corePKCS11: PKCS #11 successfully initialized.
I (10924) corePKCS11: Creating a 0x3 type object.
I (10924) PKCS11: Initializing NVS partition: "storage"
E (10964) corePKCS11: Failed to destroy object. PKCS11_PAL_DestroyObject failed.
I (10964) corePKCS11: Writing certificate into label "Claim Cert".
I (10964) corePKCS11: Creating a 0x1 type object.
I (10974) FleetProvisioning: Establishing MQTT session with claim certificate...
I (12824) coreMQTT: MQTT connection established with the broker.
I (12824) FleetProvisioning: Established connection with claim credentials.
I (13554) coreMQTT: Ack packet deserialized with result: MQTTSuccess.
I (13554) coreMQTT: State record updated. New state=MQTTPublishDone.
I (13734) coreMQTT: De-serialized incoming PUBLISH packet: DeserializerResult=MQTTSuccess.
I (13744) coreMQTT: State record updated. New state=MQTTPubAckSend.
I (13744) FleetProvisioning: Received accepted response from Fleet Provisioning CreateCertificateFromCsr API.
I (16784) FleetProvisioning: Received certificate with Id: e0f35a201bf63985eb5ca139e453296b1841cae19888d2963956b15109f43c9d
I (16814) corePKCS11: Writing certificate into label "Device Cert".
I (16814) corePKCS11: Creating a 0x1 type object.
I (17214) coreMQTT: Ack packet deserialized with result: MQTTSuccess.
I (17214) coreMQTT: State record updated. New state=MQTTPublishDone.
I (17624) coreMQTT: De-serialized incoming PUBLISH packet: DeserializerResult=MQTTSuccess.
I (17624) coreMQTT: State record updated. New state=MQTTPubAckSend.
I (17624) FleetProvisioning: Received accepted response from Fleet Provisioning RegisterThing API.
I (20644) FleetProvisioning: Received AWS IoT Thing name: thing_0000
I (20754) coreMQTT: Disconnected from the broker.
I (20754) FleetProvisioning: Establishing MQTT session with provisioned certificate...
I (22024) coreMQTT: MQTT connection established with the broker.
I (22024) FleetProvisioning: Sucessfully established connection with provisioned credentials.
I (22034) coreMQTT: Disconnected from the broker.
I (22034) corePKCS11: Successfully closed PKCS #11 session.
I (22044) corePKCS11: PKCS #11 was successfully uninitialized.
I (22044) FleetProvisioning: Demo iteration 1 is successful.
I (22054) FleetProvisioning: Demo completed successfully.
I (32054) FLEET_PROVISIONING_EXAMPLE: SPIFFS unmounted
```
正しくProvisioningが行われると、AWS IoT Coreのコンソールで、指定したプレフィックスとシリアル番号が付与されたモノが作成され、モノに証明書がアタッチされ、証明書に指定したPolicyがアタッチされていることが確認できます。
![スクリーンショット 2023-06-21 16.12.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/618e4b23-c7de-9975-298f-3b8cadea3198.png)


# FleetProvisioning sample プログラムの解説
 FleetProvisioning sampleプログラムで行われていることを確認するため、```esp-aws-iot/examples/fleet_provisioning/fleet_provisioning_with_csr/main/fleet_provisioning_with_csr_demo.c``` を見てみましょう。まず、PKCS#11とよばれる暗号化を行うためのモジュールを使用して、クレーム証明書をローカルに保存します。

```c
int aws_iot_demo_main( int argc,
          char ** argv )
{
    bool status = false;
    /* Buffer for holding the CSR. */
    char csr[ CSR_BUFFER_LENGTH ] = { 0 };
    size_t csrLength = 0;
(中略)
   do
    {
        /* Initialize the buffer lengths to their max lengths. */
        certificateLength = CERT_BUFFER_LENGTH;
        certificateIdLength = CERT_ID_BUFFER_LENGTH;
        ownershipTokenLength = OWNERSHIP_TOKEN_BUFFER_LENGTH;

        /* Initialize the PKCS #11 module */
        pkcs11ret = xInitializePkcs11Session( &p11Session );

        if( pkcs11ret != CKR_OK )
        {
            LogError( ( "Failed to initialize PKCS #11." ) );
            status = false;
        }
        else
        {
            /* Insert the claim credentials into the PKCS #11 module */
            status = loadClaimCredentials( p11Session,
                                           CLAIM_CERT_PATH,
                                           pkcs11configLABEL_CLAIM_CERTIFICATE,
                                           CLAIM_PRIVATE_KEY_PATH,
                                           pkcs11configLABEL_CLAIM_PRIVATE_KEY );
```

その後、このクレーム証明書を用いて、AWS IoT CoreとMQTTで接続します。

```c
        if( status == true )
        {
            /* Attempts to connect to the AWS IoT MQTT broker. If the
             * connection fails, retries after a timeout. Timeout value will
             * exponentially increase until maximum attempts are reached. */
            LogInfo( ( "Establishing MQTT session with claim certificate..." ) );
            status = EstablishMqttSession( provisioningPublishCallback,
                                           p11Session,
                                           pkcs11configLABEL_CLAIM_CERTIFICATE,
                                           pkcs11configLABEL_CLAIM_PRIVATE_KEY );
```
FleetProvisiningには、1）自身で秘密鍵を作成してCSRを発行する方法: CreateCertificateFromCsrと、2）AWS IoT Coreに秘密鍵と証明書の発行を依頼する方法: CreateKeysAndCertificate の2種類の方法がありますが、このサンプルプログラムは前者ですので、秘密鍵を作成し、CSRを作成、CSRのリクエストをPublishします。
```c
      if( status == true )
        {
            /* Create a new key and CSR. */
            status = generateKeyAndCsr( p11Session,
                                        pkcs11configLABEL_DEVICE_PRIVATE_KEY_FOR_TLS,
                                        pkcs11configLABEL_DEVICE_PUBLIC_KEY_FOR_TLS,
                                        csr,
                                        CSR_BUFFER_LENGTH,
                                        &csrLength );
        }

       if( status == true )
        {
            /* Create the request payload containing the CSR to publish to the
             * CreateCertificateFromCsr APIs. */
            status = generateCsrRequest( payloadBuffer,
                                         NETWORK_BUFFER_SIZE,
                                         csr,
                                         csrLength,
                                         &payloadLength );
        }

        if( status == true )
        {
            /* Publish the CSR to the CreateCertificatefromCsr API. */
            PublishToTopic( FP_CBOR_CREATE_CERT_PUBLISH_TOPIC,
                            FP_CBOR_CREATE_CERT_PUBLISH_LENGTH,
                            ( char * ) payloadBuffer,
                            payloadLength );

```
レスポンスとしてデバイス証明書が返されるので、それをローカルに保存します。

```c
        if( status == true )
        {
            /* From the response, extract the certificate, certificate ID, and
             * certificate ownership token. */
            status = parseCsrResponse( payloadBuffer,
                                       payloadLength,
                                       certificate,
                                       &certificateLength,
                                       certificateId,
                                       &certificateIdLength,
                                       ownershipToken,
                                       &ownershipTokenLength );

            if( status == true )
            {
                LogInfo( ( "Received certificate with Id: %.*s", ( int ) certificateIdLength, certificateId ) );
            }
        }

        if( status == true )
        {
            /* Save the certificate into PKCS #11. */
            status = loadCertificate( p11Session,
                                      certificate,
                                      pkcs11configLABEL_DEVICE_CERTIFICATE_FOR_TLS,
                                      certificateLength );
        }
```

最後に、RegisterThingのAPIを用いて、モノの登録を行います。
```c
       /**** Call the RegisterThing API **************************************/

        /* We then use the RegisterThing API to activate the received certificate,
         * provision AWS IoT resources according to the provisioning template, and
         * receive device configuration. */
        if( status == true )
        {
            /* Create the request payload to publish to the RegisterThing API. */
            status = generateRegisterThingRequest( payloadBuffer,
                                                   NETWORK_BUFFER_SIZE,
                                                   ownershipToken,
                                                   ownershipTokenLength,
                                                   DEVICE_SERIAL_NUMBER,
                                                   DEVICE_SERIAL_NUMBER_LENGTH,
                                                   &payloadLength );
        }

        if( status == true )
        {
            /* Subscribe to the RegisterThing response topics. */
            status = subscribeToRegisterThingResponseTopics();
        }

        if( status == true )
        {

            /* Publish the RegisterThing request. */
            PublishToTopic( FP_CBOR_REGISTER_PUBLISH_TOPIC( PROVISIONING_TEMPLATE_NAME ),
                            FP_CBOR_REGISTER_PUBLISH_LENGTH( PROVISIONING_TEMPLATE_NAME_LENGTH ),
                            ( char * ) payloadBuffer,
                            payloadLength );
```

# まとめ
ESP32のデバイスでFleetProvisoningを行い、証明書とモノの作成を行いました。デバイス側で自身のシリアル番号等を取得して、それを設定するようにすることで多数のデバイスのプロビジョニングを行うことができるようになります。これによりデバイスとクラウドの間でセキュアな通信を行うことができます。