RK3576 first-stage loader: external (KMS-style) signing test
2026-11-03 · Wilber Zheng · host-only test · no RK board, no real keys, no cloud KMS used · throwaway test keys, deleted afterwards
Result: the RK3576 loader and idblock can be signed the same way Frank did for Amlogic: the signing tool only sees the public key, and a 32-byte digest goes out to KMS for signing.
rk_sign_tool (rkbin, v1.42) has a built-in extract/inject mode (ss --extract / ss --inject). A loader signed externally is byte-identical to one signed by the tool with a local key file, except for the signature fields and a 4-byte checksum. The tool's own verification (vl / vb) passes on it.Approaches considered
- PKCS#11 / HSM mode.
setting.inihasusing_hsmandhsm_*options, and the binary contains pkcs11 engine code. It was not used: the binary is statically linked, so loading an external engine (.so) is uncertain. - Extract / inject mode (used). No plugins needed. The tool writes three SHA-256 digests to files, and accepts signatures written back into the same files.
Flow
1. Extract rk_sign_tool lk --pubkey pub.pem (public key only)
rk_sign_tool ss --extract
rk_sign_tool sl --loader loader.bin -> out/si_usb_head.bin, si_flash_head.bin (32-byte SHA-256 each)
rk_sign_tool sb --idb idblock.img -> out/si_idb_head.bin
2. Sign each digest -> KMS asymmetricSign (RSA-PSS, SHA-256, salt 32)
verify each signature with the public key, then write it back over the same file
3. Inject rk_sign_tool ss --inject
rk_sign_tool sl --loader loader.bin
rk_sign_tool sb --idb idblock.img
4. Check rk_sign_tool vl / vb, then otp --hash (public-key hash for OTP)
Tests with a local key file
| # | Test | Result |
|---|---|---|
| 0 | Build an unsigned loader and idblock with rkbin boot_merger and RK3576MINIALL.ini | Done |
| A | Baseline: rk_sign_tool signs with a test key file; check with vl / vb | Pass |
| B | External: public key only, extract 3 digests, sign them outside the tool with openssl, inject; check with vl / vb | Pass |
| A vs B | Byte compare of both loaders and idblocks | Match Differences only in the signature fields (two in the loader, one in the idblock) and the loader's trailing 4-byte checksum |
| OTP | otp --hash on both loaders | Identical |
| PSS | Signatures produced by the tool itself, checked with plain openssl | Confirmed RSA-PSS, SHA-256, salt length 32. Signatures are stored byte-reversed; the tool handles that on inject |
| C | Negative: sign with a wrong key, inject | As expected Inject accepts it silently (rc 0); vl / vb fail ("failed to check signature") |
| 4096 | A, B and C rerun with a 4096-bit key | Pass Same results as 2048-bit |
Full flow against a simulated Cloud KMS
kms_sim.py: a local stand-in for the Cloud KMS REST API, bound to 127.0.0.1 and protected by a bearer token. It servesGET …/publicKeyandPOST …:asymmetricSign(32-byte sha256 digest in, signature out) and logs each call. The test key existed only inside the simulator.rk-sign-loader.sh: the signing flow. It never reads a private key and refuses one if passed. It fetches the public key from KMS, extracts the digests, calls asymmetricSign, verifies every signature before inject, then runsvl/vbandotp --hash.
| # | Test | Result |
|---|---|---|
| 1 | Normal run, 4096-bit key | Pass All 3 signatures verified before inject; vl / vb pass; OTP hash produced. Audit log: exactly 1 getPublicKey and 3 asymmetricSign calls |
| 2 | KMS signs with a different key (misconfigured key) | Fails as expected First signature fails the public-key check; nothing is injected |
| 3 | Wrong token (no permission) | Fails as expected KMS returns 401; the flow stops |
To switch to real Cloud KMS, only the endpoint (https://cloudkms.googleapis.com/v1/projects/…/cryptoKeyVersions/1) and the token (gcloud auth print-access-token) change.
Notes
- rk_sign_tool does not check signatures on inject (test C), the same issue Frank found on Amlogic. The flow therefore verifies each signature before inject and runs
vl/vbafter. - Not yet signed with real KMS. Google documents salt length = digest length (32) for
RSA_SIGN_PSS_2048_SHA256andRSA_SIGN_PSS_4096_SHA256, matching what the tool uses, but one real signature from a dev KMS key is still needed to prove it. - Not yet booted on a board. These tests prove the format at tool level only. Whether the RK3576 BootROM accepts the signed loader (and a 4096-bit key) needs a boot test on an unfused board.
- The flash-head signature in the loader has an extra transform besides byte reversal. The tool handles it, and
vlpasses.
Comparison with Amlogic
| Amlogic (Frank) | RK3576 (this test) | |
|---|---|---|
| External-sign interface | acpu-imagetool --cmd-signpipe-* (pipe, request/response) | rk_sign_tool ss --extract / --inject (files) |
| Sent to KMS | 32-byte digest | 32-byte digest |
| Algorithm | RSA-PSS-4096, SHA-256 | RSA-PSS, SHA-256, salt 32 (2048 and 4096 tested) |
| Tool accepts a wrong signature | Yes, hence pipe-verify | Yes, hence verify before and after inject |
Sources
devices/vendors/giada/rkbin-giada-bsp.tar.gz:rkbin/tools/rk_sign_toolv1.42 (sha256f779e3a2…),boot_mergerv1.35,RKBOOT/RK3576MINIALL.ini,tools/setting.ini- Logs, scripts and the simulator audit log are kept on the bench host.