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 = 0x1parameter 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_mergerandrk_sign_tool. - upgrade_tool
- We need this version of the tool because
upgrade_toolandrkdeveloptoolcontained 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/keysdirectory1ERROR: No keys/dev.key -
When there are keys in
u-boot/keysdirectory1 2Failed: 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.imgfile. In this step, it can remain empty.uboot- partition containing U-Boot. In this step, I also flashedu-boot.itbimage 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.