ESP32のSecure Bootの仕組み

# はじめに
Secure Bootについて知りたくて、手元にあったESP32のSecure Bootの仕組みを調べました。実際にやってしまうと元に戻せなくなるので試してはいません。

主に参考にした資料はこちら。
[ESP-IDF Programming Guide - Secure Boot](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/security/secure-boot-v1.html)
日本語訳も作ってみました。
https://qiita.com/hisashij/items/3372c93ebc28fa4298f9

手順を確認する際に使った環境はESP-IDF v4.0.1。バージョンが異なるとmenuconfigの構成が少し変わるようです。

# 前提情報
## ESP32の基本的なbootの流れ
1. まずROMにある1st-stage bootloaderが、フラッシュのオフセット0x1000から2nd-stage bootloaderをRAMに読み込み、処理を渡す。
2. 2nd-stage bootloaderが、フラッシュからパーティションテーブルとアプリイメージをRAMに読み込み、アプリに処理を渡す。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679422/8edeed93-6288-5e02-603d-82246a23deca.png)

## ESP32のSecureBootの流れ（概要）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679422/2be90939-9a9d-632d-b98f-6b31152005f7.png)

## ESP32のSecure Bootで使われる鍵
以下2つの鍵を使用する。

- Secure Boot Key
    - AES 256bit
    - ROMにある1st-stage bootloaderが、2nd-stage bootloaderをフラッシュから読み込んだ際の検証（ダイジェスト値の計算）で使われる。
    - 最初にブートされた際にhardware secure boot supportが生成し、eFuseのBLOCK 2に焼きこまれる。当該領域はR/W Protectされており読み出し不可。

- Secure Boot Signing Key
    - ECDSA 256bit
    - 2nd-stage bootloaderが、パーティションテーブルとアプリイメージをフラッシュから読み込んだ際の署名検証で使われる。
    - 鍵は開発者がビルド前に生成し、厳重に保管しておく。ビルド時にその鍵を使って署名が行われる。検証用の公開鍵は2nd-stage bootloaderに埋め込まれる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679422/18db23b4-eacc-5727-6db3-2f4c6d93f0f3.png)

## ESP32のeFuse

- eFuseとは、一度ビットを1にすると0に戻すことができないメモリ。
- ESP32は、各256ビットのBLOCKが、0から3まである。

## ESP32のSecure Bootの種類
大きく以下3つの方式がある。

1. Secure Boot: One-Time Flash
本番環境で使う基本的な方式。一度書き込んだ2nd-stage bootloaderは、hardware内で生成されeFuseに焼きこまれたSecure Boot Keyで保護されており、変更できない。

2. Secure Boot: Reflashable
2nd-stage bootloaderを後で変更できる方式。Secure Boot Keyは、hardware内で生成されたものではなく、Secure Boot Signing Keyから導出されたものを使う。

3. Signed App Verification Without Hardware Secure Boot
2nd-stage bootloaderの検証は行わず、アプリイメージの署名検証だけを行う方式。

# One-Time Flash方式
**この手順を行うと元に戻せなくなるのでご注意ください。**

## ビルドの流れ

1. menuconfigでの設定。
「Security features」->「Enable hardware secure boot in bootloader」にチェックを入れる。デフォルトで「Secure bootloader mode」には「One-time flash」が選択されている。

2. Secure Boot Signing Keyを生成する。
`python espsecure.py generate_signing_key secure_boot_signing_key.pem`

3. 2nd-stage bootloaderをビルドする。
`idf.py bootloader`
これにより、セキュアブートサポートが有効化され、Secure Boot Signing Keyに対応する公開鍵が含まれた、2nd-stage bootloaderが作られる。

4. 2nd-stage bootloaderをフラッシュへ書き込む。
`idf.py -p (PORT) bootloader-flash`

5. アプリをビルドする。
`idf.py build`
アプリはSecure Boot Signing Keyで署名される。

6. アプリをフラッシュへ書き込む。
`idf.py -p (PORT) flash`

## 初回ブート時の流れ

1. まずROMにある1st-stage bootloaderが、フラッシュのオフセット0x1000から2nd-stage bootloaderをRAMに読み込む。
2. hardware secure boot supportが、Secure Boot Key（AES-256鍵）を生成してeFuse BLOCK 2に焼きこむ。
3. hardware secure boot supportが、2nd-stage bootloaderのSHA-512ダイジェスト（64バイト）を計算し、計算に使用したIV（128バイト）とともに、フラッシュのオフセット0x0000に書き込む。ダイジェストの計算は以下の通り。
    1. ランダムに生成したIVを2nd-stage bootloaderの先頭に連結。
    2. AES-256で暗号化。
    3. SHA-512でダイジェストを計算。
4. hardware secure boot supportが、セキュアブートの各種設定をeFuse BLOCK 0に焼きこむともに、BLOCK 0のabstract_done_0 bitを立てることでセキュアブートを有効化し、2nd-stage bootloaderに処理を渡す。
5. 2nd-stage bootloaderは、フラッシュからパーティションテーブルとアプリイメージをRAMに読み込む。
6. 2nd-stage bootloaderは、自身に組み込まれている署名検証用公開鍵(ECDSA 256bit)を使って、これらの検証を行った後、アプリに処理を渡す。

## 2度目以降のブート時の流れ

