はじめに
Secure Bootについて知りたくて、手元にあったESP32のSecure Bootの仕組みを調べました。実際にやってしまうと元に戻せなくなるので試してはいません。
主に参考にした資料はこちら。
ESP-IDF Programming Guide - Secure Boot
日本語訳も作ってみました。
https://qiita.com/hisashij/items/3372c93ebc28fa4298f9
手順を確認する際に使った環境はESP-IDF v4.0.1。バージョンが異なるとmenuconfigの構成が少し変わるようです。
前提情報
ESP32の基本的なbootの流れ
- まずROMにある1st-stage bootloaderが、フラッシュのオフセット0x1000から2nd-stage bootloaderをRAMに読み込み、処理を渡す。
- 2nd-stage bootloaderが、フラッシュからパーティションテーブルとアプリイメージをRAMに読み込み、アプリに処理を渡す。
ESP32のSecureBootの流れ(概要)
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に埋め込まれる。
ESP32のeFuse
- eFuseとは、一度ビットを1にすると0に戻すことができないメモリ。
- ESP32は、各256ビットのBLOCKが、0から3まである。
ESP32のSecure Bootの種類
大きく以下3つの方式がある。
-
Secure Boot: One-Time Flash
本番環境で使う基本的な方式。一度書き込んだ2nd-stage bootloaderは、hardware内で生成されeFuseに焼きこまれたSecure Boot Keyで保護されており、変更できない。 -
Secure Boot: Reflashable
2nd-stage bootloaderを後で変更できる方式。Secure Boot Keyは、hardware内で生成されたものではなく、Secure Boot Signing Keyから導出されたものを使う。 -
Signed App Verification Without Hardware Secure Boot
2nd-stage bootloaderの検証は行わず、アプリイメージの署名検証だけを行う方式。
One-Time Flash方式
この手順を行うと元に戻せなくなるのでご注意ください。
ビルドの流れ
-
menuconfigでの設定。
「Security features」->「Enable hardware secure boot in bootloader」にチェックを入れる。デフォルトで「Secure bootloader mode」には「One-time flash」が選択されている。 -
Secure Boot Signing Keyを生成する。
python espsecure.py generate_signing_key secure_boot_signing_key.pem
-
2nd-stage bootloaderをビルドする。
idf.py bootloader
これにより、セキュアブートサポートが有効化され、Secure Boot Signing Keyに対応する公開鍵が含まれた、2nd-stage bootloaderが作られる。 -
2nd-stage bootloaderをフラッシュへ書き込む。
idf.py -p (PORT) bootloader-flash
-
アプリをビルドする。
idf.py build
アプリはSecure Boot Signing Keyで署名される。 -
アプリをフラッシュへ書き込む。
idf.py -p (PORT) flash
初回ブート時の流れ
- まずROMにある1st-stage bootloaderが、フラッシュのオフセット0x1000から2nd-stage bootloaderをRAMに読み込む。
- hardware secure boot supportが、Secure Boot Key(AES-256鍵)を生成してeFuse BLOCK 2に焼きこむ。
- hardware secure boot supportが、2nd-stage bootloaderのSHA-512ダイジェスト(64バイト)を計算し、計算に使用したIV(128バイト)とともに、フラッシュのオフセット0x0000に書き込む。ダイジェストの計算は以下の通り。
- ランダムに生成したIVを2nd-stage bootloaderの先頭に連結。
- AES-256で暗号化。
- SHA-512でダイジェストを計算。
- hardware secure boot supportが、セキュアブートの各種設定をeFuse BLOCK 0に焼きこむともに、BLOCK 0のabstract_done_0 bitを立てることでセキュアブートを有効化し、2nd-stage bootloaderに処理を渡す。
- 2nd-stage bootloaderは、フラッシュからパーティションテーブルとアプリイメージをRAMに読み込む。
- 2nd-stage bootloaderは、自身に組み込まれている署名検証用公開鍵(ECDSA 256bit)を使って、これらの検証を行った後、アプリに処理を渡す。
2度目以降のブート時の流れ
- まずROMにある1st-stage bootloaderが、フラッシュのオフセット0x1000から2nd-stage bootloaderをRAMに読み込む。
- eFuse BLOCK 0のabstract_done_0 bitが立っていたらSecure Bootの処理を開始。hardware secure boot supportを使って以下の流れで2nd-stage bootloaderを検証する。
- フラッシュのオフセット0x0000から、128バイトのIVと、64バイトのSHA-512ダイジェストデータを読み込む。
- eFuse BLOCK 2からAES-256鍵を読み込んで、2nd-stage bootloaderのダイジェストを再計算する。ダイジェストの計算は以下の通り。
- 2nd-stage bootloaderの先頭にIVを連結。
- AES-256で暗号化。
- SHA-512でダイジェストを計算。
- 再計算されたダイジェストと、フラッシュ読み込んだダイジェストが一致していることを確認。
- 2nd-stage bootloaderが検証されたら、2nd-stage bootloaderに処理を渡す。
- 2nd-stage bootloaderは、フラッシュからパーティションテーブルとアプリイメージをRAMに読み込む。
- 2nd-stage bootloaderは、自身に組み込まれている署名検証用公開鍵(ECDSA 256bit)を使って、これらの検証を行った後、アプリに処理を渡す。
アプリ置き換え時の流れ
同じSecure Boot Signing Keyがある状態で以下を行えばアプリが置き換えられる(たぶん)。
-
アプリをビルドする。
idf.py build
アプリはSecure Boot Signing Keyで署名される。 -
アプリをフラッシュへ書き込む。
idf.py -p (PORT) flash
Reflashable方式
この手順を行うと元に戻せなくなるのでご注意ください。
ビルドの流れ
-
menuconfigでの設定。
「Security features」->「Enable hardware secure boot in bootloader」にチェックを入れる。
続いて「Secure bootloader mode」を「One-time flash」から「Reflashable」に変更する。 -
Secure Boot Signing Keyを生成する。
python espsecure.py generate_signing_key secure_boot_signing_key.pem
-
2nd-stage bootloaderをビルドする。
idf.py bootloader
これにより、セキュアブートサポートが有効化され、Secure Boot Signing Keyに対応する公開鍵が含まれた、2nd-stage bootloaderが作られる。また、Secure Boot Signing Keyから導出したSecure Boot Keyが作られる。 -
Secure Boot KeyをeFuseへ書き込む。
ビルド時に表示されるメッセージにしたがって以下のようなコマンドで書き込む。
python.exe espefuse.py burn_key secure_boot secure-bootloader-key-256.bin
-
2nd-stage bootloaderをフラッシュへ書き込む。
idf.py -p (PORT) bootloader-flash
-
アプリをビルドする。
idf.py build
アプリはSecure Boot Signing Keyで署名される。 -
アプリをフラッシュへ書き込む。
idf.py -p (PORT) flash
初回ブート時の流れ
One-Time Flash方式と同じ。ただし、Secure Boot KeyはeFuseに書込み済のものを使用する。
2度目以降のブート時の流れ
One-Time Flash方式と同じ。
2nd-stage bootloader置き換え時の流れ
同じSecure Boot Signing Keyがある状態で以下を行えばbootloaderとアプリが置き換えられる(たぶん)。
-
2nd-stage bootloaderをビルドする。
idf.py bootloader
-
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
-
2nd-stage bootloaderをフラッシュへ書き込む。
idf.py -p (PORT) bootloader-flash
-
アプリをビルドする。
idf.py build
アプリはSecure Boot Signing Keyで署名される。 -
アプリをフラッシュへ書き込む。
idf.py -p (PORT) flash
Signed App Verification Without Hardware Secure Boot方式
ビルドの流れ
-
menuconfigでの設定。
「Security features」->「Require signed app amages」にチェックを入れる。チェックを入れると表示される、「Bootloader verifies app signatures」「Verify app signature on update」に必要に応じてチェックを入れる。 -
Secure Boot Signing Keyを生成する。
python espsecure.py generate_signing_key secure_boot_signing_key.pem
-
アプリをビルドする。
idf.py build
アプリはSecure Boot Signing Keyで署名される。 -
アプリをフラッシュへ書き込む。
idf.py -p (PORT) flash
ブート時の流れ
- まずROMにある1st-stage bootloaderが、フラッシュのオフセット0x1000から2nd-stage bootloaderをRAMに読み込んで処理を渡す。
- 2nd-stage bootloaderは、フラッシュからパーティションテーブルとアプリイメージをRAMに読み込む。
- 2nd-stage bootloaderは、自身に組み込まれている署名検証用公開鍵(ECDSA 256bit)を使って、これらの検証を行った後、アプリに処理を渡す。
アプリ置き換え時の流れ
同じSecure Boot Signing Keyがある状態で以下を行えばアプリが置き換えられる(たぶん)。
-
アプリをビルドする。
idf.py build
アプリはSecure Boot Signing Keyで署名される。 -
アプリをフラッシュへ書き込む。
idf.py -p (PORT) flash