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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
|
Verified Boot on the Beaglebone Black
=====================================
Introduction
------------
Before reading this, please read verified-boot.txt and signature.txt. These
instructions are for mainline U-Boot from v2014.07 onwards.
There is quite a bit of documentation in this directory describing how
verified boot works in U-Boot. There is also a test which runs through the
entire process of signing an image and running U-Boot (sandbox) to check it.
However, it might be useful to also have an example on a real board.
Beaglebone Black is a fairly common board so seems to be a reasonable choice
for an example of how to enable verified boot using U-Boot.
First a note that may to help avoid confusion. U-Boot and Linux both use
device tree. They may use the same device tree source, but it is seldom useful
for them to use the exact same binary from the same place. More typically,
U-Boot has its device tree packaged wtih it, and the kernel's device tree is
packaged with the kernel. In particular this is important with verified boot,
since U-Boot's device tree must be immutable. If it can be changed then the
public keys can be changed and verified boot is useless. An attacker can
simply generate a new key and put his public key into U-Boot so that
everything verifies. On the other hand the kernel's device tree typically
changes when the kernel changes, so it is useful to package an updated device
tree with the kernel binary. U-Boot supports the latter with its flexible FIT
format (Flat Image Tree).
Overview
--------
The steps are roughly as follows:
1. Build U-Boot for the board, with the verified boot options enabled.
2. Obtain a suitable Linux kernel
3. Create a Image Tree Source file (ITS) file describing how you want the
kernel to be packaged, compressed and signed.
4. Create a key pair
5. Sign the kernel
6. Put the public key into U-Boot's image
7. Put U-Boot and the kernel onto the board
8. Try it
Step 1: Build U-Boot
--------------------
a. Set up the environment variable to point to your toolchain. You will need
this for U-Boot and also for the kernel if you build it. For example if you
installed a Linaro version manually it might be something like:
export CROSS_COMPILE=/opt/linaro/gcc-linaro-arm-linux-gnueabihf-4.8-2013.08_linux/bin/arm-linux-gnueabihf-
or if you just installed gcc-arm-linux-gnueabi then it might be
export CROSS_COMPILE=arm-linux-gnueabi-
b. Configure and build U-Boot with verified boot enabled:
export UBOOT=/path/to/u-boot
cd $UBOOT
# You can add -j10 if you have 10 CPUs to make it faster
make O=b/am335x_boneblack_vboot am335x_boneblack_vboot_config all
export UOUT=$UBOOT/b/am335x_boneblack_vboot
c. You will now have a U-Boot image:
file b/am335x_boneblack_vboot/u-boot-dtb.img
b/am335x_boneblack_vboot/u-boot-dtb.img: u-boot legacy uImage, U-Boot 2014.07-rc2-00065-g2f69f8, Firmware/ARM, Firmware Image (Not compressed), 395375 bytes, Sat May 31 16:19:04 2014, Load Address: 0x80800000, Entry Point: 0x00000000, Header CRC: 0x0ABD6ACA, Data CRC: 0x36DEF7E4
Step 2: Build Linux
--------------------
a. Find the kernel image ('Image') and device tree (.dtb) file you plan to
use. In our case it is am335x-boneblack.dtb and it is built with the kernel.
At the time of writing an SD Boot image can be obtained from here:
http://www.elinux.org/Beagleboard:Updating_The_Software#Image_For_Booting_From_microSD
You can write this to an SD card and then mount it to extract the kernel and
device tree files.
You can also build a kernel. Instructions for this are are here:
http://elinux.org/Building_BBB_Kernel
or you can use your favourite search engine. Following these instructions
produces a kernel Image and device tree files. For the record the steps were:
export KERNEL=/path/to/kernel
cd $KERNEL
git clone git://github.com/beagleboard/kernel.git .
git checkout v3.14
./patch.sh
cp configs/beaglebone kernel/arch/arm/configs/beaglebone_defconfig
cd kernel
make beaglebone_defconfig
make uImage dtbs # -j10 if you have 10 CPUs
export OKERNEL=$KERNEL/kernel/arch/arm/boot
c. You now have the 'Image' and 'am335x-boneblack.dtb' files needed to boot.
Step 3: Create the ITS
----------------------
Set up a directory for your work.
export WORK=/path/to/dir
cd $WORK
Put this into a file in that directory called sign.its:
/dts-v1/;
/ {
description = "Beaglebone black";
#address-cells = <1>;
images {
kernel {
data = /incbin/("Image.lzo");
type = "kernel";
arch = "arm";
os = "linux";
compression = "lzo";
load = <0x80008000>;
entry = <0x80008000>;
hash-1 {
algo = "sha1";
};
};
fdt-1 {
description = "beaglebone-black";
data = /incbin/("am335x-boneblack.dtb");
type = "flat_dt";
arch = "arm";
compression = "none";
hash-1 {
algo = "sha1";
};
};
};
configurations {
default = "conf-1";
conf-1 {
kernel = "kernel";
fdt = "fdt-1";
signature-1 {
algo = "sha1,rsa2048";
key-name-hint = "dev";
sign-images = "fdt", "kernel";
};
};
};
};
The explanation for this is all in the documentation you have already read.
But briefly it packages a kernel and device tree, and provides a single
configuration to be signed with a key named 'dev'. The kernel is compressed
with LZO to make it smaller.
Step 4: Create a key pair
-------------------------
See signature.txt for details on this step.
cd $WORK
mkdir keys
openssl genrsa -F4 -out keys/dev.key 2048
openssl req -batch -new -x509 -key keys/dev.key -out keys/dev.crt
Note: keys/dev.key contains your private key and is very secret. If anyone
gets access to that file they can sign kernels with it. Keep it secure.
Step 5: Sign the kernel
-----------------------
We need to use mkimage (which was built when you built U-Boot) to package the
Linux kernel into a FIT (Flat Image Tree, a flexible file format that U-Boot
can load) using the ITS file you just created.
At the same time we must put the public key into U-Boot device tree, with the
'required' property, which tells U-Boot that this key must be verified for the
image to be valid. You will make this key available to U-Boot for booting in
step 6.
ln -s $OKERNEL/dts/am335x-boneblack.dtb
ln -s $OKERNEL/Image
ln -s $UOUT/u-boot-dtb.img
cp $UOUT/arch/arm/dts/am335x-boneblack.dtb am335x-boneblack-pubkey.dtb
lzop Image
$UOUT/tools/mkimage -f sign.its -K am335x-boneblack-pubkey.dtb -k keys -r image.fit
You should see something like this:
FIT description: Beaglebone black
Created: Sun Jun 1 12:50:30 2014
Image 0 (kernel)
Description: unavailable
Created: Sun Jun 1 12:50:30 2014
Type: Kernel Image
Compression: lzo compressed
Data Size: 7790938 Bytes = 7608.34 kB = 7.43 MB
Architecture: ARM
OS: Linux
Load Address: 0x80008000
Entry Point: 0x80008000
Hash algo: sha1
Hash value: c94364646427e10f423837e559898ef02c97b988
Image 1 (fdt-1)
Description: beaglebone-black
Created: Sun Jun 1 12:50:30 2014
Type: Flat Device Tree
Compression: uncompressed
Data Size: 31547 Bytes = 30.81 kB = 0.03 MB
Architecture: ARM
Hash algo: sha1
Hash value: cb09202f889d824f23b8e4404b781be5ad38a68d
Default Configuration: 'conf-1'
Configuration 0 (conf-1)
Description: unavailable
Kernel: kernel
FDT: fdt-1
Now am335x-boneblack-pubkey.dtb contains the public key and image.fit contains
the signed kernel. Jump to step 6 if you like, or continue reading to increase
your understanding.
You can also run fit_check_sign to check it:
$UOUT/tools/fit_check_sign -f image.fit -k am335x-boneblack-pubkey.dtb
which results in:
Verifying Hash Integrity ... sha1,rsa2048:dev+
## Loading kernel from FIT Image at 7fc6ee469000 ...
Using 'conf-1' configuration
Verifying Hash Integrity ...
sha1,rsa2048:dev+
OK
Trying 'kernel' kernel subimage
Description: unavailable
Created: Sun Jun 1 12:50:30 2014
Type: Kernel Image
Compression: lzo compressed
Data Size: 7790938 Bytes = 7608.34 kB = 7.43 MB
Architecture: ARM
OS: Linux
Load Address: 0x80008000
Entry Point: 0x80008000
Hash algo: sha1
Hash value: c94364646427e10f423837e559898ef02c97b988
Verifying Hash Integrity ...
sha1+
OK
Unimplemented compression type 4
## Loading fdt from FIT Image at 7fc6ee469000 ...
Using 'conf-1' configuration
Trying 'fdt-1' fdt subimage
Description: beaglebone-black
Created: Sun Jun 1 12:50:30 2014
Type: Flat Device Tree
Compression: uncompressed
Data Size: 31547 Bytes = 30.81 kB = 0.03 MB
Architecture: ARM
Hash algo: sha1
Hash value: cb09202f889d824f23b8e4404b781be5ad38a68d
Verifying Hash Integrity ...
sha1+
OK
Loading Flat Device Tree ... OK
## Loading ramdisk from FIT Image at 7fc6ee469000 ...
Using 'conf-1' configuration
Could not find subimage node
Signature check OK
At the top, you see "sha1,rsa2048:dev+". This means that it checked an RSA key
of size 2048 bits using SHA1 as the hash algorithm. The key name checked was
'dev' and the '+' means that it verified. If it showed '-' that would be bad.
Once the configuration is verified it is then possible to rely on the hashes
in each image referenced by that configuration. So fit_check_sign goes on to
load each of the images. We have a kernel and an FDT but no ramkdisk. In each
case fit_check_sign checks the hash and prints sha1+ meaning that the SHA1
hash verified. This means that none of the images has been tampered with.
There is a test in test/vboot which uses U-Boot's sandbox build to verify that
the above flow works.
But it is fun to do this by hand, so you can load image.fit into a hex editor
like ghex, and change a byte in the kernel:
$UOUT/tools/fit_info -f image.fit -n /images/kernel -p data
NAME: kernel
LEN: 7790938
OFF: 168
This tells us that the kernel starts at byte offset 168 (decimal) in image.fit
and extends for about 7MB. Try changing a byte at 0x2000 (say) and run
fit_check_sign again. You should see something like:
Verifying Hash Integrity ... sha1,rsa2048:dev+
## Loading kernel from FIT Image at 7f5a39571000 ...
Using 'conf-1' configuration
Verifying Hash Integrity ...
sha1,rsa2048:dev+
OK
Trying 'kernel' kernel subimage
Description: unavailable
Created: Sun Jun 1 13:09:21 2014
Type: Kernel Image
Compression: lzo compressed
Data Size: 7790938 Bytes = 7608.34 kB = 7.43 MB
Architecture: ARM
OS: Linux
Load Address: 0x80008000
Entry Point: 0x80008000
Hash algo: sha1
Hash value: c94364646427e10f423837e559898ef02c97b988
Verifying Hash Integrity ...
sha1 error
Bad hash value for 'hash-1' hash node in 'kernel' image node
Bad Data Hash
## Loading fdt from FIT Image at 7f5a39571000 ...
Using 'conf-1' configuration
Trying 'fdt-1' fdt subimage
Description: beaglebone-black
Created: Sun Jun 1 13:09:21 2014
Type: Flat Device Tree
Compression: uncompressed
Data Size: 31547 Bytes = 30.81 kB = 0.03 MB
Architecture: ARM
Hash algo: sha1
Hash value: cb09202f889d824f23b8e4404b781be5ad38a68d
Verifying Hash Integrity ...
sha1+
OK
Loading Flat Device Tree ... OK
## Loading ramdisk from FIT Image at 7f5a39571000 ...
Using 'conf-1' configuration
Could not find subimage node
Signature check Bad (error 1)
It has detected the change in the kernel.
You can also be sneaky and try to switch images, using the libfdt utilities
that come with dtc (package name is device-tree-compiler but you will need a
recent version like 1.4:
dtc -v
Version: DTC 1.4.0
First we can check which nodes are actually hashed by the configuration:
fdtget -l image.fit /
images
configurations
fdtget -l image.fit /configurations
conf-1
fdtget -l image.fit /configurations/conf-1
signature-1
fdtget -p image.fit /configurations/conf-1/signature-1
hashed-strings
hashed-nodes
timestamp
signer-version
signer-name
value
algo
key-name-hint
sign-images
fdtget image.fit /configurations/conf-1/signature-1 hashed-nodes
/ /configurations/conf-1 /images/fdt-1 /images/fdt-1/hash /images/kernel /images/kernel/hash-1
This gives us a bit of a look into the signature that mkimage added. Note you
can also use fdtdump to list the entire device tree.
Say we want to change the kernel that this configuration uses
(/images/kernel). We could just put a new kernel in the image, but we will
need to change the hash to match. Let's simulate that by changing a byte of
the hash:
fdtget -tx image.fit /images/kernel/hash-1 value
c9436464 6427e10f 423837e5 59898ef0 2c97b988
fdtput -tx image.fit /images/kernel/hash-1 value c9436464 6427e10f 423837e5 59898ef0 2c97b981
Now check it again:
$UOUT/tools/fit_check_sign -f image.fit -k am335x-boneblack-pubkey.dtb
Verifying Hash Integrity ... sha1,rsa2048:devrsa_verify_with_keynode: RSA failed to verify: -13
rsa_verify_with_keynode: RSA failed to verify: -13
-
Failed to verify required signature 'key-dev'
Signature check Bad (error 1)
This time we don't even get as far as checking the images, since the
configuration signature doesn't match. We can't change any hashes without the
signature check noticing. The configuration is essentially locked. U-Boot has
a public key for which it requires a match, and will not permit the use of any
configuration that does not match that public key. The only way the
configuration will match is if it was signed by the matching private key.
It would also be possible to add a new signature node that does match your new
configuration. But that won't work since you are not allowed to change the
configuration in any way. Try it with a fresh (valid) image if you like by
running the mkimage link again. Then:
fdtput -p image.fit /configurations/conf-1/signature-1 value fred
$UOUT/tools/fit_check_sign -f image.fit -k am335x-boneblack-pubkey.dtb
Verifying Hash Integrity ... -
sha1,rsa2048:devrsa_verify_with_keynode: RSA failed to verify: -13
rsa_verify_with_keynode: RSA failed to verify: -13
-
Failed to verify required signature 'key-dev'
Signature check Bad (error 1)
Of course it would be possible to add an entirely new configuration and boot
with that, but it still needs to be signed, so it won't help.
6. Put the public key into U-Boot's image
-----------------------------------------
Having confirmed that the signature is doing its job, let's try it out in
U-Boot on the board. U-Boot needs access to the public key corresponding to
the private key that you signed with so that it can verify any kernels that
you sign.
cd $UBOOT
make O=b/am335x_boneblack_vboot EXT_DTB=${WORK}/am335x-boneblack-pubkey.dtb
Here we are overriding the normal device tree file with our one, which
contains the public key.
Now you have a special U-Boot image with the public key. It can verify can
kernel that you sign with the private key as in step 5.
If you like you can take a look at the public key information that mkimage
added to U-Boot's device tree:
fdtget -p am335x-boneblack-pubkey.dtb /signature/key-dev
required
algo
rsa,r-squared
rsa,modulus
rsa,n0-inverse
rsa,num-bits
key-name-hint
This has information about the key and some pre-processed values which U-Boot
can use to verify against it. These values are obtained from the public key
certificate by mkimage, but require quite a bit of code to generate. To save
code space in U-Boot, the information is extracted and written in raw form for
U-Boot to easily use. The same mechanism is used in Google's Chrome OS.
Notice the 'required' property. This marks the key as required - U-Boot will
not boot any image that does not verify against this key.
7. Put U-Boot and the kernel onto the board
-------------------------------------------
The method here varies depending on how you are booting. For this example we
are booting from an micro-SD card with two partitions, one for U-Boot and one
for Linux. Put it into your machine and write U-Boot and the kernel to it.
Here the card is /dev/sde:
cd $WORK
export UDEV=/dev/sde1 # Change thes two lines to the correct device
export KDEV=/dev/sde2
sudo mount $UDEV /mnt/tmp && sudo cp $UOUT/u-boot-dtb.img /mnt/tmp/u-boot.img && sleep 1 && sudo umount $UDEV
sudo mount $KDEV /mnt/tmp && sudo cp $WORK/image.fit /mnt/tmp/boot/image.fit && sleep 1 && sudo umount $KDEV
8. Try it
---------
Boot the board using the commands below:
setenv bootargs console=ttyO0,115200n8 quiet root=/dev/mmcblk0p2 ro rootfstype=ext4 rootwait
ext2load mmc 0:2 82000000 /boot/image.fit
bootm 82000000
You should then see something like this:
U-Boot# setenv bootargs console=ttyO0,115200n8 quiet root=/dev/mmcblk0p2 ro rootfstype=ext4 rootwait
U-Boot# ext2load mmc 0:2 82000000 /boot/image.fit
7824930 bytes read in 589 ms (12.7 MiB/s)
U-Boot# bootm 82000000
## Loading kernel from FIT Image at 82000000 ...
Using 'conf-1' configuration
Verifying Hash Integrity ... sha1,rsa2048:dev+ OK
Trying 'kernel' kernel subimage
Description: unavailable
Created: 2014-06-01 19:32:54 UTC
Type: Kernel Image
Compression: lzo compressed
Data Start: 0x820000a8
Data Size: 7790938 Bytes = 7.4 MiB
Architecture: ARM
OS: Linux
Load Address: 0x80008000
Entry Point: 0x80008000
Hash algo: sha1
Hash value: c94364646427e10f423837e559898ef02c97b988
Verifying Hash Integrity ... sha1+ OK
## Loading fdt from FIT Image at 82000000 ...
Using 'conf-1' configuration
Trying 'fdt-1' fdt subimage
Description: beaglebone-black
Created: 2014-06-01 19:32:54 UTC
Type: Flat Device Tree
Compression: uncompressed
Data Start: 0x8276e2ec
Data Size: 31547 Bytes = 30.8 KiB
Architecture: ARM
Hash algo: sha1
Hash value: cb09202f889d824f23b8e4404b781be5ad38a68d
Verifying Hash Integrity ... sha1+ OK
Booting using the fdt blob at 0x8276e2ec
Uncompressing Kernel Image ... OK
Loading Device Tree to 8fff5000, end 8ffffb3a ... OK
Starting kernel ...
[ 0.582377] omap_init_mbox: hwmod doesn't have valid attrs
[ 2.589651] musb-hdrc musb-hdrc.0.auto: Failed to request rx1.
[ 2.595830] musb-hdrc musb-hdrc.0.auto: musb_init_controller failed with status -517
[ 2.606470] musb-hdrc musb-hdrc.1.auto: Failed to request rx1.
[ 2.612723] musb-hdrc musb-hdrc.1.auto: musb_init_controller failed with status -517
[ 2.940808] drivers/rtc/hctosys.c: unable to open rtc device (rtc0)
[ 7.248889] libphy: PHY 4a101000.mdio:01 not found
[ 7.253995] net eth0: phy 4a101000.mdio:01 not found on slave 1
systemd-fsck[83]: Angstrom: clean, 50607/218160 files, 306348/872448 blocks
.---O---.
| | .-. o o
| | |-----.-----.-----.| | .----..-----.-----.
| | | __ | ---'| '--.| .-'| | |
| | | | | |--- || --'| | | ' | | | |
'---'---'--'--'--. |-----''----''--' '-----'-'-'-'
-' |
'---'
The Angstrom Distribution beaglebone ttyO0
Angstrom v2012.12 - Kernel 3.14.1+
beaglebone login:
At this point your kernel has been verified and you can be sure that it is one
that you signed. As an exercise, try changing image.fit as in step 5 and see
what happens.
Further Improvements
--------------------
Several of the steps here can be easily automated. In particular it would be
capital if signing and packaging a kernel were easy, perhaps a simple make
target in the kernel.
Some mention of how to use multiple .dtb files in a FIT might be useful.
U-Boot's verified boot mechanism has not had a robust and independent security
review. Such a review should look at the implementation and its resistance to
attacks.
Perhaps the verified boot feature could could be integrated into the Amstrom
distribution.
Simon Glass
sjg@chromium.org
2-June-14
|