Introduction
Recently, I was tasked with implementing Secure Boot on the ODROID M1 (RK3568B). In this post, I will describe how I managed to store the hash of the public key in the OTP memory and enabled verification of the pre-loader’s signature (Rockchip TPL + U-Boot SPL) as well as the verification of the U-Boot’s configuration by SPL, which includes hashes of all images contained in the FIT image.
Enabling Secure Boot
Enabling Secure Boot on ARM is based on storing the public key in a secure place known by the BootROM so it can be used to verify the pre-loader signature. Typically it is eFuse, OTP or PUF those are scarce and expensive resources. On a RK3568 SoC, instead of storing the public key, we only store its hash in OTP (One Time Programmable) memory, while the public key is embedded inside the pre-loader, which contains TPL (Tertiary Program Loader) and SPL (Secondary Program Loader).
When booting after Secure Boot is enabled, BootROM first calculates the hash of the public key that’s stored in the pre-loader and checks if it’s identical to the hash stored inside OTP memory. After successful hash verification, it uses this key to verify TPL and SPL signatures. If signatures match, then Boot ROM boots verified image. Verification process looks as on following image1:
Plan
When starting this endeavor, I planned to achieve 2 things:
- Enable stage 2 (TPL & SPL) signature verification by BootROM
- Boot fairly new mainline U-Boot with stage 3 (U-Boot itself) verification by U-Boot SPL
Most of this blog post deals with the first part, as it is the one that took me the longest. It can be split into a couple of stages:
- Generate RSA keys and certificate
- Build U-Boot SPL that can write hash to OTP
- Add previously generated public key to the U-Boot SPL signature node and sign U-Boot
- Add
burn-key-hash = 0x1
parameter to the U-Boot SPL signature node - Write U-Boot SPL and U-Boot to Odroid-M1
- Verify that Secure Boot was enabled
After booting, U-Boot SPL should detect burn-key-hash
parameter in the
signature node during signature verification and initiate writing public key
hash to OTP memory, after which unsigned images shouldn’t be able to boot.
Preparation
Repositories
To complete this stage, I needed a couple of repositories:
- Rockchip U-Boot - This U-Boot version contains a function that saves the hash of the public key to OTP memory, to be exact, it’s rsa_burn_key_hash. Hardkernel U-Boot also contains this functionality. I didn’t test this version, but it should work with minimal changes to other steps.
- rkbin
- Contains needed files:
Rockchip TPL
,BL31
,boot_merger
andrk_sign_tool
. - upgrade_tool
- We need this version of the tool because
upgrade_tool
andrkdeveloptool
contained in the rkbin repository can’t handle loaders generated with a new idb header.
I used the newest commits available in those repositories.
Generating RSA Keys and certificate
To enable Secure Boot, I needed to generate RSA 2048-bit key. While the SoC
datasheet says that RK3568 Supports up to 4096 bits PKA mathematical operations for RSA/ECC
I had to use 2048 bits because it’s the only key length accepted by
rsa_burn_key_hash
:
|
|
To generate RSA keys and certificates, I decided to use the openssl
tool.
|
|
Final directory structure
This is my final directory structure, which contains the needed repositories and tools. I also created a symlink to upgrade_tool to make commands shorter.
|
|
Dependencies
Below are the packages needed to build U-Boot on Debian.
|
|
To build Rockchip U-Boot, I also needed a cross-compiler. By default, make.sh
script uses Linaro 6.3.1 toolchain. At first, I tried to use a cross-compiler
installed from the apt package manager, but unfortunately, the build ended in
errors.
Fixing one error led to another, so I chose to use
Linaro
compiler.
Configuration
rkbin
In rkbin/RKBOOT/RK3568MINIALL.ini
, I had to update FlashBoot to point to the
u-boot-spl.bin
file. Later this .ini
file will be used by the boot_merger
tool to create the rk356x_spl_loader_v1.21.113.bin
file, containing U-Boot
SPL. It’ll then be written to SPI flash memory.
|
|
U-Boot
I had to update the cross-compiler path in the CROSS_COMPILE_ARM64
variable in
the make.sh
file in the U-Boot repository, so it pointed to where my
cross-compiler was installed.
|
|
ODROID-M1 uses RK3568B SoC, so I used rk3568_defconfig
configuration file as a
base.
|
|
SPL will store key’s hash in OTP memory if the signature node containing the
public key has the property burn-key-hash = 0x1
. To add a node with the public
key to SPL, we can use the mkimage
tool. During my first attempt, I used a
tool from the mainline U-Boot that I had on hand because without changes to the
configuration, mkimage
from the Rockchip repository cannot add the key.
Unfortunately, the created signature node had an incorrect format. I checked the
contents of the signature node using the command:
|
|
In the left column is a signature node created using mkimage
from
mainline U-Boot, and in the right column is the correct signature node created
with mkimage
built from the Rockchip repository. The mainline U-Boot signature
node lacks some properties that the rsa_burn_key_hash
function requires, e.g.,
rsa,c
.
|
|
To build mkimage
from the Rockchip repository that can add a public key to
SPL, I had to set
CONFIG_FIT_SIGNATURE
and
CONFIG_SPL_FIT_SIGNATURE
Building U-Boot
I used make.sh
script to build U-Boot. The build was completed successfully
before setting the FIT_SIGNATURE
and SPL_FIT_SIGNATURE
variables. After
setting those variables, the make.sh
script ended in error, but fortunately,
everything I needed was built correctly. The error only happened when trying to
add signatures to the image.
Build output should show Platform RK3568 is build OK, with exist .config
message along with either one of those errors:
-
When there are no keys in
u-boot/keys
directory1
ERROR: No keys/dev.key
-
When there are keys in
u-boot/keys
directory1 2
Failed: external offset 0x1000 overlaps FIT length 0x1200 ./tools/mkimage Can't add hashes to FIT blob: -22
The files we need are spl/u-boot-spl.dtb
and u-boot.itb
.
Now it’s time to add a public key to u-boot-spl.dtb
|
|
To verify whether the signature node with the public key was added to SPL, we
can use the fdtget
command that I used before.
Now that I had SPL with the public key, I needed to add the burn-key-hash
property to this node. When SPL sees this property, it’ll try to write the
public key hash to OTP memory.
|
|
After that, I created a new u-boot-spl.bin
file.
|
|
Creating loader
In this step, I created a loader which will be used to write a pre-loader
(U-Boot TPL and SPL) to SPI flash memory.
I used the boot_merger
tool from the rkbin repository to create a loader as
the one created when building U-Boot contains old SPL without a signature node.
To do that, I used RKBOOT/RK3568MINIALL.ini
config file that was modified in
configuration step
|
|
There should now be a rk356x_spl_loader_v1.21.113.bin
file in the rkbin
folder.
It should contain my pre-loader (TPL + SPL) and the rk356x_usbplug_vX.Y.bin
image that will allow me to write the pre-loader to SPI memory.
Sending loader to ODROID
I needed to enter MaskROM mode on ODROID to write the pre-loader to SPI flash memory. Restarting the device while pressing the recovery button is the easiest way. This way, ODROID will try to boot the pre-loader from eMMC/SD memory. If no eMMC/SD is connected, the platform will enter MaskROM mode.
Clearing SPI
This step could most likely be skipped. I’ll describe it because during my
attempts to enable Secure Boot, I cleared SPI memory multiple times.
I used upgrade_tool
from Hardkernel.
|
|
On the UART console, there should be output from the loader:
|
|
Now restart the platform and enter MaskROM mode again.
Upgrading pre-loader
To upgrade the pre-loader, I had to send the loader to ODROID. The upgrade is
done with one command that first boots the usbplug.bin
file embedded in the
loader image, which allows upgrade_tool
to write the pre-loader to SPI flash
memory.
|
|
Writing key hash to OTP
The pre-loader created in the previous step will write the hash to OTP memory
when it encounters the burn-key-hash
property inside the signature
node.
It’ll only happen when trying to verify the signature of the next boot stage,
i.e., U-Boot.
In my case, there was nothing in SPI flash except the pre-loader, so I had to
also flash the U-Boot image. I decided to do it on an SD card because it was
easier and faster.
To do that, I created 3 partitions:
Partlabel | Starting sector | size |
---|---|---|
spl | 64 | 4M |
uboot | 16384 | 4M |
misc | 24576 | 4M |
spl
- I’ll use this partition in later steps to write mainline U-Boot pre-loader, i.e.,idbloader.img
file. In this step, it can remain empty.uboot
- partition containing U-Boot. In this step, I also flashedu-boot.itb
image to this partition.misc
- Rockchip U-Boot SPL needs this partition. Without it, booting fails before verification, and the key hash is written.
After inserting the SD card into ODROID-M1 and restarting it, I got this output on UART console:
|
|
From this moment, ODROID stopped booting any image that wasn’t signed with the correct keys.
Verification
To verify whether the platform verifies signatures, I tried to run unsigned
loader/pre-loader and ones signed with the wrong keys. We can also check if
Secure Boot is enabled by booting the ramboot loader, which contains TPL and the
rk3568_ramboot_v1.08.bin
file.
Generating ramboot loader
To check the SecureMode state, we need to run the ramboot loader. To do that, I
used the boot_merger
tool with the RK3568MINIALL_RAMBOOT.ini
config file to
create rk356x_ramboot_loader_v1.21.108.bin
:
|
|
Signing loader
Now we need to sign the generated rk356x_ramboot_loader_v1.21.108.bin
loader.
To do that, I used rk_sign_tool
from the rkbin repository.
First, I needed to configure this tool with the correct SoC and keys.
|
|
After which, I signed the loader.
|
|
The loader was signed correctly even though the tool printed some failures.
Sending ramboot loader
I again used upgrade_tool
to send the ramboot loader, but this time with the
db
option.
|
|
I got this output on the UART console.
|
|
Before writing hash to OTP memory, the ramboot loader returned SecureMode = 0
.
Mainline U-Boot with signature verification
Now that we have pre-loader (TPL + SPL) signature verification, we can use the pre-loader to verify the next steps.
To complete this stage, I decided to use v2024.01 mainline U-Boot with a couple of changes. I also needed the rkbin repository, RSA keys, and certificate (copied into the u-boot directory). It’s possible to use the same key as earlier or create a new one.
U-Boot configuration
The main changes needed in my commit were just adding signature
and
u-boot-spl-pubkey-dtb
node in
rockchip-u-boot.dtsi
and changing CONFIG_SPL_STACK_R_MALLOC_SIMPLE_LEN
config variable to
0x150000
to fix alloc space exhausted
error when booting.
|
|
To configure U-Boot, it’s enough to use odroid-m1-sb-rk3568_defconfig config and set couple variables.
|
|
I used the gcc-aarch64-linux-gnu
cross-compiler from the Debian package
manager this time.
Build
After configuration, we build it using make
. It should build a signed U-Boot
with a public key embedded inside SPL.
|
|
By default, odroid-m1-sb-rk3568_defconfig
enables signing of only
configuration. Anyone interested why can read more on
https://github.com/u-boot/u-boot.
Signing idbloader
Signing idbloader is similar to the Signing Loader section
except with the sb --idb
argument. It’s important to remember to sign
idbloader with the same keys used in that section (in case the current ones are
different).
|
|
You can verify whether idbloader.img
is signed correctly by using:
|
|
In case of an unsigned file, the command would return an invalid idblock tag
.
U-Boot Verification
To check if SPL is signed correctly and verifies U-Boot correctly, I have
written idbloader.img
file to spl
partition created in Writing key hash to
OTP. I also flashed the u-boot.itb
file to the
uboot
partition.
After writing the needed files and inserting the SD card into ODROID, I restarted the platform while pressing the recovery button.
|
|
The expected output should contain sha256,rsa2048:dev+ OK
, which means the
signature was verified correctly (+
sign).
What’s next
While I had managed to enable Secure Boot on Odroid, it would be good to test its security and capabilities more thoroughly. Some of the questions that I would like to find answers to are whether there really isn’t any way to overwrite the key hash stored in OTP and if it’s possible to store more than one. OTP has 8k bits of memory based on the RK3568 datasheet, while hashes are only 256 bits, so theoretically, we could store 32 different hashes.
A good next step would be to have an upstream capability of writing hash to OTP from Rockchip U-Boot to mainline U-Boot, simplifying the whole implementation.
Conclusion
Please let us know your experience integrating and provisioning Root of Trust and Chain of Trust technologies on ARM-based platforms, especially Rockchip.
For any questions or feedback, feel free to contact us at contact@3mdeb.com or hop on our community channels:
To join the discussion.