1. まずROMにある1st-stage bootloaderが、フラッシュのオフセット0x1000から2nd-stage bootloaderをRAMに読み込む。
2. eFuse BLOCK 0のabstract_done_0 bitが立っていたらSecure Bootの処理を開始。hardware secure boot supportを使って以下の流れで2nd-stage bootloaderを検証する。
    1. フラッシュのオフセット0x0000から、128バイトのIVと、64バイトのSHA-512ダイジェストデータを読み込む。
    2. eFuse BLOCK 2からAES-256鍵を読み込んで、2nd-stage bootloaderのダイジェストを再計算する。ダイジェストの計算は以下の通り。
        1. 2nd-stage bootloaderの先頭にIVを連結。
        2. AES-256で暗号化。
        3. SHA-512でダイジェストを計算。
    4. 再計算されたダイジェストと、フラッシュ読み込んだダイジェストが一致していることを確認。
5. 2nd-stage bootloaderが検証されたら、2nd-stage bootloaderに処理を渡す。
6. 2nd-stage bootloaderは、フラッシュからパーティションテーブルとアプリイメージをRAMに読み込む。
7. 2nd-stage bootloaderは、自身に組み込まれている署名検証用公開鍵(ECDSA 256bit)を使って、これらの検証を行った後、アプリに処理を渡す。

## アプリ置き換え時の流れ
同じSecure Boot Signing Keyがある状態で以下を行えばアプリが置き換えられる（たぶん）。

1. アプリをビルドする。
`idf.py build`
アプリはSecure Boot Signing Keyで署名される。

2. アプリをフラッシュへ書き込む。
`idf.py -p (PORT) flash`

# Reflashable方式
**この手順を行うと元に戻せなくなるのでご注意ください。**

## ビルドの流れ

1. menuconfigでの設定。
「Security features」->「Enable hardware secure boot in bootloader」にチェックを入れる。
続いて「Secure bootloader mode」を「One-time flash」から「Reflashable」に変更する。

2. Secure Boot Signing Keyを生成する。
`python espsecure.py generate_signing_key secure_boot_signing_key.pem`

3. 2nd-stage bootloaderをビルドする。
`idf.py bootloader`
これにより、セキュアブートサポートが有効化され、Secure Boot Signing Keyに対応する公開鍵が含まれた、2nd-stage bootloaderが作られる。また、Secure Boot Signing Keyから導出したSecure Boot Keyが作られる。

4. Secure Boot KeyをeFuseへ書き込む。
ビルド時に表示されるメッセージにしたがって以下のようなコマンドで書き込む。
`python.exe espefuse.py burn_key secure_boot secure-bootloader-key-256.bin`

5. 2nd-stage bootloaderをフラッシュへ書き込む。
`idf.py -p (PORT) bootloader-flash`

5. アプリをビルドする。
`idf.py build`
アプリはSecure Boot Signing Keyで署名される。

6. アプリをフラッシュへ書き込む。
`idf.py -p (PORT) flash`

## 初回ブート時の流れ

One-Time Flash方式と同じ。ただし、Secure Boot KeyはeFuseに書込み済のものを使用する。

## 2度目以降のブート時の流れ

One-Time Flash方式と同じ。

## 2nd-stage bootloader置き換え時の流れ
同じSecure Boot Signing Keyがある状態で以下を行えばbootloaderとアプリが置き換えられる（たぶん）。

1. 2nd-stage bootloaderをビルドする。
`idf.py bootloader`

2. 2nd-stage bootloaderのダイジェスト値をフラッシュへ書き込む。
ビルド時に表示されるメッセージにしたがって以下のようなコマンドで書き込む。
`python.exe esptool.py --chip esp32 --port (PORT) --baud (BAUD) --before default_reset --after no_reset write_flash --flash_mode dio --flash_freq 40m --flash_size 2MB  -u 0x0 bootloader-reflash-digest.bin`

3. 2nd-stage bootloaderをフラッシュへ書き込む。
`idf.py -p (PORT) bootloader-flash`

4. アプリをビルドする。
`idf.py build`
アプリはSecure Boot Signing Keyで署名される。

5. アプリをフラッシュへ書き込む。
`idf.py -p (PORT) flash`

# Signed App Verification Without Hardware Secure Boot方式

## ビルドの流れ

1. menuconfigでの設定。
「Security features」->「Require signed app amages」にチェックを入れる。チェックを入れると表示される、「Bootloader verifies app signatures」「Verify app signature on update」に必要に応じてチェックを入れる。

2. Secure Boot Signing Keyを生成する。
`python espsecure.py generate_signing_key secure_boot_signing_key.pem`

5. アプリをビルドする。
`idf.py build`
アプリはSecure Boot Signing Keyで署名される。

6. アプリをフラッシュへ書き込む。
`idf.py -p (PORT) flash`

## ブート時の流れ

1. まずROMにある1st-stage bootloaderが、フラッシュのオフセット0x1000から2nd-stage bootloaderをRAMに読み込んで処理を渡す。
2. 2nd-stage bootloaderは、フラッシュからパーティションテーブルとアプリイメージをRAMに読み込む。
3. 2nd-stage bootloaderは、自身に組み込まれている署名検証用公開鍵(ECDSA 256bit)を使って、これらの検証を行った後、アプリに処理を渡す。

## アプリ置き換え時の流れ
同じSecure Boot Signing Keyがある状態で以下を行えばアプリが置き換えられる（たぶん）。

1. アプリをビルドする。
`idf.py build`
アプリはSecure Boot Signing Keyで署名される。

2. アプリをフラッシュへ書き込む。
`idf.py -p (PORT) flash`