| 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 |