概要
従来の組み込み製品の製品化では多くの場合、最初のバージョンで高い信頼性や成熟度を達成し、その後はできる限り改版しないことを目標にしてきました。しかし、近年、バグの増大・複雑化、製品寿命の長期化、はたまた外部環境や使用環境の変化に対応するために稼働後のファームウェアのバージョンアップは必須となりつつあります。しかし、ファームウェア更新はセキュリティ上、極めて大きなリスクでもあります。セキュアなファームウェア更新を実現することはIoT および組み込みシステムでは必須の条件になってきています。
今回の記事では、セキュアファームウェア更新の基本機能について説明します。また、理解を助けるために実際のMCUでセキュアにファームウェアを更新するサンプルプログラムを動かしてみたいと思います。
セキュアなブート及びファームウェアの更新を行うモジュールとしてwolfSSL社のwolfBoot を使用します。wolfBoot の特徴については2.4章でも紹介します。またその特徴について wolfSSLのブログ記事が参考になりますので掲載します。
https://wolfssl.jp/wolfblog/2020/05/15/top-ten-things-know-secure-boot/
セキュアなファームウェア更新
2.1 セキュアなファームウェア更新の基本機能
この章ではセキュアなファームウェア更新を実現するために必要となる基本機能についてみていきます。図 1は、セキュアなファームウェア更新の基本構成要素を示します。セキュアなファームウェア更新という中には、新しいプログラムの開発、それらを管理配布するためのシステム、いわゆるプロビジョニング用のサーバ機能、それからターゲットとなるIoTデバイスとサーバ間の安全な通信チャネルを含む幅広い機能を含みます。
しかし、基本となる機能は各々それほど複雑ではありません。本稿では図に示したように、セキュアなファームウェア更新の中心となる、ローカルなファームウェアイメージの更新の部分にフォーカスします。
それでは、まずはローカルなファームウェア更新の概要を理解するために、その流れを順に追ってみていくことにしましょう。
上の箱がファームウェアの開発環境です。下の箱が実行環境IoTデバイスです。
更新のための基本的なコンポーネントとしてはデバイス上で動作するブートローダ本体のほか、署名のための鍵生成ツール、署名を実際におこなう署名ツールなどが含まれます。
準備のために鍵生成ツールで一組の秘密鍵と公開鍵を準備します。
署名検証用の鍵は、ブートローダが生成される際にその一部として組み込みます。公開鍵を組み込んだブートローダをIoTデバイスにダウンロードし署名検証ができるようにします。
この時、本来は最初のファームウェアの組み込み手順がありますが、今回は簡略させていただいて、とりあえずカレントのファームウェアが動作しているということにします。
ファームウェアの更新にフォーカスして説明します。
出来上がった更新イメージに対応した署名を署名ツールで生成して、これを更新イメージとともにダウンロードします。
カレントのファームウェアから更新要求が出ると、ブートローダは次のシステム再開時に自身に組み込まれた公開鍵と署名を使って更新イメージの一貫性を検証します。
署名が正しいことが検証できたら、両バージョンのファームウェアをスワップします。
この際にいかなるタイミングで障害が発生してもファームウェアの一部が失われたり、中間的な状態になったりしないようにブロック管理を行いながら処理を行うことも重要な要素になります。万が一途中で何らかの障害が発生した場合は、完全な形でロールバックも保証されます。
図で示しているように、公開鍵署名検証を使ったこうした安全なファームウェア更新という場合には、IoTデバイス側に組み込まれているのは署名検証用の鍵だけで秘密鍵の方は完全に安全な場所に保管しておくことが出来ます。
2.2 公開鍵暗号による署名検証
全体の流れが理解できたので、次に安全性実現の要となる公開鍵署名のメカニズムについてもう少し詳しく見ていきます。
公開鍵暗号による署名検証を導入することでファームウェアに対する改竄やすり替えがないこと(真正性)と処理するデータがすべて正確であり揃った状態(完全性)を検証することが出来ます。また、公開鍵と秘密鍵の異なる鍵を使用する特徴があるため、暗号化・復号化に同一鍵を用いるメッセージ認証コードMACと比べて鍵漏洩のリスクを抑えることが出来ます。
セキュアなブートローダで使用される公開鍵の署名検証に注目し説明します。
署名についてはまず一組の鍵を生成します。公開しても良い鍵と秘密に保管しておかなければならいない秘密鍵です。
公開鍵は署名の検証に使用するので、ブートローダが検証に使えるように、自身の中に格納しておきます。
署名はファームウェアのハッシュ値を求めた値に対して行います。署名を行う際には、他人に知られてはいけない秘密鍵を用いて署名します。
署名の検証では、署名から公開鍵を使ってハッシュ値を復号し取り出します。一方、バージョン番号を含むファームウェアのイメージから再度ハッシュ値を計算し、計算による値と復号による値が同じかを検証します。
ファームウェアが1ビットでも異なる場合、検証は失敗します。
2.3 更新の状態管理
ファームウェア更新のもう一つ重要な機能は、どんな場合にもブートローダや更新イメージが中途半端な状態に陥ったりしないための、正確な状態管理です。ここでは、更新の状態管理ついて、順番に説明していきます。
最初の状態遷移図の図 2は、ファームウェアの更新はなく通常の起動をリセットで繰り返すものです。
図2の遷移図の説明を表2にまとめます。
状態 | 説明 |
---|---|
初期状態 | 通常ブートの場合は、署名検証による真正性の検証状態に遷移します。 |
署名検証 | ファームウェアの真正性を検証します。結果のOK/NGで次の状態へ遷移します。 |
アプリケーション実行 | 署名検証がOKの場合、アプリケーション実行に遷移します。 |
ブート成功 | アプリケーションからその実行がOKだった場合に遷移します。通常リセットを含むパワーオンで初期状態へと遷移します。 |
ブート失敗 | 署名検証がNGの場合に遷移する状態です。通常は無限ループなどのアイドル状態となりリセットで再起動します。 |
図3の状態遷移図は、ファームウェアの更新がある場合で、その通知フェーズです。初期状態から通常ブートで始まりますが、事前に更新領域に安全な通信路を利用し新ファームウェアが配置されています。
状態 | 説明 |
---|---|
初期状態 | 通常ブートで署名検証による真正性の検証状態に遷移します。 |
署名検証 | ファームウェアの真正性を検証します。結果のOK/NGで次の状態へ遷移します。 |
アプリケーション実行 | 署名検証がOKの場合、アプリケーション実行に遷移します。新ファームウェアの有無などにより、ファームウェア更新をブートローダへ通知します。 |
ブート成功 | アプリケーションからその実行がOKだった場合に遷移します。アプリケーションよりファームウェア更新の通知を受け取っているので、次回の起動時に更新が行えるように更新リセットをトリガします。 |
ブート失敗 | 署名検証がNGの場合に遷移する状態です。通常は無限ループなどのアイドル状態となりリセットで再起動します。 |
続いて、ファームウェアの更新フェーズ時の状態遷移図になります。事前に更新用ファームウェアが更新用領域に配置され、ブートローダは更新の通知を受け取っています。
状態 | 説明 |
---|---|
初期状態 | 更新ブートでファームウェアのスワップ状態へ遷移します。 |
ファームウェアのイメージスワップ | 現在バージョンと新バージョンのイメージをスワップします。スワップ完了すると署名検証へ状態遷移します。 |
署名検証 | ファームウェアの真正性を検証します。結果のOK/NGで次の状態へ遷移します。 |
アプリケーション実行(仮) | 署名検証がOKの場合、アプリケーション実行に遷移します。この時アプリケーションが正しく実行OK通知が出せない場合は、何らかの不都合が発生しているため以前のバージョンに戻す必要があります。そのためアプリ―ケーションはまだ受け入れ済みではなく仮OK状態になっています。 |
ブート失敗 | 署名検証がNGの場合に遷移する状態です。通常は無限ループなどのアイドル状態となりリセットで再起動します。 |
ブート成功 | アプリケーション実行OKだった場合に遷移します。 |
最後に更新ファームウェアが何らかの原因でうまくいかなかった場合の状態遷移図です。うまくいかない場合は、署名検証がNGもしくはアプリケーション実行になんらかの不具合でNGになる場合があります。そのような場合、継続性を担保するために以前のバージョンに戻す必要があります。
更新ブートがNGとなると、アプリケーションが仮状態のままとなり、その状態でリセットが発生すると保存しておいた前バージョンのファームウェアにロールバック(後退復帰)します。
状態 | 説明 |
---|---|
初期状態 | 更新フェーズでNGだった場合、ロールバックが発生します。 |
ファームウェアのイメージスワップ | 新バージョンと現在バージョンのイメージをスワップします。スワップ完了すると署名検証へ状態遷移します。 |
署名検証 | ファームウェアの真正性を検証する。結果のOK/NGで次の状態へ遷移する |
アプリケーション実行 | 署名検証がOKの場合、アプリケーション実行に遷移する。新ファームウェアの有無などにより、ファームウェア更新をブートローダへ通知する。 |
ブート失敗 | 署名検証がNGの場合に遷移する状態。通常は無限ループなどのアイドル状態となりリセットで再起動する。 |
ブート成功 | アプリケーション実行OKだった場合に遷移する。 |
2.4 wolfBootの特徴
wolfBootはwolfSSL社の提供するセキュアファームウェア更新のフレームワークです。 IETF SUIT ワーキンググループの推奨に従い、ターゲットデバイスでの実行を許可する前にファームウェアの真正性と整合性を検証し安全なファームウェア更新を実現します。署名検証などの暗号アルゴリズム部分には暗号エンジン wolfCrypt の公開鍵ベースの認証メカニズムを使用します。
wolfBootには次のような特徴があります。
- 安全な更新が行われるように2.3で説明したような状態遷移図を実装している。
- 基本的には組み込み向けを強く意識し、C言語で記述されており、ベアメタル(OSなし)でも動作するように小型・軽量のブートローダです。
- フラッシュメモリへのアクセスに関しては、アクセス先デバイス毎に異なる仕様があるため抽象化レイヤーを持っています。
- 暗号レイヤーにwolfCrypt を利用することでテスト済みの豊富で暗号アルゴリズムを利用できます。
セキュアなファームウェア更新を動かしてみる
セキュアなファームウェア更新の動きの理解をさらに深めるためにwolfBoot に含まれるサンプルプログラムを動かしてみます。サンプルプログラムは、ルネサス社製MCU RX72N で動作するものです。最初に署名に使用する鍵生成ツールおよび署名ツールを作成するところから始めます。次いで、wolfBoot とサンプルプログラムをコンパイルします。サンプルプログラムはフラッシュライターで書き込みます。フラッシュ書き込み用に変換するためにGCCのツールを使用します。
3.1 準備
A) 使用するハードウェア
サンプルプログラムを動作させるために必要となるハードウェアは下記となります。
- Windows PC
- Renesas RX72N Envision Kit
- USB 2.0 Micro-B(マイクロB)(PCとボード間を接続します。)
B) 使用するソフトウェアのインストール
以下の表4のツールをWindows PCへ予めインストールしておきます。
名称 | 備考 |
---|---|
wolfBoot wolfCrypt | https://github.com/wolfssl/wolfBoot からGitを使ってCloneします。3.2のStep1参照のこと。wolfCrptは、wolfBoot の/lib/wolfSSL以下にサブモジュールとして含まれます。 |
e2studio Windows版 | Renesas社サイトよりダウンロード |
CCRX | Renesas社製RX用コンパイラ |
Renesas Flash Programmer | Renesas社製Flash Writer V3.12 |
GCC for Renesas RX 8.3.0.202202-GNU-RX-ELF | Flash書き込み用バイナリ作成のため使用。 https://llvm-gcc-renesas.com/rx-download-toolchains/ |
3.2 署名検証用の鍵を作成
署名検証用の鍵生成および署名ツールは、wolfBoot のソースに含まれています。ここでは、LinuxやWSLなどのコマンド環境を想定しています。
1.wolfBoot のソースコード取得とビルド
$ git clone --depth 1 https://github.com/wolfssl/wolfBoot $ git submodule init $ git submodule update $ cd wolfBoot $
2.鍵生成ツールおよび署名ツールを作成
/* サンプルのコンフィグをコピー */ $ cp ./config/examples/sim.config .config $ make keytools /* 作成されたツールは、tools/keytools ファルダの keygen と sign */ $ ls -al tools/keytools/keygen tools/keytools/sign -rwxr-xr-x 1 xxxx yyyyy 669640 Oct 19 14:01 tools/keytools/keygen -rwxr-xr-x 1 xxxx yyyyy 681712 Oct 19 14:01
3.RSA2048ビットの鍵生成
/* key tools へのパスを通す */ $ export PATH=$PATH:/foo/bar/wolfBoot/tools/keytools $ keygen --rsa2048 -g ./pri-rsa2048.der Keytype: RSA2048 Gen ./pri-rsa2048.der Generating key (type: RSA2048) RSA public key len: 294 bytes Associated key file: ./pri-rsa2048.der Key type : RSA2048 Public key slot: 0 Done.
3.3 ファームウェア更新の手順
A)e2studioでプロジェクトを開く
e2studioで開いてコンパイルしてみます。プロジェクトは基盤になるwolfBootライブラリとサンプルプロジェクトの入った2つになります。
(ア)インストール済みのe2studioを起動します。
(イ)[ファイル・システムからプロジェクトを開く..]を選択し、インポート元に<wolfSSL Boott Folder>\IDE\Renesas\e2studio\RX72N\wolfBoot を指定します。 [終了]ボタンをクリックします。
(ウ)[ファイル・システムからプロジェクトを開く..]を選択し、インポート元に<wolfSSL Boott Folder>\IDE\Renesas\e2studio\RX72N\app_RenesasRX01 を指定します。 [終了]ボタンをクリックします。
(エ)[app_RenesasRX01]と[wolfssl]の二つのプロジェクトが開かれたことを確認します
B)wolfBootの構成
wolfBoot プロジェクトのソースファイルは次の表のようなフォルダで構成されています。
ソースコード | ||
wolfBoot/src | ||
hal | フラッシュメモリアクセス用の抽象化レイヤー Renesas RX用のソースコード | |
wolfboot | wolfBoot 本体 | |
wolfcrypt | 暗号処理用のwolfCryptソースコード <wolfBoot>/lib/wolfssl以下にインポートされたwolfSSLライブラリから必要なものをピックアップ | |
keystore.c | 鍵作成ツール keygen が作成するソースコード。公開鍵に関するデータを格納している | |
ヘッダーファイル | ||
wolfBoot/src | ||
tareget.h | ブート領域、更新領域などメモリ領域の定義マクロ | |
user_settings.h | 機能の有効・無効化のマクロ定義 例:署名に使用するアルゴリズムの定義など |
C)サンプルプログラムの内容
サンプルプログラムは、非常に単純なものです。ブート領域と更新領域の状態情報および自身のバージョン番号を出力します。また、バージョン番号が“1”の場合、またはそれが“2”の場合で、動作を少しだけ変えています。
void main(void) { uint8_t firmware_version = 0; printf("| ------------------------------------------------------------------- |\n"); printf("| Renesas RX User Application in BOOT partition started by wolfBoot |\n"); printf("| ------------------------------------------------------------------- |\n\n"); hal_init(); printPartitions(); /* The same as: wolfBoot_get_image_version(PART_BOOT); */ firmware_version = wolfBoot_current_firmware_version(); printf("\nCurrent Firmware Version: %d\n", firmware_version); if (firmware_version >= 1) { if (firmware_version == 1) { printf("Hit any key to call wolfBoot_success the firmware.\n"); getchar(); wolfBoot_success(); printPartitions(); printf("\nHit any key to update the firmware.\n"); getchar(); wolfBoot_update_trigger(); printf("Firmware Update is triggered\n"); printPartitions(); } else if (firmware_version == 2) { printf("Hit any key to call wolfBoot_success the firmware.\n"); getchar(); wolfBoot_success(); printPartitions(); } } else { printf("Invalid Firmware Version\n"); goto busy_idle; } /* busy wait */ busy_idle: while (1) ; }
D)バージョン1のコンパイルから実行まで
(ア)スマートコンフィグレータ
最初に必要なソースファイルをスマートコンフィグレータで生成します。生成されるコードは[wolfBoot]プロジェクトでも参照することから、最初にこのステップを行います。[test]プロジェクトに含まれるwolfBoot.cfgファイルをクリックし、スマートコンフィグレータを起動し[コンポーネント]タブを開きます。「コードの生成」ボタンをクリックしコードを生成します。必要なコンポーネントは登録されているので、単に「コード生成」を行うだけで必要なコードが生成されます
(イ)wolfBootをコンパイル
[wolfBoot]プロジェクトを選択し、右クリック→プロジェクトのビルドでwolfBootをビルドします。
(ウ)app_RenesasRX01アプリケーションをコンパイル
セクション設定ファイルを読み込みます。[app_RenesasRX01]プロジェクトを選択し、右クリック→プロパティ→C/C++ビルド→設定→ツール設定タブ→Linker→セクション→セクションビューアを起動し、インポートボタンをクリックします。ダイアログから
次に[app_RenesasRX01]プロジェクトを選択し、右クリック→プロジェクトのビルドでapp_RenesasRX01をビルドします。
(エ)作成された app_RenesasRX01のELF 形式のファイルから不必要なセクションを取り除いたバイナリを作成します。
[app_RenesasRX01] の [HardwareDebug]フォルダにあるapp_RenesasRX01.x を適当なフォルダにコピーします。[GCC for Renesas RX 8.3.0.202202-GNU-RX-ELF]の[bin]フォルダにPATHが通っていることを確認し、コマンドプロントやWSLなどを開いて下記コマンドを実行します。
$ rx-elf-objcopy.exe -O binary\ -R '$ADDR_C_FE7F5D00' -R '$ADDR_C_FE7F5D10' -R '$ADDR_C_FE7F5D20' -R '$ADDR_C_FE7F5D30'\ -R '$ADDR_C_FE7F5D40' -R '$ADDR_C_FE7F5D48' -R '$ADDR_C_FE7F5D50' -R '$ADDR_C_FE7F5D64'\ -R '$ADDR_C_FE7F5D70' -R EXCEPTVECT -R RESETVECT app_RenesasRx01.x app_RenesasRx01.bin
(オ)作成された app_RenesasRx01.bin にV1の署名を行います。3.4でクローンした wolfBootのルートフォルダで下記コマンドを実行します。App_RenesasRx01_v1.0_signed.bin というファイルが生成されます。
$ sign --rsa2048 app_RenesasRx01.bin ./pri-rsa2048.der 1.0 $ ls -al app_RenesasRx01_v1.0_signed.bin -rw-r--r-- 1 xxxx yyyy 22643 Oct 20 09:38 app_RenesasRx01_v1.0_signed.bin
(カ)バイナリファイルからFlash Programmer を使ってFlashに書き込めるように hex ファイルを生成します。
$ rx-elf-objcopy.exe -I binary -O srec --change-addresses=0xffc10000 app_RenesasRx01_v1.0_signed.bin app_RenesasRx01_v1.0_signed.hex
(キ)Renesas Flash Programmerを使って hex ファイルを書き込みます。
(ク)e2studioから wolfBoot プロジェクトを起動します。
初回起動時には、デバック用ランチャがないので、[wolfBoot]プロジェクトを選択し、右クリック→デバック→3 Renesas GDB Hardware Debugging を選択します。Renesas Hardware Debugging 画面が開くので、E2 Lite(RX) を選択します。ターゲットデバイスには、R5F572NNを選択します。
デバック構成からwolfBoot.x を選択します。DebuggingタブのConnection Settings を開きます。表5のプロパティをそれぞれ設定します。
クロック メインクロックソース | EXTAL |
ターゲットボードとの接続 接続タイプ | Fine |
wolfBoot.x のデバックを開始します。
(ケ)実行結果をRenesas Debug Virtual Console で確認します。以下のようなメッセージが出力されていれば、ファームウェアの署名検証が成功しアプリケーションへ実行が移ります。
| ------------------------------------------------------------------- | | Renesas RX User Application in BOOT partition started by wolfBoot | | ------------------------------------------------------------------- | === Boot Partition[ffc10000] === Magic: WOLF Version: 01 Status: ff (New) Tail Mgc: ���� === Update Partition[ffdf8000] === Magic: ���� Version: 00 Status: ff (New) Tail Mgc: ���� Current Firmware Version: 1 Hit any key to call wolfBoot_success the firmware.
Boot Partition のイメージのステータスは、“FF”(新規)となっています。何かキーを押し処理を続行します。
Hit any key to call wolfBoot_success the firmware.
=== Boot Partition[ffc10000] === Magic: WOLF Version: 01 Status: 00 (Success) Tail Mgc: BOOT === Update Partition[ffdf8000] === Magic: ���� Version: 00 Status: 00 (Success) Tail Mgc: BOOT Hit any key to update the firmware.
アプリケーションより wolfBoot_success APIが呼び出され、ファームウェアが正しく実行されたことがwolfBootに通知され、イメージの状態は“00”(成功)になっています。バージョン2アプリケーションの更新を実行するために、さらにキーを入力しwolfBootの処理を続行します。
Hit any key to update the firmware. Firmware Update is triggered === Boot Partition[ffc10000] === Magic: WOLF Version: 01 Status: 00 (Success) Tail Mgc: BOOT === Update Partition[ffdf8000] === Magic: ���� Version: 00 Status: 70 (Updating) Tail Mgc: BOOT
Update Partition の状態が、“70”(更新)になっています。次にバージョン2のアプリケーションへの更新を行っています。
E)バージョン2のコンパイルから実行まで
(ア)バージョン2アプリケーションの作成
本来の作業ではここで何かしらコードの変更がありバージョンを更新します。このサンプルではヘッダーに埋め込まれたバージョン番号を表示するのでD)の(エ)で作成したバイナリをそのまま使用し、バージョン番号のみを署名作成時にインクリメントします。同じプログラムですが、これにより更新後はバージョン2.0と表示されるはずです。
$ sign --rsa2048 app_RenesasRx01.bin ./pri-rsa2048.der 2.0
(イ)バイナリファイルからFlash Programmer を使ってFlashに書き込めるように hex ファイルを生成します。–change-addressesに指定するアドレスは、更新領域の0xffdf8000であることに注意してください。
$ rx-elf-objcopy.exe -I binary -O srec --change-addresses=0xffdf8000 app_RenesasRx01_v2.0_signed.bin app_RenesasRx01_v2.0_signed.hex
(ウ)Renesas Flash Programmerを使って hex ファイルを書き込みます。書き込みアドレスは、0xffdf8000です。
(エ)e2studioから wolfBoot プロジェクトを起動します。
実行結果をRenesas Debug Virtual Console で確認します。以下のようなメッセージが出力されていれば、更新ファームウェアの署名検証が成功しています。
| ------------------------------------------------------------------- | | Renesas RX User Application in BOOT partition started by wolfBoot | | ------------------------------------------------------------------- | === Boot Partition[ffc10000] === Magic: WOLF Version: 02 Status: 10 (Testing) Tail Mgc: BOOT === Update Partition[ffdf8000] === Magic: WOLF Version: 01 Status: 30 (Unknown) Tail Mgc: BOOT Current Firmware Version: 2 Hit any key to call wolfBoot_success the firmware.
ブート領域のステータスはいまだ“10(Testing)”です。これはまだwolfBoot_success APIが呼び出されていないためです。何かキーを押して進めます。
=== Boot Partition[ffc10000] === Magic: WOLF Version: 02 Status: 00 (Success) Tail Mgc: BOOT === Update Partition[ffdf8000] === Magic: WOLF Version: 01 Status: 00 (Success) Tail Mgc: BOOT
ステータスが“00(Success)”となったことが分かります。
まとめ
ここまでセキュアなファームウェア更新の基本的な機能について説明してきました。
最後にまとめとして、wolfBoot/wolfCrypt のサポートする環境などについて紹介します。
wolfBoot :
- IETF SUIT WG推奨に従い、ファームウェアの真正性と完全性を検証
- ARM, Risc-V 他、複数のアーキテクチャをサポート
- コンパクトな設計
- コンパクトなHAL API
- OSに非依存
wolfCrypt :
- wolfSSL TLS/SSL ライブラリの暗号エンジンとして高い信頼性
- コンパクトな設計で小さなフットプリント
- シンプルでライセンスクリーンなAPI
- 多数のハードウェア暗号化のサポート
- 非OSを含む種々のOSサポート
wolfBoot, wolfCrypt 製品の詳細ついては、下記URLも参照ください。
wolfBoot : https://wolfssl.jp/products/wolfboot/
wolfCrypt : https://wolfssl.jp/products/wolfcrypt/