Raspberry Pi 4にはHTTP経由でFAT32のブートイメージを取得して起動する機能が追加されましたが、 HTTPなのでそのままでは改ざんされる可能性があります。これに対してSecure Boot機能なるものが追加されたようですので試してみます。 (この機能では改ざんは防げますが、盗聴は防げません。また、ブートイメージのサイズが大きすぎると失敗します。ご注意ください。)

手順としては次のようになります。

  • FAT32のブートイメージ(boot.img)を作成
  • 検証用のRSA鍵ペアを生成
    • eepromに仕込む公開鍵: public.pem
    • イメージファイルに署名する秘密鍵: private.pem
  • eepromイメージをHTTP Secure Boot用にカスタマイズし、Raspberry Pi 4に書き込む
  • boot.imgに署名したboot.sigを生成し、eepromの設定に仕込んだWebサーバのパスに設置

必要なもの

  • Raspberry Pi 4本体(2台あると楽)
  • DHCPが利用できる有線ネットワーク
  • 到達可能なWebサーバ

boot.imgの作成

boot.imgは適当に作成しておいてください。 (今回はRaspberry Pi Imagerのboot.imgをそのまま用いました。)

とりあえずhttps://github.com/raspberrypi/firmware/tree/master/bootのファイルを全部持ってきて、 FAT32でフォーマットしたファイルイメージに全部コピーしておけば、kernelが起動するところまでは試せます。後はinitramfsにbusyboxを仕込むなどすればなんとでもなります。

検証用のRSA鍵ペアを生成

署名用の秘密鍵private.pemとeepromに仕込む公開鍵public.pemを生成します。

鍵フォーマットはごくふつうのRSA 2048bit鍵でよいようです。 (今のところこれ以外だと動作しないらしい)

1
2
% openssl genrsa 2048 > private.pem
% openssl rsa -in private.pem -pubout -out public.pem

eeprom-http-boot.conf

eepromの設定ファイルの例です。 主に編集が必要なのはBOOT_ORDERNET_INSTALL_ENABLEDNET_INSTALL_KEYBOARD_WAITHTTP_HOSTHTTP_PORTHTTP_PATHの部分です。

NET_INSTALL_ENABLED1にすると、起動時にネットインストール(HTTP Boot)の指示待ち (キーボードによるShiftキーの入力待ち)が行われるようになります。 この指示待ちはNET_INSTALL_KEYBOARD_WAITで指定された時間(今回は10000なので10000ms)行われ、 Shiftキーの入力があった場合にHTTP Bootが試行されます。 もし入力がなければ、BOOT_ORDERの順にブートが試行されます。

BOOT_ORDERでは、SDカード(BOOT_ORDER0x1)、 USBストレージ(BOOT_ORDER0x4)の順で参照、 いずれも失敗したらリセット(BOOT_ORDERの0xf)するようにしています。 もしHTTP Bootを強制的に実行したいのであれば、BOOT_ORDERの任意の場所に0x7を加えます。 BOOT_ORDER下位桁から順に参照される点に注意してください。

HTTP_HOSTHTTP_PORTHTTP_PATHは、その名の通りホスト名とポート番号、サーバ上でのパスです。 この例では、http://rpi-image.hachune.net/net_install/boot.imgからダウンロードされるようになります。 ブートイメージファイルの名前(boot.img)は変更できないようです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[all]
BOOT_UART=1
WAKE_ON_GPIO=1
POWER_OFF_ON_HALT=0
BOOT_ORDER=0xf41
SD_BOOT_MAX_RETRIES=1
HTTP_HOST=rpi-image.hachune.net
HTTP_PORT=80
HTTP_PATH=net_install
NET_INSTALL_ENABLED=1
NET_INSTALL_KEYBOARD_WAIT=10000
USB_MSD_DISCOVER_TIMEOUT=10000
FREEZE_VERSION=1

[none]

eepromイメージのカスタマイズ

rpi-eeprom-configコマンドと先ほど作成した設定ファイルeeprom-http-boot.confでeepromイメージをカスタマイズします。 ついでにSecure Boot用公開鍵も追加します。

1
2
3
4
5
6
# eepromイメージへの公開鍵の追加と設定の更新
% rpi-eeprom-config -c eeprom-http-boot.conf -p public.pem -o pieeprom.bin  /usr/lib/firmware/raspberrypi/bootloader/stable/pieeprom-2022-03-10.bin
# eepromイメージの検証用ハッシュを生成
% rpi-eeprom-digest -i pieeprom.bin -o pieeprom.sig
# アップデート用ファイル名に変更
% ln -f pieeprom.bin pieeprom.upd

