ESP32-S3でSecure Boot V2 -1-

 Super mini S3を使って、Secure Boot V2をテストしてみる。

 プロジェクトは動作確認に使ったsupermini_rgb48_testをそのまま使う。

 Secure Boot V2は、趣味のプロダクトをビルドするときには、まったく気にする必要がないのだけれど、商用の商品をリリースする場合には必須の項目になる。

 Secure Boot V2とは

 PCのブートプロセスほど厳格ではないけれど、マイコンであるESP32にもSecure Bootの仕組みがある。

 ドキュメントはこのあたり。

Secure Boot v2 - ESP32-S3 - — ESP-IDF Programming Guide v5.5.2 documentation
https://docs.espressif.com/projects/esp-idf/en/stable/esp32s3/security/secure-boot-v2.html

 Secure Bootは、デバイス上で未署名・不正署名のコードを起動しないようにして、アプリケーションの改ざんや差し替えを防ぐ機能。

 ROM上にあるブートローダーである1st stage bootloaderが、eFuseに書かれた公開鍵のダイジェストを使って2nd stage bootloaderの署名ブロックの公開鍵のダイジェストが一致するかを検証し、さらに2nd stage bootloaderの署名検証を行う。2nd stage bootloaderは、アプリ側の署名ブロックに入っている公開鍵を使い、そのダイジェストがeFuse登録済みのものと一致することを確認した上で、その公開鍵を使ってアプリケーションイメージやOTAイメージの署名を検証して、マッチしない場合は起動しないようにする。

 ただ、アプリケーションなどに署名はされていても、eFuseに鍵が書き込まれ、Secure Boot V2を使用する設定になっていない場合は、署名がされたバイナリでも普通に起動できるので、署名そのものは開発の初期からやるように設定しておいたほうがよい。

 eFuseへの鍵のダイジェストの書き込みは、一度行うと書き換えができず、開発時には不自由な環境を強いられるため初期からやるべきではない。製品の製造や出荷時に行うのがよい。

 公開鍵のダイジェストを書き込むeFuse領域は3つあるので、未使用エリアをrevoke設定していない限り、ダイジェストは3種類保持することができる。署名検証の検索は順に行われるため、いずれかの鍵で署名されたイメージを起動することができる。

 このような機能は、鍵の漏洩時や漏洩リスクの低減などで、鍵ローテーションを行う場合に利用できる。アップデートで、アプリケーション側からもう使わない鍵のrevokeを行うことはできるが、起動不能になるリスクを伴うので、製造時の管理などで利用することを想定している。一度revokeしてしまうと、そのエリアは使用できなくなるので注意。

 Secure BootはV1とV2があって、検索して出てくる日本語での解説はV1についての内容がほどんどであり、V2とは状況が異なるのであまり参考にならないので注意。

 SecureBoot鍵を事前に作成する

 製品共通のSecure Boot v2署名鍵(RSA-3072)を作成する。

 サンプルの鍵の内容は、普通は公開しないものなのだけど、ここではそのまま書いておく。

 鍵の管理は、流出対策にはリポジトリの外に置くのが妥当なのだろうけど、今回はそんなに気にしないのでプロジェクトにkeysディレクトリを掘って、gitignoreを設定しておく。

 % mkdir keys

 % vi .gitignore

 /keys/*.pem

 /keys/*.key

 鍵は、ESP-IDFの環境が入っている端末で「idf.py secure-generate-signing-key」やopensslコマンドを使って作成する。

 % idf.py secure-generate-signing-key --version 2 ./keys/secure_boot_signing_key.pem

 Executing action: secure-generate-signing-key

 Running espsecure in directory /Users/kinneko/Downloads/esp/supermini/supermini_rgb48_test

 Executing "/Users/kinneko/.espressif/python_env/idf5.5_py3.13_env/bin/python -m espsecure generate_signing_key --version 2 ./keys/secure_boot_signing_key.pem"...

 espsecure.py v4.11.dev1

 RSA 3072 private key in PEM format written to ./keys/secure_boot_signing_key.pem

 % ls -al ./keys/

 total 8

 drwxr-xr-x 3 kinneko staff 96 2 12 14:20 .

 drwxr-xr-x 18 kinneko staff 576 2 12 14:20 ..

 -rw-r--r-- 1 kinneko staff 2459 2 12 14:20 secure_boot_signing_key.pem

 % cat keys/secure_boot_signing_key.pem

 -----BEGIN RSA PRIVATE KEY-----

 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

 -----END RSA PRIVATE KEY-----

 OpenSSLコマンドで作ることもできる。コマンド実行時の出力は特にない。

 % openssl genrsa -out ./keys/secure_boot_signing_key_openssl.pem 3072

 % ls -al ./keys/secure_boot_signing_key_openssl.pem

 -rw------- 1 kinneko staff 2484 2 12 14:22 ./keys/secure_boot_signing_key_openssl.pem

 % cat ./keys/secure_boot_signing_key_openssl.pem

 -----BEGIN PRIVATE KEY-----

 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

 EC/CzNl4Z4y07btTEKzsTQ==

 -----END PRIVATE KEY-----

 eFuseに書き込むダイジェスト鍵を作成する。これは、idf.pyではラップされていないので、独自のコマンドはない。opensslコマンドで作成する。

 % openssl rsa -in ./keys/secure_boot_signing_key.pem -pubout -out ./keys/secure_boot_signing_key_pub.pem

 writing RSA key

 % ls -al keys/secure_boot_signing_key_pub.pem

 -rw-r--r-- 1 kinneko staff 625 2 12 14:27 keys/secure_boot_signing_key_pub.pem

 % cat keys/secure_boot_signing_key_pub.pem

 -----BEGIN PUBLIC KEY-----

 MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAxB97Vkkg5lXq5zg+HBEU

 uQ7J/otTAc0lb+HmmwGgNxG+XhlGYAyzSSQSHrtMomNkqGJRN3CTdAK3tMCbMMsq

 Irl9ukHZgQSqItNPHTCTriBkuSoq73HVgfNLRRvy4gDu9w8kZsv1OMS70VwVQJl3

 148e7mn29DQIIdXoD4n/yrQXOjX4ybg/M8FDb/I4ZtT4LaHih9w+LB5ZrzSpl9fO

 4VhzEGOlsA05wvA1lDibQZfjrZ26NYWgN45Nhd4Y8u71VwEutSuDZUrdiSR9uFWQ

 KOoq4M3txYq2E7D6GJ33g7YSIcWXxxN0ZjMKoH5GMgcGEBLZsbu5q9zUe5+P2YCL

 XvTQKyqJ2HgBO+TONwdY0C9TiIEjd6ECzJqjI6SVvhnL39xc62QA+fdkakRFQdzA

 r3xHbF+WH2eHvWBqcLfML0dS3mvWWfprPqyLjuajTPA9FlbOaVkX+w2kAgmyBes6

 wiauWulh9CWgwibDqLpixAZAlUQhV1oowbBVTNdHX6avAgMBAAE=

 -----END PUBLIC KEY-----

 署名の実行-手動署名-

 作成した鍵を使って実際に署名を行うには、ビルド時に自動適用する方法と、ビルドしたバイナリにコマンドで署名を追加する方法がある。手動署名と自動署名の順で試す。

 署名する必要があるファイルは通常ビルドを行うと作成される以下の4つになる。

 ・アプリとOTA:build/ota_data_initial.bin, build/supermini_rgb48_test.bin

 ・ブートローダー(2nd):build/bootloader/bootloader.bin

 ・パーティションテーブル:build/partition_table/partition-table.bin

 ただし、パーティションテーブルの署名は検証されないので、手動で行う場合はやる必要はない。

 アプリ本体のsupermini_rgb48_test.binを署名してsupermini_rgb48_test_signed.binを作る。使用するコマンドはidf.py secure-sign-dataとespsecure.py sign_data --version 2があって、どちらかを実行する。

 idf.py secure-sign-dataの実行時には、内部でbuildが作業ディレクトリになるので、イメージのbuildパスは指定する必要がない、また、keyfileはbuildからの相対パスで指定しておく。(そんなん知らんがなめんどくさ)

 % idf.py secure-sign-data --keyfile ../keys/secure_boot_signing_key.pem --output supermini_rgb48_test_signed.bin supermini_rgb48_test.bin

 Executing action: secure-sign-data

 Running espsecure in directory /Users/kinneko/Downloads/esp/supermini/supermini_rgb48_test/build

 Executing "/Users/kinneko/.espressif/python_env/idf5.5_py3.13_env/bin/python -m espsecure sign_data --version 2 --keyfile ../keys/secure_boot_signing_key.pem --output supermini_rgb48_test_signed.bin supermini_rgb48_test.bin"...

 espsecure.py v4.11.dev1

 Padding data contents by 3776 bytes so signature sector aligns at sector boundary

 1 signing key(s) found.

 Signed 225280 bytes of data from supermini_rgb48_test.bin. Signature sector now has 1 signature blocks.

 % ls -al build/supermini_rgb48_test_signed.bin

 -rw-r--r-- 1 kinneko staff 229376 2 12 16:06 build/supermini_rgb48_test_signed.bin

 espsecure.py sign_dataのほうは、プロジェクトのルートからのパス指定でよい。

 % espsecure.py sign_data --version 2 --keyfile ./keys/secure_boot_signing_key.pem --output ./build/supermini_rgb48_test_signed_espsecure.bin ./build/supermini_rgb48_test.bin

 espsecure.py v4.11.dev1

 Padding data contents by 3776 bytes so signature sector aligns at sector boundary

 1 signing key(s) found.

 Signed 225280 bytes of data from ./build/supermini_rgb48_test.bin. Signature sector now has 1 signature blocks.

 % ls -al build/supermini_rgb48_test_signed_espsecure.bin

 -rw-r--r-- 1 kinneko staff 229376 2 12 16:08 build/supermini_rgb48_test_signed_espsecure.bin

 OTAイメージやブートローダーも同様に署名しておく。

 つづく


オリジナル投稿:
ESP32-S3でSecure Boot V2 -1-|kinneko|pixivFANBOX
https://kinneko.fanbox.cc/posts/11394371