pieeprom.updpieeprom.sigをHTTP bootしたいRaspberry Pi 4のboot用ファイルシステム内にコピーしておけば、次回起動時に自動的にeepromが更新されます。 更新後はカラフルでいかにもHTTP bootしそうな画面が出てくるようになります。

boot.imgの署名ファイル生成

Raspberry Pi 4の起動に必要なファームウェアやカーネル等が詰まったboot.imgに対し、先ほど作成した秘密鍵private.pemで署名を行い、boot.sigを生成します。

1
2
# private.pemを用いてboot.imgの署名ファイルboot.sigを生成
% rpi-eeprom-digest -i boot.img -o boot.sig -k private.pem

boot.imgとboot.sigの設置

設定ファイルeeprom-http-boot.confHTTP_HOSTとHTTP_PORTHTTP_PATHで設定したWebサーバにboot.imgboot.sigを設置します。 Webサーバはなんでも大丈夫ですが、必ずRaspberry Pi 4が利用するネットワークのDHCPサーバから得られるDNSサーバが、 Webサーバのアドレスを引けるようにしておく必要があります。 今回の場合は、http://rpi-image.hachune.net/net_install/boot.imghttp://rpi-image.hachune.net/net_install/boot.sigがダウンロードできるようにします。

HTTP bootする

すべての条件が整ったら、Raspberry Pi 4を起動します。 (テストしやすいようにストレージ類は全部抜いておきましょう) 起動したらすかさずShiftキーを押し続けます。

HTTP bootの様子(1)

うまくダウンロードが始まると、こんな感じになります。 HTTP bootの様子(2)

後はいつも通りカーネルとユーザランドのアプリが起動してきます。 HTTP bootの様子(3)

起動時のログ

eeprom設定ファイル作成時にBOOT_UART=1としておくと、こんな感じのログが得られます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
RPi: BOOTLOADER release VERSION:778c182c DATE: 2022/03/10 TIME: 11:57:12 BOOTMODE: 0x00000006 part: 0 BUILD_TIMESTAMP=1646913432 0x519624e9 0x00d03114 0x0005795f
PM_RSTS: 0x00001000
part 00000000 reset_info 00000000
uSD voltage 3.3V
Initialising SDRAM 'Micron' 32Gb x2 total-size: 64 Gbit 3200

XHCI-STOP
xHC ver: 256 HCS: 05000420 fc000031 00e70004 HCC: 002841eb
USBSTS 11
xHC ver: 256 HCS: 05000420 fc000031 00e70004 HCC: 002841eb
xHC ports 5 slots 32 intrs 4
Boot mode: SD (01) order f4
USB2[1] 400202e1 connected
USB2 root HUB port 1 init
DEV [01:00] 2.16 000000:01 class 9 VID 2109 PID 3431
HUB init [01:00] 2.16 000000:01
HUB [01:00] 2.16 000000:01 init port 3 speed 1
DEV [02:01] 2.00 000003:01 class 0 VID 17ef PID 6047
HID [02:01] 2.00 000003:01 register HID
HDMI0 edid block 0 offset 0
00ffffffffffff002157010001000000
011d0104824627782ad9b0a357499c25
11494b210800714081c0810081408180
9500a9c0b300023a801871382d40582c
4500c48e2100001e000000fd0017551e
641e0410000000000001000000f7000a
0040c6440000000000000000000000fc
0048444d4920544f205553420a2001d4
HDMI0 edid block 1 offset 128
02032cf151010203041112131f202122
3c3d3e905f6423090707830100006703
0c001000b83ce50e61606665011d0072
51d01e206e285500c48e2100001e8c0a
d08a20e02d10103e9600c48e21000018
8c0ad090204031200c405500c48e2100
00184e1f008051001e3040803700c48e
21000018000000000000000000000022
HDMI0: best-mode 1 (limit 1) CEA 1e000f80070000700000008010000000
GENET: RESET_PHY
PHY ID 600d 84a2
NET_BOOT: dc:a6:32:xx:xx:xx wait for link TFTP: 0.0.0.0
LINK STATUS: speed: 1000 full duplex
Link ready
GENET START: 64 16 32
GENET: UMAC_START 0xdca632b2 0x4bcf0000
RX: 0 IP: 0 IPV4: 0 MAC: 0 UDP: 0 UDP RECV: 0 IP_CSUM_ERR: 0 UDP_CSUM_ERR: 0
DHCP src: 62:bd:62:xx:xx:xx 192.168.254.254
YI_ADDR 192.168.254.105
SI_ADDR 192.168.254.254
OPTIONS:-
        op: 53 len:   1 DHCP recv OFFER (2) expect OFFER
        op: 54 len:   4 192.168.254.254
        op: 51 len:   4
        op:  1 len:   4 255.255.255.0
        op:  3 len:   4 192.168.254.254
        op:  6 len:   4 192.168.254.254
NET 192.168.254.105 255.255.255.0 gw 192.168.254.254 tftp 0.0.0.0
DHCP src: 62:bd:62:d6:de:5a 192.168.254.254
YI_ADDR 192.168.254.105
SI_ADDR 192.168.254.254
OPTIONS:-
        op: 53 len:   1 DHCP recv ACK (5) expect ACK
        op: 54 len:   4 192.168.254.254
        op: 51 len:   4
        op:  1 len:   4 255.255.255.0
        op:  3 len:   4 192.168.254.254
        op:  6 len:   4 192.168.254.254
        op: 66 len:  13 192.168.254.101[66]: 192.168.254.101

        op: 15 len:  16
NET 192.168.254.105 255.255.255.0 gw 192.168.254.254 tftp 0.0.0.0
RX: 2 IP: 0 IPV4: 2 MAC: 2 UDP: 2 UDP RECV: 2 IP_CSUM_ERR: 0 UDP_CSUM_ERR: 0
Loading boot.img ...
HTTP: GET request for http://rpi-image.hachune.net:80/net_install/boot.sig
SIG boot.sig b9fb1f930760ca62c981adab010eb20aeaff86079e62e147598f229d8813b54c 1649341567
HTTP: GET request for http://rpi-image.hachune.net:80/net_install/boot.img
HTTP: got 0% at 1 kB/s
HTTP: got 10% at 1285 kB/s
HTTP: got 28% at 2255 kB/s
HTTP: got 46% at 2747 kB/s
HTTP: got 64% at 3038 kB/s
HTTP: got 82% at 3234 kB/s
HTTP: got 100% at 3925 kB/s
Verifying
RSA verify
rsa-verify pass (0x0)
MBR: 0x00000000,       0 type: 0x00
MBR: 0x00000000,       0 type: 0x00
MBR: 0x00000000,       0 type: 0x00
MBR: 0x00000000,       0 type: 0x00
Trying partition: 0
type: 16 lba: 0 oem: 'mkfs.fat' volume: '  V       ^ '
rsc 4 fat-sectors 48 c-count 11759 c-size 4
root dir cluster 1 sectors 16 entries 256
FAT16 clusters 11759
Read config.txt bytes      280 hnd 0x52
Read start4.elf bytes  2240608 hnd 0x2151
Read fixup4.dat bytes     5354 hnd 0x53
Firmware: bd88f66f8952d34e4e0613a85c7a6d3da49e13e2 Jan 20 2022 13:56:48
0x00d03114 0x00000000 0x00000fff
MEM GPU: 76 ARM: 948 TOTAL: 1024
Starting start4.elf @ 0xfec00200 partition 0
Stopping network
RX: 15 IP: 0 IPV4: 6 MAC: 2 UDP: 2 UDP RECV: 2 IP_CSUM_ERR: 0 UDP_CSUM_ERR: 0
RX: 15 IP: 0 IPV4: 6 MAC: 2 UDP: 2 UDP RECV: 2 IP_CSUM_ERR: 0 UDP_CSUM_ERR: 0
GENET STOP: 0
GENET: RESET_PHY
PHY ID 600d 84a2
XHCI-STOP
xHC ver: 256 HCS: 05000420 fc000031 00e70004 HCC: 002841eb
USBSTS 18
+

Welcome to Buildroot
buildroot login:

rsa-verify pass (0x0)となっており、boot.imgの検証が成功していることが分かります。

eeprom更新における公開鍵の取り扱いについて

Raspberry Pi DocumentationAt the moment, if you then apply a public eeprom update, your key will be lost and will need to be re-added. とある通り、現時点ではeepromの更新を行うと公開鍵が失われるようです。 このため、eepromの更新を停止するといった処置(eepromの設定ファイルにFREEZE_VERSION=1を追記)が必要となります。

FREEZE_VERSION=1を解除する際は、microSDカードとrecovery.binを用いた復旧作業が必要となる点に注意してください。

参考

編集履歴