From bbcde0010b7248903fe03251b67711ba0941b1b4 Mon Sep 17 00:00:00 2001 From: Abhijit Nathwani Date: Mon, 29 Jun 2026 17:26:13 -0700 Subject: [PATCH 1/6] Initial wiring for the preops selftest. Working AES-CBC encrypt/decrypt KAT --- fw/pal/traits/src/error.rs | 3 + fw/plat/uno/fw/drivers/aes/Cargo.toml | 1 + fw/plat/uno/fw/drivers/aes/src/api.rs | 9 +++ fw/plat/uno/fw/drivers/sha/Cargo.toml | 1 + fw/plat/uno/fw/drivers/sha/src/api.rs | 10 ++- fw/plat/uno/fw/pal/src/alloc.rs | 22 ++++-- fw/plat/uno/fw/pal/src/io.rs | 17 +++++ fw/plat/uno/fw/pal/src/lib.rs | 1 + fw/plat/uno/fw/pal/src/pal.rs | 24 ++++++ fw/plat/uno/fw/pal/src/self_test/aes_cbc.rs | 85 +++++++++++++++++++++ fw/plat/uno/fw/pal/src/self_test/mod.rs | 83 ++++++++++++++++++++ fw/plat/uno/fw/pal/src/self_test/vectors.rs | 58 ++++++++++++++ fw/plat/uno/fw/reg/soc/src/io_gsram.rs | 6 +- fw/plat/uno/rdl/soc/dtcm_map.rdl | 7 +- fw/plat/uno/rdl/soc/gsram_map.rdl | 9 ++- 15 files changed, 321 insertions(+), 15 deletions(-) create mode 100644 fw/plat/uno/fw/pal/src/self_test/aes_cbc.rs create mode 100644 fw/plat/uno/fw/pal/src/self_test/mod.rs create mode 100644 fw/plat/uno/fw/pal/src/self_test/vectors.rs diff --git a/fw/pal/traits/src/error.rs b/fw/pal/traits/src/error.rs index 8bb1eac8b..0775e69e9 100644 --- a/fw/pal/traits/src/error.rs +++ b/fw/pal/traits/src/error.rs @@ -329,6 +329,9 @@ pub enum HsmError { /// (`mfgr_seed` / `owner_seed`) when no provisioned BKS seed row /// carries the requested selector (SVN / owner id). SeedNotFound = 0x08700100, + /// A cryptographic algorithm self-test (CAST) produced output that + /// did not match its known-answer vector. + SelfTestKatMismatch = 0x08700101, } impl core::fmt::Debug for HsmError { diff --git a/fw/plat/uno/fw/drivers/aes/Cargo.toml b/fw/plat/uno/fw/drivers/aes/Cargo.toml index cd045a546..128055ce2 100644 --- a/fw/plat/uno/fw/drivers/aes/Cargo.toml +++ b/fw/plat/uno/fw/drivers/aes/Cargo.toml @@ -19,6 +19,7 @@ azihsm_fw_uno_error = { workspace = true } azihsm_fw_uno_pac = { workspace = true } azihsm_fw_uno_reg_soc = { workspace = true } bitfield-struct = { workspace = true } +cortex-m = { workspace = true } embassy-sync = { workspace = true } tock-registers = { workspace = true } zerocopy = { features = ["derive"], workspace = true } diff --git a/fw/plat/uno/fw/drivers/aes/src/api.rs b/fw/plat/uno/fw/drivers/aes/src/api.rs index c790506ab..d4e70d4f6 100644 --- a/fw/plat/uno/fw/drivers/aes/src/api.rs +++ b/fw/plat/uno/fw/drivers/aes/src/api.rs @@ -488,6 +488,15 @@ impl AesExclusive<'_, DEPTH> { fn submit_cmd(slot: usize) { let desc_addr = IO_GSRAM_BASE + AES_CMD_OFFSET + (slot as u32) * AES_CMD_STRIDE; + // Order the descriptor writes (Normal GSRAM memory, written by the caller) + // before the doorbell write (Device memory). On Cortex-M7 a Device write + // does NOT order prior Normal-memory writes, so without this barrier the + // engine can fetch a partially-written descriptor and reject it (CMD_ERROR) + // — observed as a cold-boot failure on the first AES decrypt, where the + // descriptor slot still holds uninitialized GSRAM. Matches the reference + // firmware's `dmb()` before the doorbell and the other uno drivers + // (iic/oic/gdma/ipc). + cortex_m::asm::dmb(); AES.command.set(desc_addr); } diff --git a/fw/plat/uno/fw/drivers/sha/Cargo.toml b/fw/plat/uno/fw/drivers/sha/Cargo.toml index 53c315fa2..15b2c1e9a 100644 --- a/fw/plat/uno/fw/drivers/sha/Cargo.toml +++ b/fw/plat/uno/fw/drivers/sha/Cargo.toml @@ -19,6 +19,7 @@ azihsm_fw_uno_error = { workspace = true } azihsm_fw_uno_pac = { workspace = true } azihsm_fw_uno_reg_soc = { workspace = true } bitfield-struct = { workspace = true } +cortex-m = { workspace = true } embassy-sync = { workspace = true } tock-registers = { workspace = true } zerocopy = { features = ["derive"], workspace = true } diff --git a/fw/plat/uno/fw/drivers/sha/src/api.rs b/fw/plat/uno/fw/drivers/sha/src/api.rs index 5ee82f836..1f8b1bec0 100644 --- a/fw/plat/uno/fw/drivers/sha/src/api.rs +++ b/fw/plat/uno/fw/drivers/sha/src/api.rs @@ -587,6 +587,12 @@ impl ShaExclusive<'_, DEPTH> { fn submit_cmd(slot: usize) { let desc_addr = IO_GSRAM_BASE + SHA_CMD_OFFSET + (slot as u32) * SHA_CMD_STRIDE; + // Order the descriptor writes (Normal memory, written by `write_cmd`) before + // the doorbell write (Device memory). On Cortex-M7 a Device write does NOT + // order prior Normal-memory writes, so without this barrier the engine can + // fetch a partially-written descriptor and reject it (CMD_ERROR). Matches the + // reference firmware and the other uno drivers (iic/oic/gdma/ipc). + cortex_m::asm::dmb(); SHA.command.set(desc_addr); } @@ -611,8 +617,8 @@ fn write_cmd(idx: usize, req: &ShaRequest<'_>, mode: u8) { 0 }; let ref_digest_addr = req.ref_digest.map_or(0, |d| d.as_ptr() as u32); - // Plain pointer writes — SHA_CMD is in DTCM, not MMIO. - // The COMMAND register write (submit_cmd) is the barrier. + // Plain pointer writes — SHA_CMD is in DTCM, not MMIO. A `dmb()` in + // `submit_cmd` orders these writes before the doorbell. let entry_ptr = (&SHA_Q.sha_cmd[idx]) as *const ShaCmdEntry as *mut u32; unsafe { entry_ptr.add(0).write(cmd.into()); diff --git a/fw/plat/uno/fw/pal/src/alloc.rs b/fw/plat/uno/fw/pal/src/alloc.rs index 2f43d200e..da7280961 100644 --- a/fw/plat/uno/fw/pal/src/alloc.rs +++ b/fw/plat/uno/fw/pal/src/alloc.rs @@ -33,15 +33,23 @@ const HEAPS: usize = 2; pub(crate) const IO_SLOTS: usize = SRAM_IO_BUF_COUNT as usize; -/// Dedicated admin/internal IO slot — the last of [`IO_SLOTS`]. +/// Dedicated admin/internal IO slot. /// /// IIC only hands out the host slots `0..ADMIN_IO_INDEX` (the first -/// [`IO_SLOTS`]` - 1` slots); the PAL reserves this final slot for -/// internal provisioning crypto (partition identity and enable-time -/// keygen), so its bump heaps never collide with a concurrent host IO. -/// It has full DMA (`SRAM_IO_BUF`) and NonDma (`DTCM_IO_BUF`) backing, -/// like any host slot. -pub(crate) const ADMIN_IO_INDEX: u16 = (IO_SLOTS - 1) as u16; +/// 32 slots); the PAL reserves this slot for internal provisioning +/// crypto (partition identity and enable-time keygen), so its bump +/// heaps never collide with a concurrent host IO. It has full DMA +/// (`SRAM_IO_BUF`) and NonDma (`DTCM_IO_BUF`) backing, like any host +/// slot. +pub(crate) const ADMIN_IO_INDEX: u16 = (IO_SLOTS - 2) as u16; + +/// Dedicated self-test (CAST) IO slot — the last of [`IO_SLOTS`]. +/// +/// Reserved for the pre-operational and periodic cryptographic +/// algorithm self-tests, separate from [`ADMIN_IO_INDEX`] so the +/// periodic self-test never races partition provisioning over a +/// shared bump heap. Same DMA/NonDma backing as any host slot. +pub(crate) const SELF_TEST_IO_INDEX: u16 = (IO_SLOTS - 1) as u16; // DTCM IO buffer region — per-IO NonDma scratch in upper DTCM. // See dtcm_map.rdl: DTCM_IO_BUF[33] @ offset 0x2EC00 from DTCM base. diff --git a/fw/plat/uno/fw/pal/src/io.rs b/fw/plat/uno/fw/pal/src/io.rs index eb4309989..3e2cb653e 100644 --- a/fw/plat/uno/fw/pal/src/io.rs +++ b/fw/plat/uno/fw/pal/src/io.rs @@ -45,6 +45,7 @@ use tock_registers::interfaces::Writeable; use crate::UnoHsmPal; use crate::alloc::ADMIN_IO_INDEX; +use crate::alloc::SELF_TEST_IO_INDEX; use crate::alloc::reset_io_alloc; /// Typed overlay of the IO GSRAM region. @@ -87,6 +88,22 @@ impl UnoHsmIo { io } + /// Constructs an IO handle over the dedicated self-test slot + /// ([`SELF_TEST_IO_INDEX`]). + /// + /// Used by the cryptographic algorithm self-tests (CAST), which run + /// without a host IO. KAT operands are allocated from this slot's + /// `SRAM_IO_BUF` via the bump allocator. No partition context applies, + /// so `IO_META` is left untouched ([`pid`](HsmIo::pid) is unused by the + /// self-test path). + /// + /// [`SELF_TEST_IO_INDEX`]: crate::alloc::SELF_TEST_IO_INDEX + pub(crate) fn self_test() -> Self { + Self { + index: SELF_TEST_IO_INDEX, + } + } + /// Returns a reference to the IO_META entry for this slot. #[inline] fn io_meta(&self) -> &IoMetaEntry { diff --git a/fw/plat/uno/fw/pal/src/lib.rs b/fw/plat/uno/fw/pal/src/lib.rs index 310aa6f2c..d8663cea1 100644 --- a/fw/plat/uno/fw/pal/src/lib.rs +++ b/fw/plat/uno/fw/pal/src/lib.rs @@ -45,6 +45,7 @@ mod lock; mod pal; mod part; mod seed; +mod self_test; mod session; mod vault; diff --git a/fw/plat/uno/fw/pal/src/pal.rs b/fw/plat/uno/fw/pal/src/pal.rs index e98516e9c..995b5e6f2 100644 --- a/fw/plat/uno/fw/pal/src/pal.rs +++ b/fw/plat/uno/fw/pal/src/pal.rs @@ -523,6 +523,18 @@ impl UnoHsmPal { WAKE_TABLE[irq as usize](self, irq); } } + + // AES/SHA completion is detected by POLLING the engine STATUS, not by the + // `AES_DONE`/`SHA_DONE` edge interrupt. On this silicon the done IRQ does + // not reliably re-arm for back-to-back commands (the first op completes + // and raises the IRQ; the second completes in hardware but no second IRQ + // is delivered), which silently hangs any sequential AES/SHA sequence + // (pre-op self-tests, partition-provisioning HMAC-SHA384 KDF + AES-CBC + // key masking, …). The reference firmware polls BUSY for exactly this + // reason. `wake()` is a guarded no-op when no flag bit is set, so calling + // it every poll is cheap (one MMIO read) and idempotent with the IRQ path. + self.aes.wake(); + self.sha.wake(); } /// Handle an incoming IPC message. @@ -555,6 +567,18 @@ impl UnoHsmPal { if !self.try_ack_state_change(buf, IoProcessorState::Start, "WaitStart") { return; } + // Pre-operational self-test gate (FIPS): the in-scope algorithm + // KATs must pass before the HSM exposes any crypto. Crypto + // engines are construct-ready (AES/SHA/PKA need no boot-time + // init), so this runs before `on_boot_complete`. On failure we + // withhold the transition — the PAL never reaches `Running`, + // never publishes `BootStatus::Run`. + let io = crate::io::UnoHsmIo::self_test(); + if let Err(_e) = crate::self_test::run_pre_op(self, &io).await { + error!("selftest", _e, "pre-operational self-test FAILED"); + return; + } + info!("selftest", "pre-operational self-test PASSED"); self.on_boot_complete() } BootPhase::Running => { diff --git a/fw/plat/uno/fw/pal/src/self_test/aes_cbc.rs b/fw/plat/uno/fw/pal/src/self_test/aes_cbc.rs new file mode 100644 index 000000000..de933fa25 --- /dev/null +++ b/fw/plat/uno/fw/pal/src/self_test/aes_cbc.rs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! AES-256-CBC cryptographic algorithm self-test (CAST). +//! +//! Runs a fixed known-answer vector through the HSM AES engine in **both** +//! directions — encrypt the plaintext and compare against the expected +//! ciphertext, then decrypt that ciphertext and compare back to the plaintext — +//! matching the reference firmware's `aes_cbc_self_test`. Operands are staged +//! into the self-test IO slot's DMA buffer via the bump allocator (see +//! [`crate::self_test`]). + +use azihsm_fw_hsm_pal_traits::AesOp; +use azihsm_fw_hsm_pal_traits::HsmAes; +use azihsm_fw_hsm_pal_traits::HsmAlloc; +use azihsm_fw_hsm_pal_traits::HsmError; +use azihsm_fw_hsm_pal_traits::HsmResult; +use azihsm_fw_hsm_pal_traits::HsmScopedAlloc; +use azihsm_fw_uno_trace::tracing::error; +use azihsm_fw_uno_trace::tracing::info; + +use super::vectors::AES_CBC_256_KAT; +use crate::UnoHsmIo; +use crate::UnoHsmPal; + +/// Runs the AES-256-CBC known-answer test against the HSM AES engine. +/// +/// Encrypts the KAT plaintext and verifies the ciphertext, then decrypts the +/// ciphertext and verifies the plaintext (round trip), matching the reference +/// firmware. Returns [`HsmError::SelfTestKatMismatch`] on any mismatch (or any +/// error surfaced by the AES engine / allocator). +pub(super) async fn run_aes_cbc(pal: &UnoHsmPal, io: &UnoHsmIo) -> HsmResult<()> { + let v = &AES_CBC_256_KAT; + + pal.alloc_scoped_async(io, async |scope| { + // Stage the shared key/IV into DMA-visible memory (the self-test slot). + // The IV buffer is not mutated by the CBC path (it copies into internal + // scratch), so the same `iv` is reused for both directions. + let key = scope.dma_alloc(v.key.len())?; + key.copy_from_slice(v.key); + let iv = scope.dma_alloc(v.iv.len())?; + iv.copy_from_slice(v.iv); + + // ── Encrypt: plaintext → ciphertext, verify against the vector ── + let pt = scope.dma_alloc(v.plaintext.len())?; + pt.copy_from_slice(v.plaintext); + let ct_out = scope.dma_alloc_zeroed(v.ciphertext.len())?; + info!("selftest", "AES-CBC encrypt 64B: submit"); + pal.aes_cbc_enc_dec(io, AesOp::Encrypt, &*key, &*pt, &*iv, &mut *ct_out, None) + .await?; + info!("selftest", "AES-CBC encrypt: engine ok"); + // KAT vectors are public, fixed test data — a plain slice comparison is + // correct; no constant-time compare is needed. + if &ct_out[..] != v.ciphertext { + error!( + "selftest", + HsmError::SelfTestKatMismatch, + "AES-CBC encrypt KAT mismatch" + ); + return Err(HsmError::SelfTestKatMismatch); + } + info!("selftest", "AES-CBC encrypt: verified"); + + // ── Decrypt: ciphertext → plaintext, verify against the vector ── + let ct = scope.dma_alloc(v.ciphertext.len())?; + ct.copy_from_slice(v.ciphertext); + let pt_out = scope.dma_alloc_zeroed(v.plaintext.len())?; + info!("selftest", "AES-CBC decrypt 64B: submit"); + pal.aes_cbc_enc_dec(io, AesOp::Decrypt, &*key, &*ct, &*iv, &mut *pt_out, None) + .await?; + info!("selftest", "AES-CBC decrypt: engine ok"); + if &pt_out[..] != v.plaintext { + error!( + "selftest", + HsmError::SelfTestKatMismatch, + "AES-CBC decrypt KAT mismatch" + ); + return Err(HsmError::SelfTestKatMismatch); + } + info!("selftest", "AES-CBC decrypt: verified"); + + Ok::<(), HsmError>(()) + }) + .await +} diff --git a/fw/plat/uno/fw/pal/src/self_test/mod.rs b/fw/plat/uno/fw/pal/src/self_test/mod.rs new file mode 100644 index 000000000..fcd7f9ca5 --- /dev/null +++ b/fw/plat/uno/fw/pal/src/self_test/mod.rs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Cryptographic Algorithm Self-Tests (CAST) for the Uno HSM core. +//! +//! Implements the FIPS pre-operational and (future) periodic self-tests for +//! the HSM-core cryptographic algorithms. Each test runs a fixed known-answer +//! vector through the PAL crypto path and compares the result against the +//! expected output. +//! +//! # Memory +//! +//! All tests run on a dedicated, reserved IO slot +//! ([`crate::alloc::SELF_TEST_IO_INDEX`]) obtained via +//! [`UnoHsmPal::self_test_io`]. KAT operands are bump-allocated from that +//! slot's `SRAM_IO_BUF`, so the self-tests never contend with host IO or +//! partition-provisioning crypto (which uses the separate admin slot). +//! +//! # Status +//! +//! AES-256-CBC is implemented as the initial test. Further tests (HKDF, KBKDF, +//! per-engine ECDSA / ECDH / RSA, and the RNG/DRBG FW-mode KAT) extend the +//! [`SelfTest`] enum and the [`run_one`] dispatch. + +mod aes_cbc; +mod vectors; + +use azihsm_fw_hsm_pal_traits::HsmResult; + +use crate::UnoHsmIo; +use crate::UnoHsmPal; + +/// Identifies a single cryptographic algorithm self-test. +// Variants beyond `AesCbc` (and their constructors) land with the boot gate +// and per-engine tests in a later milestone. +#[allow(dead_code)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[non_exhaustive] +pub enum SelfTest { + /// AES-256-CBC known-answer test (HSM AES engine). + AesCbc, +} + +/// One scheduled self-test unit: a [`SelfTest`] optionally pinned to a specific +/// hardware engine instance. +/// +/// `engine` is `None` for tests that are not engine-scoped (such as +/// [`SelfTest::AesCbc`]); per-PKA-engine tests (added later) carry +/// `Some(index)` to validate each engine individually. +// +// `engine` is read once per-engine tests are wired (later milestone). +#[allow(dead_code)] +#[derive(Clone, Copy)] +pub struct SelfTestItem { + /// Which self-test to run. + pub test: SelfTest, + /// Target PKA engine instance, if the test is engine-scoped. + pub engine: Option, +} + +/// Runs a single self-test item to completion. +/// +/// The caller decides which items to run; `run_one` itself is +/// context-agnostic. +// +// Wired into the boot gate and the periodic self-test task in a later +// milestone; retained here as the dispatch entry point. +#[allow(dead_code)] +pub(crate) async fn run_one(pal: &UnoHsmPal, io: &UnoHsmIo, item: SelfTestItem) -> HsmResult<()> { + match item.test { + SelfTest::AesCbc => aes_cbc::run_aes_cbc(pal, io).await, + } +} + +/// Runs the full pre-operational self-test suite. +/// +/// Returns `Err` on the first failing test, leaving it to the caller (the boot +/// gate) to withhold the transition to the running state on failure. Mirrors +/// the reference firmware's `preops_cast` — positive known-answer tests only. +pub(crate) async fn run_pre_op(pal: &UnoHsmPal, io: &UnoHsmIo) -> HsmResult<()> { + aes_cbc::run_aes_cbc(pal, io).await?; + Ok(()) +} diff --git a/fw/plat/uno/fw/pal/src/self_test/vectors.rs b/fw/plat/uno/fw/pal/src/self_test/vectors.rs new file mode 100644 index 000000000..4c74681db --- /dev/null +++ b/fw/plat/uno/fw/pal/src/self_test/vectors.rs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Known-answer test (KAT) vectors for the cryptographic algorithm +//! self-tests. +//! +//! Vector fields are `&'static [u8]` slices rather than owned arrays because +//! byte lengths vary across algorithms (and would otherwise force a distinct +//! struct per size). The `&[..]` array literals are promoted to `.rodata` in +//! const context (rvalue static promotion), so the data lives in flash with +//! no allocation and no runtime cost. + +/// AES-CBC known-answer vector. +/// +/// Holds the full plaintext/ciphertext pair so the self-test can exercise both +/// directions (encrypt then decrypt) against the same vector, matching the +/// reference firmware's `aes_cbc_self_test`. +pub(super) struct AesCbcKat { + /// Cipher key. + pub key: &'static [u8], + /// Initialization vector (one AES block). + pub iv: &'static [u8], + /// Plaintext (block multiple). + pub plaintext: &'static [u8], + /// Expected ciphertext for `plaintext` under (`key`, `iv`). + pub ciphertext: &'static [u8], +} + +/// AES-256-CBC known-answer test vector. +/// +/// Source: NIST SP 800-38A, F.2.5 (CBC-AES256.Encrypt) / F.2.6 +/// (CBC-AES256.Decrypt) — all four blocks. Byte-for-byte identical to the +/// reference firmware's `AES_SELF_TEST_{KEY,IV,PLAIN_TEXT,CIPHER_TEXT}`. +pub(super) const AES_CBC_256_KAT: AesCbcKat = AesCbcKat { + key: &[ + 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, + 0x81, 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, + 0xdf, 0xf4, + ], + iv: &[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, + ], + plaintext: &[ + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, + 0x2a, 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, + 0x8e, 0x51, 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, + 0x0a, 0x52, 0xef, 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, + 0xe6, 0x6c, 0x37, 0x10, + ], + ciphertext: &[ + 0xf5, 0x8c, 0x4c, 0x04, 0xd6, 0xe5, 0xf1, 0xba, 0x77, 0x9e, 0xab, 0xfb, 0x5f, 0x7b, 0xfb, + 0xd6, 0x9c, 0xfc, 0x4e, 0x96, 0x7e, 0xdb, 0x80, 0x8d, 0x67, 0x9f, 0x77, 0x7b, 0xc6, 0x70, + 0x2c, 0x7d, 0x39, 0xf2, 0x33, 0x69, 0xa9, 0xd9, 0xba, 0xcf, 0xa5, 0x30, 0xe2, 0x63, 0x04, + 0x23, 0x14, 0x61, 0xb2, 0xeb, 0x05, 0xe2, 0xc3, 0x9b, 0xe9, 0xfc, 0xda, 0x6c, 0x19, 0x07, + 0x8c, 0x6a, 0x9d, 0x1b, + ], +}; diff --git a/fw/plat/uno/fw/reg/soc/src/io_gsram.rs b/fw/plat/uno/fw/reg/soc/src/io_gsram.rs index 449350973..4ab24408d 100644 --- a/fw/plat/uno/fw/reg/soc/src/io_gsram.rs +++ b/fw/plat/uno/fw/reg/soc/src/io_gsram.rs @@ -62,7 +62,7 @@ pub const BKS_TABLE_COUNT: u32 = 12; pub const BKS_TABLE_STRIDE: u32 = 0x29; pub const BKS_TABLE_SIZE: u32 = 0x29; pub const SRAM_IO_BUF_OFFSET: u32 = 0x20D60; -pub const SRAM_IO_BUF_COUNT: u32 = 33; +pub const SRAM_IO_BUF_COUNT: u32 = 34; pub const SRAM_IO_BUF_STRIDE: u32 = 0x4800; pub const SRAM_IO_BUF_SIZE: u32 = 0x4800; @@ -342,8 +342,8 @@ pub mod regs { (0x154a4 => pub sha_cmd: [super::ShaCmdEntry; 32]), (0x158a4 => _reserved6), (0x20c58 => pub io_meta: [super::IoMetaEntry; 33]), - (0x20d60 => pub sram_io_buf: [u8; 608256]), - (0xb5560 => @END), + (0x20d60 => pub sram_io_buf: [u8; 626688]), + (0xb9d60 => @END), } } } diff --git a/fw/plat/uno/rdl/soc/dtcm_map.rdl b/fw/plat/uno/rdl/soc/dtcm_map.rdl index 0c9a2eae7..d024b1101 100644 --- a/fw/plat/uno/rdl/soc/dtcm_map.rdl +++ b/fw/plat/uno/rdl/soc/dtcm_map.rdl @@ -58,7 +58,12 @@ addrmap hsm_dtcm { default hw = na; default sw = rw; - // Per-IO NonDma scratch (33 × 2 KB = 66 KB; slots 0..31 host, 32 admin) + // Per-IO NonDma scratch (33 × 2 KB = 66 KB; slots 0..31 host, 32 admin). + // Stays 33 (not 34): the self-test slot (SRAM index 33) uses DMA buffers + // only — all crypto drivers allocate from the SRAM (Dma) heap and never the + // DTCM (NonDma) heap — so it needs no DTCM scratch. DTCM is also full here: + // DTCM_IO_BUF[33] ends exactly at CRASHDUMP_BASE (0x3F400), leaving no room + // to grow. dtcm_io_buf_t DTCM_IO_BUF[33] @ 0x2EC00 += 0x800; // Crashdump + heartbeat diff --git a/fw/plat/uno/rdl/soc/gsram_map.rdl b/fw/plat/uno/rdl/soc/gsram_map.rdl index 63075a1fb..975154fe7 100644 --- a/fw/plat/uno/rdl/soc/gsram_map.rdl +++ b/fw/plat/uno/rdl/soc/gsram_map.rdl @@ -541,9 +541,14 @@ addrmap io_gsram { upka_cmd_entry_t UPKA_ENGINE_CMD[16] @ 0x15364 += 0x14; sha_cmd_entry_t SHA_CMD[32] @ 0x154A4 += 0x20; - // ─── Heap (per-IO buffers) — 33 slots: 0..31 host (IIC), 32 admin ─── + // ─── Heap (per-IO buffers) — 34 SRAM slots: 0..31 host (IIC), 32 admin, + // 33 self-test (CAST). IO_META stays 33: only host+admin slots carry + // per-IO metadata (controller/queue id); the self-test slot never uses + // io_meta() (it does not ride the host IO path), so it needs no entry. + // Keeping IO_META at 33 also preserves SRAM_IO_BUF's 32-byte alignment + // at 0x20D60 (growing IO_META would shift SRAM_IO_BUF off alignment). ── io_meta_entry_t IO_META[33] @ 0x20C58 += 0x08; - sram_io_buf_t SRAM_IO_BUF[33] @ 0x20D60 += 0x4800; + sram_io_buf_t SRAM_IO_BUF[34] @ 0x20D60 += 0x4800; // ─── Partition persistent store (65 entries, 3072 B each) ─── part_store_t PART_STORE[65] @ 0xBB000 += 0xC00; From 86cc51441ffe5ca6cb98d42c5d196b735d8fcdd0 Mon Sep 17 00:00:00 2001 From: Abhijit Nathwani Date: Thu, 2 Jul 2026 10:05:38 -0700 Subject: [PATCH 2/6] Add HKDF KAT for preops gate --- fw/plat/uno/fw/pal/src/self_test/mod.rs | 2 + fw/plat/uno/fw/pal/src/self_test/vectors.rs | 52 +++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/fw/plat/uno/fw/pal/src/self_test/mod.rs b/fw/plat/uno/fw/pal/src/self_test/mod.rs index fcd7f9ca5..02b12fae2 100644 --- a/fw/plat/uno/fw/pal/src/self_test/mod.rs +++ b/fw/plat/uno/fw/pal/src/self_test/mod.rs @@ -23,6 +23,7 @@ //! [`SelfTest`] enum and the [`run_one`] dispatch. mod aes_cbc; +mod kdf; mod vectors; use azihsm_fw_hsm_pal_traits::HsmResult; @@ -79,5 +80,6 @@ pub(crate) async fn run_one(pal: &UnoHsmPal, io: &UnoHsmIo, item: SelfTestItem) /// the reference firmware's `preops_cast` — positive known-answer tests only. pub(crate) async fn run_pre_op(pal: &UnoHsmPal, io: &UnoHsmIo) -> HsmResult<()> { aes_cbc::run_aes_cbc(pal, io).await?; + kdf::run_hkdf(pal, io).await?; Ok(()) } diff --git a/fw/plat/uno/fw/pal/src/self_test/vectors.rs b/fw/plat/uno/fw/pal/src/self_test/vectors.rs index 4c74681db..9685ac6ab 100644 --- a/fw/plat/uno/fw/pal/src/self_test/vectors.rs +++ b/fw/plat/uno/fw/pal/src/self_test/vectors.rs @@ -18,10 +18,13 @@ pub(super) struct AesCbcKat { /// Cipher key. pub key: &'static [u8], + /// Initialization vector (one AES block). pub iv: &'static [u8], + /// Plaintext (block multiple). pub plaintext: &'static [u8], + /// Expected ciphertext for `plaintext` under (`key`, `iv`). pub ciphertext: &'static [u8], } @@ -56,3 +59,52 @@ pub(super) const AES_CBC_256_KAT: AesCbcKat = AesCbcKat { 0x8c, 0x6a, 0x9d, 0x1b, ], }; + +/// HKDF (RFC 5869, HMAC-SHA-256) known-answer vector. +/// +/// Exercises HKDF-Extract (`salt` + `ikm` → PRK) followed by HKDF-Expand +/// (PRK + `info` → OKM), comparing the derived `okm` against the expected +/// vector. Byte-for-byte identical to the reference firmware's +/// `HKDF_SELF_TEST_256_{SALT,IKM,INFO,DKM}`. +pub(super) struct HkdfKat { + /// Salt (HKDF-Extract). + pub salt: &'static [u8], + + /// Input keying material (HKDF-Extract). + pub ikm: &'static [u8], + + /// Context/application info (HKDF-Expand): PartyU ‖ PartyV ‖ length. + pub info: &'static [u8], + + /// Expected derived keying material (output of HKDF-Expand). + pub okm: &'static [u8], +} + +/// HKDF-SHA-256 known-answer test vector (32-byte OKM). +pub(super) const HKDF_SHA256_KAT: HkdfKat = HkdfKat { + salt: &[ + 0xb1, 0xd6, 0x09, 0xd7, 0x5e, 0x4d, 0x0b, 0xe8, 0x1d, 0xca, 0xc2, 0xa8, 0x9a, 0xbc, 0xf0, + 0xa8, 0xac, 0xd7, 0x7d, 0xb8, 0xb7, 0x64, 0x17, 0x87, 0x21, 0xe6, 0xbe, 0x68, 0xcc, 0xf5, + 0x42, 0x51, 0x38, 0xa0, 0x56, 0xa2, 0xae, 0x37, 0x1a, 0x1e, 0x72, 0x7c, 0x15, 0xb5, 0x02, + 0x6d, 0xcf, 0x8e, 0xe7, 0x6d, 0x42, 0x08, 0x0e, 0x9c, 0x98, 0x08, 0x78, 0xb7, 0x0e, 0x78, + 0x6c, 0x7d, 0x64, 0x32, + ], + ikm: &[ + 0x7e, 0x4a, 0x88, 0x71, 0x5d, 0xe4, 0xd8, 0x78, 0x8f, 0x8f, 0x06, 0x9a, 0xb1, 0x05, 0x8a, + 0x42, 0xa1, 0xba, 0xc4, 0xb1, 0x9e, 0x7a, 0x4c, 0xe8, 0xcf, 0x7e, 0xa0, 0x4c, 0xc6, 0xbd, + 0x64, 0xf7, + ], + info: &[ + // PartyU ID (16 bytes) + 0x23, 0x6b, 0x2b, 0x47, 0x5c, 0xb1, 0xed, 0x72, 0x08, 0x5f, 0x11, 0xc0, 0x53, 0xbf, 0xb2, + 0x40, // PartyV ID (16 bytes) + 0x01, 0x5f, 0x5d, 0x21, 0xbb, 0x74, 0x4d, 0x2d, 0xd4, 0xf0, 0x17, 0x24, 0x03, 0x4d, 0x1b, + 0xab, // Length (256 bits) (4 bytes) + 0x00, 0x00, 0x01, 0x00, + ], + okm: &[ + 0x05, 0x69, 0xc2, 0x57, 0x2a, 0x11, 0x3d, 0x89, 0x70, 0x57, 0xdd, 0xac, 0xcc, 0xf1, 0x19, + 0xd9, 0x83, 0x7d, 0x7a, 0x5a, 0xcd, 0x14, 0xaa, 0xcb, 0xf7, 0xbb, 0xf7, 0x5d, 0x42, 0xbd, + 0x5c, 0x1d, + ], +}; From 562e8da5354e4ce6fdf62a943ca6fdd3ea90ed9f Mon Sep 17 00:00:00 2001 From: Abhijit Nathwani Date: Thu, 2 Jul 2026 12:04:36 -0700 Subject: [PATCH 3/6] Add KBKDF self test and vectors * Clean up unused APIs --- fw/plat/uno/fw/pal/src/self_test/aes_cbc.rs | 7 --- fw/plat/uno/fw/pal/src/self_test/mod.rs | 50 ++------------------ fw/plat/uno/fw/pal/src/self_test/vectors.rs | 52 +++++++++++++++++++++ 3 files changed, 57 insertions(+), 52 deletions(-) diff --git a/fw/plat/uno/fw/pal/src/self_test/aes_cbc.rs b/fw/plat/uno/fw/pal/src/self_test/aes_cbc.rs index de933fa25..3dca97831 100644 --- a/fw/plat/uno/fw/pal/src/self_test/aes_cbc.rs +++ b/fw/plat/uno/fw/pal/src/self_test/aes_cbc.rs @@ -17,7 +17,6 @@ use azihsm_fw_hsm_pal_traits::HsmError; use azihsm_fw_hsm_pal_traits::HsmResult; use azihsm_fw_hsm_pal_traits::HsmScopedAlloc; use azihsm_fw_uno_trace::tracing::error; -use azihsm_fw_uno_trace::tracing::info; use super::vectors::AES_CBC_256_KAT; use crate::UnoHsmIo; @@ -45,10 +44,8 @@ pub(super) async fn run_aes_cbc(pal: &UnoHsmPal, io: &UnoHsmIo) -> HsmResult<()> let pt = scope.dma_alloc(v.plaintext.len())?; pt.copy_from_slice(v.plaintext); let ct_out = scope.dma_alloc_zeroed(v.ciphertext.len())?; - info!("selftest", "AES-CBC encrypt 64B: submit"); pal.aes_cbc_enc_dec(io, AesOp::Encrypt, &*key, &*pt, &*iv, &mut *ct_out, None) .await?; - info!("selftest", "AES-CBC encrypt: engine ok"); // KAT vectors are public, fixed test data — a plain slice comparison is // correct; no constant-time compare is needed. if &ct_out[..] != v.ciphertext { @@ -59,16 +56,13 @@ pub(super) async fn run_aes_cbc(pal: &UnoHsmPal, io: &UnoHsmIo) -> HsmResult<()> ); return Err(HsmError::SelfTestKatMismatch); } - info!("selftest", "AES-CBC encrypt: verified"); // ── Decrypt: ciphertext → plaintext, verify against the vector ── let ct = scope.dma_alloc(v.ciphertext.len())?; ct.copy_from_slice(v.ciphertext); let pt_out = scope.dma_alloc_zeroed(v.plaintext.len())?; - info!("selftest", "AES-CBC decrypt 64B: submit"); pal.aes_cbc_enc_dec(io, AesOp::Decrypt, &*key, &*ct, &*iv, &mut *pt_out, None) .await?; - info!("selftest", "AES-CBC decrypt: engine ok"); if &pt_out[..] != v.plaintext { error!( "selftest", @@ -77,7 +71,6 @@ pub(super) async fn run_aes_cbc(pal: &UnoHsmPal, io: &UnoHsmIo) -> HsmResult<()> ); return Err(HsmError::SelfTestKatMismatch); } - info!("selftest", "AES-CBC decrypt: verified"); Ok::<(), HsmError>(()) }) diff --git a/fw/plat/uno/fw/pal/src/self_test/mod.rs b/fw/plat/uno/fw/pal/src/self_test/mod.rs index 02b12fae2..2b0e96272 100644 --- a/fw/plat/uno/fw/pal/src/self_test/mod.rs +++ b/fw/plat/uno/fw/pal/src/self_test/mod.rs @@ -18,9 +18,10 @@ //! //! # Status //! -//! AES-256-CBC is implemented as the initial test. Further tests (HKDF, KBKDF, -//! per-engine ECDSA / ECDH / RSA, and the RNG/DRBG FW-mode KAT) extend the -//! [`SelfTest`] enum and the [`run_one`] dispatch. +//! AES-256-CBC, HKDF, and KBKDF are implemented. Further tests (per-engine +//! ECDSA / ECDH / RSA, and the RNG/DRBG FW-mode KAT) are appended to +//! [`run_pre_op`] as they are added — each is a direct call, with per-PKA-engine +//! tests wrapped in a `for engine in 0..PKA_ENGINES` loop. mod aes_cbc; mod kdf; @@ -31,48 +32,6 @@ use azihsm_fw_hsm_pal_traits::HsmResult; use crate::UnoHsmIo; use crate::UnoHsmPal; -/// Identifies a single cryptographic algorithm self-test. -// Variants beyond `AesCbc` (and their constructors) land with the boot gate -// and per-engine tests in a later milestone. -#[allow(dead_code)] -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -#[non_exhaustive] -pub enum SelfTest { - /// AES-256-CBC known-answer test (HSM AES engine). - AesCbc, -} - -/// One scheduled self-test unit: a [`SelfTest`] optionally pinned to a specific -/// hardware engine instance. -/// -/// `engine` is `None` for tests that are not engine-scoped (such as -/// [`SelfTest::AesCbc`]); per-PKA-engine tests (added later) carry -/// `Some(index)` to validate each engine individually. -// -// `engine` is read once per-engine tests are wired (later milestone). -#[allow(dead_code)] -#[derive(Clone, Copy)] -pub struct SelfTestItem { - /// Which self-test to run. - pub test: SelfTest, - /// Target PKA engine instance, if the test is engine-scoped. - pub engine: Option, -} - -/// Runs a single self-test item to completion. -/// -/// The caller decides which items to run; `run_one` itself is -/// context-agnostic. -// -// Wired into the boot gate and the periodic self-test task in a later -// milestone; retained here as the dispatch entry point. -#[allow(dead_code)] -pub(crate) async fn run_one(pal: &UnoHsmPal, io: &UnoHsmIo, item: SelfTestItem) -> HsmResult<()> { - match item.test { - SelfTest::AesCbc => aes_cbc::run_aes_cbc(pal, io).await, - } -} - /// Runs the full pre-operational self-test suite. /// /// Returns `Err` on the first failing test, leaving it to the caller (the boot @@ -81,5 +40,6 @@ pub(crate) async fn run_one(pal: &UnoHsmPal, io: &UnoHsmIo, item: SelfTestItem) pub(crate) async fn run_pre_op(pal: &UnoHsmPal, io: &UnoHsmIo) -> HsmResult<()> { aes_cbc::run_aes_cbc(pal, io).await?; kdf::run_hkdf(pal, io).await?; + kdf::run_kbkdf(pal, io).await?; Ok(()) } diff --git a/fw/plat/uno/fw/pal/src/self_test/vectors.rs b/fw/plat/uno/fw/pal/src/self_test/vectors.rs index 9685ac6ab..c488246c8 100644 --- a/fw/plat/uno/fw/pal/src/self_test/vectors.rs +++ b/fw/plat/uno/fw/pal/src/self_test/vectors.rs @@ -108,3 +108,55 @@ pub(super) const HKDF_SHA256_KAT: HkdfKat = HkdfKat { 0x5c, 0x1d, ], }; + +/// KBKDF (SP 800-108 counter mode, HMAC-SHA-512) known-answer vector. +/// +/// Exercises the production [`sp800_108_kdf`](azihsm_fw_hsm_pal_traits::HsmKdf::sp800_108_kdf) +/// path, which builds each PRF input as `counter ‖ label ‖ 0x00 ‖ context ‖ L` +/// and derives `output = HMAC-SHA-512(key, …)`. +/// +/// The reference firmware's KBKDF self-test (and the raw NIST CAVP counter-mode +/// vectors) use an **opaque `FixedInputData`** blob, which uno's structured KDF +/// cannot reproduce (it always inserts the `0x00` separator and appends its own +/// `L`). So this `okm` was computed for uno's exact construction with a +/// KDF-CTR implementation **validated to reproduce the NIST CAVP HMAC-SHA-512 +/// answers byte-for-byte**; `key` is a real NIST CAVP HMAC-SHA-512 key. +pub(super) struct KbkdfKat { + /// Key-derivation key (KDK), 64 bytes (NIST CAVP HMAC-SHA-512 key). + pub key: &'static [u8], + + /// Label (application-specific purpose string). + pub label: &'static [u8], + + /// Context (application-specific context bytes). + pub context: &'static [u8], + + /// Expected derived output (40 bytes; L = 320 bits, single SHA-512 block). + pub okm: &'static [u8], +} + +/// KBKDF-SHA-512 known-answer test vector (40-byte output). +pub(super) const KBKDF_SHA512_KAT: KbkdfKat = KbkdfKat { + key: &[ + 0xdd, 0x5d, 0xbd, 0x45, 0x59, 0x3e, 0xe2, 0xac, 0x13, 0x97, 0x48, 0xe7, 0x64, 0x5b, 0x45, + 0x0f, 0x22, 0x3d, 0x2f, 0xf2, 0x97, 0xb7, 0x3f, 0xd7, 0x1c, 0xbc, 0xeb, 0xe7, 0x1d, 0x41, + 0x65, 0x3c, 0x95, 0x0b, 0x88, 0x50, 0x0d, 0xe5, 0x32, 0x2d, 0x99, 0xef, 0x18, 0xdf, 0xdd, + 0x30, 0x42, 0x82, 0x94, 0xc4, 0xb3, 0x09, 0x4f, 0x4c, 0x95, 0x43, 0x34, 0xe5, 0x93, 0xbd, + 0x98, 0x2e, 0xc6, 0x14, + ], + // "azihsm-kbkdf-selftest" + label: &[ + 0x61, 0x7a, 0x69, 0x68, 0x73, 0x6d, 0x2d, 0x6b, 0x62, 0x6b, 0x64, 0x66, 0x2d, 0x73, 0x65, + 0x6c, 0x66, 0x74, 0x65, 0x73, 0x74, + ], + // "uno-hsm-cast-vector" + context: &[ + 0x75, 0x6e, 0x6f, 0x2d, 0x68, 0x73, 0x6d, 0x2d, 0x63, 0x61, 0x73, 0x74, 0x2d, 0x76, 0x65, + 0x63, 0x74, 0x6f, 0x72, + ], + okm: &[ + 0x97, 0x39, 0xea, 0x79, 0xc6, 0x1c, 0xf6, 0x73, 0x15, 0xbf, 0xb1, 0xc6, 0xfd, 0x3e, 0x0d, + 0x06, 0xee, 0xea, 0x91, 0x51, 0x93, 0x58, 0x79, 0xf1, 0xba, 0x87, 0x40, 0x4f, 0xd7, 0x36, + 0x7c, 0x66, 0x83, 0x37, 0x05, 0x13, 0x5e, 0x8b, 0x9e, 0x4f, + ], +}; From 9d5bea24be2307c49df1f63a7ec0f6c1c5a364de Mon Sep 17 00:00:00 2001 From: Abhijit Nathwani Date: Thu, 2 Jul 2026 16:29:33 -0700 Subject: [PATCH 4/6] Add RSA self test and fix pka driver issue --- fw/pal/traits/src/error.rs | 2 + fw/plat/uno/fw/drivers/upka/Cargo.toml | 1 + fw/plat/uno/fw/drivers/upka/src/executor.rs | 13 ++- fw/plat/uno/fw/pal/src/self_test/mod.rs | 4 + fw/plat/uno/fw/pal/src/self_test/pka.rs | 82 +++++++++++++++ fw/plat/uno/fw/pal/src/self_test/vectors.rs | 105 ++++++++++++++++++++ 6 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 fw/plat/uno/fw/pal/src/self_test/pka.rs diff --git a/fw/pal/traits/src/error.rs b/fw/pal/traits/src/error.rs index 0775e69e9..c5f1c0804 100644 --- a/fw/pal/traits/src/error.rs +++ b/fw/pal/traits/src/error.rs @@ -325,10 +325,12 @@ pub enum HsmError { /// Distinct from [`HsmError::InvalidArg`], which signals a /// caller bug (unknown id, kind mismatch, etc.). PartPropNotFound = 0x087000FF, + /// Returned by [`HsmSeedStore`](crate::HsmSeedStore) /// (`mfgr_seed` / `owner_seed`) when no provisioned BKS seed row /// carries the requested selector (SVN / owner id). SeedNotFound = 0x08700100, + /// A cryptographic algorithm self-test (CAST) produced output that /// did not match its known-answer vector. SelfTestKatMismatch = 0x08700101, diff --git a/fw/plat/uno/fw/drivers/upka/Cargo.toml b/fw/plat/uno/fw/drivers/upka/Cargo.toml index bb35fba61..e9302faa6 100644 --- a/fw/plat/uno/fw/drivers/upka/Cargo.toml +++ b/fw/plat/uno/fw/drivers/upka/Cargo.toml @@ -19,6 +19,7 @@ azihsm_fw_uno_error = { workspace = true } azihsm_fw_uno_pac = { workspace = true } azihsm_fw_uno_reg_soc = { workspace = true } bitfield-struct = { workspace = true } +cortex-m = { workspace = true } embassy-sync = { workspace = true } tock-registers = { workspace = true } zerocopy = { features = ["derive"], workspace = true } diff --git a/fw/plat/uno/fw/drivers/upka/src/executor.rs b/fw/plat/uno/fw/drivers/upka/src/executor.rs index 692b814c0..af1253b26 100644 --- a/fw/plat/uno/fw/drivers/upka/src/executor.rs +++ b/fw/plat/uno/fw/drivers/upka/src/executor.rs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use core::sync::atomic::compiler_fence; -use core::sync::atomic::Ordering; - use azihsm_fw_static_ref::StaticRef; use azihsm_fw_uno_reg_soc::io_gsram::regs::IoGsramRegs; use azihsm_fw_uno_reg_soc::io_gsram::IO_GSRAM_BASE; @@ -53,7 +50,15 @@ impl EngineExecutor { arg3: u32, ) { Self::write_descriptor(engine_id, opcode, result, arg1, arg2, arg3); - compiler_fence(Ordering::SeqCst); + // Order the descriptor writes (Normal GSRAM memory) before the doorbell + // write (Device memory). On Cortex-M7 a Device write does NOT order + // prior Normal-memory writes, so without this barrier the engine can + // fetch a partially-written descriptor and fault on the stale arg + // addresses (ERROR_BUS) — observed as a deterministic per-engine + // failure. A `compiler_fence` only constrains the compiler, not the + // hardware store buffer. Matches the reference firmware's `dmb()` before + // the doorbell and the other uno drivers (aes/sha/iic/oic/gdma/ipc). + cortex_m::asm::dmb(); Self::submit_cmd(engine_id); } diff --git a/fw/plat/uno/fw/pal/src/self_test/mod.rs b/fw/plat/uno/fw/pal/src/self_test/mod.rs index 2b0e96272..de905eb39 100644 --- a/fw/plat/uno/fw/pal/src/self_test/mod.rs +++ b/fw/plat/uno/fw/pal/src/self_test/mod.rs @@ -25,6 +25,7 @@ mod aes_cbc; mod kdf; +mod pka; mod vectors; use azihsm_fw_hsm_pal_traits::HsmResult; @@ -41,5 +42,8 @@ pub(crate) async fn run_pre_op(pal: &UnoHsmPal, io: &UnoHsmIo) -> HsmResult<()> aes_cbc::run_aes_cbc(pal, io).await?; kdf::run_hkdf(pal, io).await?; kdf::run_kbkdf(pal, io).await?; + for engine in 0..pka::PKA_ENGINES { + pka::run_rsa_mod_exp_on_engine(pal, io, engine).await?; + } Ok(()) } diff --git a/fw/plat/uno/fw/pal/src/self_test/pka.rs b/fw/plat/uno/fw/pal/src/self_test/pka.rs new file mode 100644 index 000000000..9c58b559c --- /dev/null +++ b/fw/plat/uno/fw/pal/src/self_test/pka.rs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Per-engine PKA cryptographic algorithm self-tests (CAST). +//! +//! Unlike the algorithm-level KATs (AES / HKDF / KBKDF), the PKA tests validate +//! **each hardware PKA engine individually**: they pin a specific engine via +//! [`UpkaDriver::acquire_engine`] and run the operation on that engine, so the +//! caller can cover all [`PKA_ENGINES`] engines. +//! +//! The initial test is the RSA-2048 modular-exponentiation (private-key) +//! known-answer test, ported from the reference firmware's +//! `rsa_mod_exp_self_test`: it computes `plaintext = c^d mod n` on the pinned +//! engine and compares against the expected plaintext `k`. Operands are staged +//! into the self-test IO slot's DMA buffer via the bump allocator (see +//! [`crate::self_test`]). + +use azihsm_fw_hsm_pal_traits::HsmAlloc; +use azihsm_fw_hsm_pal_traits::HsmError; +use azihsm_fw_hsm_pal_traits::HsmResult; +use azihsm_fw_hsm_pal_traits::HsmScopedAlloc; +use azihsm_fw_uno_drivers_upka::UpkaRsaKeyType; +use azihsm_fw_uno_reg_soc::io_gsram::UPKA_ENGINE_CMD_COUNT; +use azihsm_fw_uno_trace::tracing::error; + +use super::vectors::RSA_2K_MOD_EXP_KAT; +use crate::UnoHsmIo; +use crate::UnoHsmPal; + +/// Number of hardware PKA engines to validate (one self-test run each). +pub(super) const PKA_ENGINES: u8 = UPKA_ENGINE_CMD_COUNT as u8; + +/// RSA-2048 modulus size in bytes. +const RSA_2K_LEN: usize = 256; + +/// Runs the RSA-2048 mod-exp (private-key) known-answer test on PKA engine +/// `engine`. +/// +/// Computes `c^d mod n` on the pinned engine and compares the result against +/// the expected plaintext. Returns [`HsmError::SelfTestKatMismatch`] on a +/// mismatch, or any error surfaced by the PKA engine / allocator. +pub(super) async fn run_rsa_mod_exp_on_engine( + pal: &UnoHsmPal, + io: &UnoHsmIo, + engine: u8, +) -> HsmResult<()> { + let v = &RSA_2K_MOD_EXP_KAT; + + pal.alloc_scoped_async(io, async |scope| { + // Private key is laid out as `d ‖ n` (exponent then modulus). + let key = scope.dma_alloc(RSA_2K_LEN * 2)?; + key[..RSA_2K_LEN].copy_from_slice(v.d); + key[RSA_2K_LEN..].copy_from_slice(v.n); + // Input ciphertext `c` and output plaintext buffer. + let input = scope.dma_alloc(RSA_2K_LEN)?; + input.copy_from_slice(v.c); + let output = scope.dma_alloc_zeroed(RSA_2K_LEN)?; + + // Pin the requested engine for the operation, release afterwards. + let mut eng = pal.pka.acquire_engine(engine).await?; + let outcome = eng + .rsa_mod_exp_priv(UpkaRsaKeyType::Rsa2048, &*key, &*input, &mut *output) + .await; + let release = eng.release().await; + outcome?; + release?; + + // KAT vectors are public, fixed test data — a plain slice comparison is + // correct; no constant-time compare is needed. + if &output[..] != v.k { + error!( + "selftest", + HsmError::SelfTestKatMismatch, + "RSA-2K mod-exp KAT mismatch on engine" + ); + return Err(HsmError::SelfTestKatMismatch); + } + + Ok::<(), HsmError>(()) + }) + .await +} diff --git a/fw/plat/uno/fw/pal/src/self_test/vectors.rs b/fw/plat/uno/fw/pal/src/self_test/vectors.rs index c488246c8..d1cee70c5 100644 --- a/fw/plat/uno/fw/pal/src/self_test/vectors.rs +++ b/fw/plat/uno/fw/pal/src/self_test/vectors.rs @@ -160,3 +160,108 @@ pub(super) const KBKDF_SHA512_KAT: KbkdfKat = KbkdfKat { 0x7c, 0x66, 0x83, 0x37, 0x05, 0x13, 0x5e, 0x8b, 0x9e, 0x4f, ], }; + +/// RSA-2048 modular-exponentiation (private-key) known-answer vector. +/// +/// Exercises the raw RSA private-key operation `plaintext = c^d mod n` on a PKA +/// engine, comparing against the expected plaintext `k`. Byte-for-byte +/// identical to the reference firmware's `KAT_RSA_TEST_2K_TEST_VECTORS` +/// (`n`, `d`, `c`, `k`). All values are little-endian, matching the LE-native +/// PKA wire format. +pub(super) struct RsaModExpKat { + /// Modulus `n` (256 bytes). + pub n: &'static [u8], + + /// Private exponent `d` (256 bytes). + pub d: &'static [u8], + + /// Ciphertext input `c` (256 bytes). + pub c: &'static [u8], + + /// Expected plaintext output `k` (256 bytes). + pub k: &'static [u8], +} + +/// RSA-2048 mod-exp (private-key) known-answer test vector. +pub(super) const RSA_2K_MOD_EXP_KAT: RsaModExpKat = RsaModExpKat { + n: &[ + 0xb1, 0x94, 0x6c, 0xfa, 0x0a, 0x3d, 0xff, 0x4e, 0xab, 0x05, 0xd2, 0xca, 0xcc, 0xdd, 0x6b, + 0xfd, 0x45, 0x89, 0x40, 0xda, 0xd0, 0xea, 0x71, 0x97, 0x47, 0x03, 0xc3, 0x77, 0x30, 0xe8, + 0xea, 0xd8, 0x69, 0xb9, 0x0f, 0x16, 0xa1, 0x42, 0x16, 0xcb, 0x89, 0x36, 0xdf, 0x5a, 0x2a, + 0x8b, 0x97, 0x0e, 0x96, 0xe9, 0xe7, 0x8c, 0x82, 0xf5, 0x6d, 0xf2, 0x2a, 0x72, 0xfe, 0x9a, + 0x7e, 0x68, 0x1b, 0x0b, 0x6a, 0x40, 0x44, 0x54, 0x3f, 0xe5, 0xe9, 0xd3, 0x33, 0x5d, 0x1b, + 0x6b, 0x1b, 0xad, 0xde, 0x58, 0x1c, 0xaf, 0x60, 0x7d, 0xd9, 0xa1, 0x98, 0x03, 0x57, 0x56, + 0x13, 0x9f, 0x44, 0xef, 0x57, 0x52, 0xed, 0x52, 0x69, 0x73, 0x17, 0x04, 0x3f, 0x19, 0xfb, + 0xc9, 0x8f, 0x54, 0x15, 0xb3, 0x4a, 0x24, 0x49, 0x9c, 0x63, 0x3c, 0xe5, 0x0b, 0xbc, 0x60, + 0x9e, 0x22, 0x49, 0x8a, 0xbc, 0x22, 0x0f, 0x87, 0xf6, 0x83, 0x63, 0x6a, 0x65, 0x0b, 0x9b, + 0x48, 0xe3, 0x5e, 0x04, 0x8c, 0xf5, 0x0d, 0xc6, 0x10, 0x73, 0xdf, 0x96, 0xaf, 0x24, 0x55, + 0x7c, 0xd2, 0x69, 0x2b, 0x9a, 0xd7, 0x9d, 0x7c, 0xef, 0x59, 0x24, 0xf6, 0xad, 0xd2, 0x52, + 0x62, 0x72, 0x2d, 0xa0, 0x94, 0x0a, 0x30, 0xa1, 0xaf, 0x1d, 0xf7, 0xdb, 0x55, 0xcb, 0xb8, + 0xbc, 0x7d, 0x83, 0x95, 0xea, 0x3b, 0xe3, 0x46, 0x88, 0xf4, 0xf9, 0xd6, 0x13, 0xa6, 0xc7, + 0xc4, 0x9d, 0x29, 0xe1, 0x8c, 0x29, 0x5e, 0xf3, 0x0f, 0x26, 0xb9, 0x41, 0x5a, 0xc9, 0xcf, + 0x6e, 0xe1, 0xc9, 0xed, 0xdd, 0x87, 0x74, 0xef, 0xf4, 0x97, 0x3b, 0xfe, 0x77, 0x26, 0xd7, + 0xa5, 0x86, 0xa1, 0xf7, 0x25, 0x07, 0x97, 0x02, 0x86, 0x0c, 0x39, 0x35, 0x55, 0x5b, 0x92, + 0x64, 0xbb, 0x13, 0x7a, 0x36, 0xc3, 0xd4, 0x47, 0xe2, 0xbf, 0x20, 0x7f, 0x80, 0x24, 0xb8, + 0xc3, + ], + d: &[ + 0x39, 0xe0, 0x6c, 0x00, 0x27, 0x47, 0x86, 0x6d, 0xf1, 0xe9, 0xb9, 0x08, 0x60, 0x01, 0x8e, + 0xaa, 0xc6, 0x82, 0x41, 0x67, 0xe9, 0xb1, 0xbc, 0x83, 0x6e, 0x58, 0x35, 0x6e, 0x0d, 0xc7, + 0xd1, 0xc5, 0xb7, 0x47, 0x57, 0xa2, 0xca, 0x2f, 0xba, 0x47, 0xc8, 0xb2, 0x01, 0x7b, 0x63, + 0x49, 0x1a, 0x5c, 0xd3, 0x53, 0x2b, 0xb6, 0x27, 0xa1, 0x34, 0xf1, 0x0c, 0x5a, 0x2e, 0xec, + 0xa6, 0x4f, 0xd6, 0x99, 0x87, 0xc9, 0x78, 0xcc, 0x82, 0xf2, 0x4a, 0x0e, 0xda, 0x2e, 0x45, + 0xce, 0xde, 0x9a, 0x49, 0x61, 0x9a, 0xd4, 0x45, 0x34, 0x97, 0x29, 0xd7, 0x7b, 0x3d, 0x6a, + 0x1e, 0x00, 0x32, 0xd6, 0xbe, 0xcf, 0x90, 0x1f, 0x7d, 0x6f, 0x6e, 0x5e, 0xe9, 0xed, 0x34, + 0x75, 0x61, 0x31, 0x15, 0x3d, 0xcd, 0xc3, 0xea, 0x1f, 0x89, 0x1e, 0xd4, 0x3a, 0x54, 0x64, + 0x9f, 0x1a, 0x27, 0x0d, 0x6b, 0x02, 0x33, 0x38, 0x25, 0x47, 0xe5, 0x50, 0x44, 0xa4, 0xa8, + 0x0e, 0xf8, 0x01, 0x1c, 0xd3, 0xb5, 0x05, 0xea, 0x6f, 0x8a, 0x9f, 0x3d, 0x35, 0x51, 0xe4, + 0x96, 0xc1, 0x4a, 0x7b, 0x04, 0xe5, 0x1c, 0x64, 0x0f, 0xa7, 0xc3, 0x08, 0xb4, 0xa2, 0x13, + 0x9d, 0x05, 0x59, 0x27, 0xcf, 0x51, 0x4a, 0x82, 0xb1, 0xdb, 0x1f, 0xdb, 0xcc, 0x34, 0xe3, + 0xe2, 0x6d, 0x9e, 0xe1, 0xda, 0x7b, 0x33, 0x1e, 0x2b, 0x08, 0x4c, 0xc0, 0x00, 0x1e, 0xab, + 0x77, 0x81, 0xea, 0x1e, 0xc3, 0x8b, 0x6c, 0xee, 0x90, 0xc4, 0xbb, 0xd6, 0xde, 0x7a, 0x92, + 0xd9, 0xff, 0x75, 0xd4, 0x42, 0x8a, 0xa6, 0x41, 0x87, 0x64, 0x3a, 0x93, 0x96, 0x42, 0x75, + 0x37, 0xa2, 0x56, 0x43, 0x99, 0x82, 0xb9, 0xdb, 0xc5, 0x17, 0x6d, 0xcb, 0xd8, 0xcd, 0x1e, + 0x9a, 0x15, 0xc8, 0xce, 0x1d, 0x37, 0xf9, 0x0c, 0xba, 0xec, 0xc5, 0xdc, 0xe8, 0x7c, 0x3d, + 0x18, + ], + c: &[ + 0x73, 0xf7, 0x6e, 0x60, 0xf3, 0xd9, 0x09, 0xaf, 0xb2, 0x27, 0xd5, 0xf8, 0x90, 0xa0, 0xeb, + 0xcb, 0x73, 0xea, 0x8c, 0x72, 0xe2, 0xab, 0xb6, 0xa4, 0x22, 0xe5, 0xdd, 0xa1, 0xf6, 0x1c, + 0x4e, 0xa8, 0x80, 0xa3, 0x5f, 0x88, 0xcf, 0x31, 0x30, 0xbf, 0xa3, 0xee, 0x19, 0x84, 0x4d, + 0xaf, 0xb7, 0xd0, 0x62, 0xff, 0x2b, 0x19, 0xe6, 0x56, 0xa7, 0xd4, 0x0d, 0x75, 0x51, 0x01, + 0xa9, 0x11, 0xa6, 0xfe, 0x36, 0x26, 0x9c, 0x0f, 0xbf, 0xad, 0xa3, 0xfa, 0xeb, 0xf2, 0xd9, + 0x19, 0x93, 0xbc, 0xd7, 0x1c, 0x12, 0xfa, 0xf7, 0x09, 0xff, 0x9c, 0xa9, 0xff, 0xf3, 0x6d, + 0xbb, 0x24, 0x30, 0x81, 0xfa, 0x66, 0x7e, 0x45, 0x87, 0x7e, 0x99, 0x0c, 0x78, 0x1c, 0x0a, + 0x83, 0x95, 0x75, 0x8d, 0xfd, 0x94, 0x24, 0x36, 0x79, 0x95, 0x26, 0xc7, 0x68, 0xd1, 0x65, + 0x37, 0x1e, 0xdf, 0x03, 0xa1, 0x06, 0x94, 0x04, 0x9e, 0x8a, 0xfc, 0x66, 0x2d, 0x84, 0x53, + 0xab, 0x97, 0x37, 0x42, 0x69, 0xcd, 0x02, 0xc0, 0x4d, 0x07, 0x94, 0x2b, 0x19, 0x3a, 0x76, + 0x67, 0xc8, 0x73, 0x32, 0x48, 0x3e, 0x55, 0x0b, 0x5c, 0x53, 0xe6, 0xab, 0xba, 0x01, 0x8c, + 0xda, 0x0c, 0x00, 0x7e, 0xfd, 0x26, 0x45, 0x1e, 0xd7, 0x04, 0x6f, 0x72, 0x65, 0x7f, 0x03, + 0x0b, 0x66, 0x9a, 0x1a, 0xf8, 0xbb, 0x56, 0x37, 0xbc, 0x50, 0xaa, 0x1d, 0xc5, 0x04, 0x02, + 0x37, 0xb6, 0x18, 0x6d, 0x37, 0x0c, 0xbe, 0x67, 0x34, 0x85, 0x34, 0xec, 0x12, 0x62, 0xc7, + 0x54, 0x4f, 0x60, 0xc4, 0x37, 0xfc, 0xe5, 0x59, 0xe8, 0xba, 0xb5, 0xca, 0xbc, 0x30, 0xaf, + 0x48, 0x30, 0x61, 0xf4, 0xfa, 0x02, 0x09, 0xae, 0xd2, 0x47, 0xb6, 0x33, 0x0b, 0xe3, 0x2b, + 0x24, 0x82, 0xf2, 0xce, 0x4d, 0x63, 0x87, 0x67, 0x2d, 0x7b, 0x7f, 0x58, 0xeb, 0x95, 0xb6, + 0x53, + ], + k: &[ + 0x0d, 0xd1, 0xc1, 0x57, 0xef, 0x85, 0xae, 0x6d, 0x12, 0x7d, 0x9f, 0x39, 0x25, 0xf0, 0x1a, + 0x8a, 0xd2, 0x9b, 0x95, 0x10, 0x45, 0x09, 0x01, 0x82, 0xde, 0x62, 0x7a, 0x6d, 0x37, 0x4b, + 0xe8, 0x11, 0x36, 0xca, 0x83, 0xfd, 0xdb, 0x69, 0x36, 0xca, 0x12, 0xa6, 0xf8, 0x58, 0x3c, + 0xb6, 0x8f, 0x52, 0xf1, 0x14, 0xce, 0x48, 0x07, 0x0d, 0xe3, 0x53, 0xd2, 0x23, 0xa9, 0x8f, + 0x5c, 0x2b, 0x51, 0x83, 0x3e, 0x25, 0x24, 0x93, 0xe5, 0xc0, 0x91, 0x43, 0xfe, 0x0d, 0xb2, + 0x60, 0xa8, 0xe3, 0x7d, 0x77, 0x9f, 0xea, 0x9b, 0x84, 0xe4, 0xc4, 0xd9, 0xdb, 0xd9, 0x4f, + 0x56, 0x29, 0x42, 0xe5, 0x73, 0x4e, 0x6d, 0xba, 0x37, 0xd1, 0x7e, 0xf2, 0x23, 0x1c, 0xec, + 0xa0, 0x87, 0x6a, 0x3c, 0x06, 0x55, 0x6e, 0xd4, 0xdf, 0xbb, 0xf9, 0x23, 0x91, 0xcf, 0x03, + 0x83, 0x5a, 0x15, 0x9c, 0x94, 0x7a, 0x30, 0x02, 0xd8, 0x23, 0xa4, 0x13, 0xcb, 0xa2, 0x6b, + 0x69, 0x5a, 0xbd, 0xd1, 0x45, 0x41, 0xb5, 0xcc, 0x12, 0xd9, 0xfa, 0x55, 0xbd, 0xd6, 0x59, + 0x2b, 0x9c, 0x87, 0x3d, 0x7a, 0x58, 0x2c, 0xd2, 0xd2, 0x13, 0x5d, 0xad, 0x2e, 0x69, 0xbf, + 0xbf, 0xe6, 0xac, 0xbf, 0xa4, 0x3f, 0xc5, 0xfd, 0x52, 0xd0, 0xc3, 0x4c, 0x4b, 0x45, 0xc7, + 0xf4, 0x82, 0x3f, 0x58, 0xfe, 0xb9, 0xe1, 0x66, 0x1f, 0xa9, 0xbe, 0xa5, 0xb7, 0x62, 0xc9, + 0x5c, 0xf8, 0xb9, 0x6c, 0xe2, 0x60, 0x0b, 0xc5, 0x4d, 0x6c, 0xfe, 0x24, 0x85, 0x8b, 0x7f, + 0x36, 0xe3, 0xc3, 0xa3, 0x68, 0xc5, 0xa2, 0x5d, 0xf6, 0xaa, 0x05, 0x51, 0xfc, 0xbe, 0xca, + 0xcf, 0x84, 0xc1, 0xe5, 0xa0, 0xb0, 0xc6, 0xaf, 0x93, 0xd0, 0xd2, 0xff, 0x12, 0xb1, 0x4a, + 0x73, 0xfc, 0x50, 0x22, 0xad, 0x96, 0x44, 0xc0, 0xdb, 0x9e, 0x93, 0x1c, 0x3a, 0xc5, 0xa5, + 0x00, + ], +}; From 13c12dfc7bc10ab0845a78b0c981ee489b004ebe Mon Sep 17 00:00:00 2001 From: Abhijit Nathwani Date: Thu, 2 Jul 2026 16:45:50 -0700 Subject: [PATCH 5/6] Add missing kdf.rs file --- fw/plat/uno/fw/pal/src/self_test/kdf.rs | 111 ++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 fw/plat/uno/fw/pal/src/self_test/kdf.rs diff --git a/fw/plat/uno/fw/pal/src/self_test/kdf.rs b/fw/plat/uno/fw/pal/src/self_test/kdf.rs new file mode 100644 index 000000000..e32f0b776 --- /dev/null +++ b/fw/plat/uno/fw/pal/src/self_test/kdf.rs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Key-derivation-function cryptographic algorithm self-tests (CAST). +//! +//! Runs fixed known-answer vectors through the HSM KDF path and compares the +//! derived key material against the expected output, matching the reference +//! firmware's `hkdf_self_test_256` (and, later, `kbkdf_self_test_512`). +//! Operands are staged into the self-test IO slot's DMA buffer via the bump +//! allocator (see [`crate::self_test`]). + +use azihsm_fw_hsm_pal_traits::HsmAlloc; +use azihsm_fw_hsm_pal_traits::HsmError; +use azihsm_fw_hsm_pal_traits::HsmHashAlgo; +use azihsm_fw_hsm_pal_traits::HsmKdf; +use azihsm_fw_hsm_pal_traits::HsmResult; +use azihsm_fw_hsm_pal_traits::HsmScopedAlloc; +use azihsm_fw_uno_trace::tracing::error; + +use super::vectors::HKDF_SHA256_KAT; +use super::vectors::KBKDF_SHA512_KAT; +use crate::UnoHsmIo; +use crate::UnoHsmPal; + +/// Runs the HKDF (HMAC-SHA-256) known-answer test. +/// +/// Performs HKDF-Extract (`salt` + `ikm` → PRK) then HKDF-Expand +/// (PRK + `info` → OKM) and compares the derived OKM against the expected +/// vector. Returns [`HsmError::SelfTestKatMismatch`] on a mismatch (or any +/// error surfaced by the KDF path / allocator). +pub(super) async fn run_hkdf(pal: &UnoHsmPal, io: &UnoHsmIo) -> HsmResult<()> { + let v = &HKDF_SHA256_KAT; + let algo = HsmHashAlgo::Sha256; + + pal.alloc_scoped_async(io, async |scope| { + // Stage the KAT operands into DMA-visible memory (the self-test slot). + let salt = scope.dma_alloc(v.salt.len())?; + salt.copy_from_slice(v.salt); + let ikm = scope.dma_alloc(v.ikm.len())?; + ikm.copy_from_slice(v.ikm); + let prk = scope.dma_alloc_zeroed(algo.digest_len())?; + + // HKDF-Extract: PRK = HMAC-Hash(salt, IKM). + pal.hkdf_extract(io, algo, Some(&*salt), &*ikm, &mut *prk) + .await?; + + // HKDF-Expand: OKM = Expand(PRK, info, L). + let info = scope.dma_alloc(v.info.len())?; + info.copy_from_slice(v.info); + let okm = scope.dma_alloc_zeroed(v.okm.len())?; + pal.hkdf_expand(io, algo, &*prk, Some(&*info), &mut *okm) + .await?; + + // KAT vectors are public, fixed test data — a plain slice comparison is + // correct; no constant-time compare is needed. + if &okm[..] != v.okm { + error!( + "selftest", + HsmError::SelfTestKatMismatch, + "HKDF KAT mismatch" + ); + return Err(HsmError::SelfTestKatMismatch); + } + + Ok::<(), HsmError>(()) + }) + .await +} + +/// Runs the KBKDF (SP 800-108 counter mode, HMAC-SHA-512) known-answer test. +/// +/// Derives output via [`sp800_108_kdf`](HsmKdf::sp800_108_kdf) — the same +/// production path used by the `KbkdfCounterHmacDerive` DDI op — and compares +/// against the expected vector. Returns [`HsmError::SelfTestKatMismatch`] on a +/// mismatch (or any error surfaced by the KDF path / allocator). +pub(super) async fn run_kbkdf(pal: &UnoHsmPal, io: &UnoHsmIo) -> HsmResult<()> { + let v = &KBKDF_SHA512_KAT; + + pal.alloc_scoped_async(io, async |scope| { + // Stage the KAT operands into DMA-visible memory (the self-test slot). + let key = scope.dma_alloc(v.key.len())?; + key.copy_from_slice(v.key); + let label = scope.dma_alloc(v.label.len())?; + label.copy_from_slice(v.label); + let context = scope.dma_alloc(v.context.len())?; + context.copy_from_slice(v.context); + let okm = scope.dma_alloc_zeroed(v.okm.len())?; + + pal.sp800_108_kdf( + io, + HsmHashAlgo::Sha512, + &*key, + Some(&*label), + Some(&*context), + &mut *okm, + ) + .await?; + + if &okm[..] != v.okm { + error!( + "selftest", + HsmError::SelfTestKatMismatch, + "KBKDF KAT mismatch" + ); + return Err(HsmError::SelfTestKatMismatch); + } + + Ok::<(), HsmError>(()) + }) + .await +} From be2d53442839470e95ccc0c0907a2b5998f6a782 Mon Sep 17 00:00:00 2001 From: Abhijit Nathwani Date: Mon, 6 Jul 2026 16:07:33 -0700 Subject: [PATCH 6/6] Add RSA-2K CRT self tests * Modify the driver to support this test --- fw/plat/uno/fw/drivers/upka/src/engine.rs | 25 ++- fw/plat/uno/fw/drivers/upka/src/opcode.rs | 43 +++++- fw/plat/uno/fw/pal/src/self_test/mod.rs | 12 +- fw/plat/uno/fw/pal/src/self_test/pka.rs | 69 ++++++++- fw/plat/uno/fw/pal/src/self_test/vectors.rs | 161 ++++++++++++++++++++ 5 files changed, 288 insertions(+), 22 deletions(-) diff --git a/fw/plat/uno/fw/drivers/upka/src/engine.rs b/fw/plat/uno/fw/drivers/upka/src/engine.rs index 33f320536..3aeabf917 100644 --- a/fw/plat/uno/fw/drivers/upka/src/engine.rs +++ b/fw/plat/uno/fw/drivers/upka/src/engine.rs @@ -218,7 +218,10 @@ impl UpkaEngine<'_, DEPTH, ENGINES> { /// # Parameters /// /// - `key_type`: RSA key type (size + format selector). - /// - `key`: DMA-capable private key buffer. + /// - `key`: DMA-capable private key buffer. For standard keys this is a + /// `d ‖ n` blob; for CRT keys it is a contiguous `param1 ‖ param2` blob + /// (`param1` = `p ‖ q ‖ dp ‖ dq`, `param2` = `n ‖ n1q ‖ n2p`) at least + /// `5 * mod_size` bytes long. /// - `input`: DMA-capable input block buffer. /// - `output`: DMA-capable output block buffer. /// @@ -235,16 +238,28 @@ impl UpkaEngine<'_, DEPTH, ENGINES> { output: &mut DmaBuf, ) -> HsmResult<()> { let mod_size = rsa_mod_size(key_type); - Self::ensure_cmd_input( - !key.is_empty() && input.len() == mod_size && output.len() == mod_size, - )?; + + // CRT private keys are a contiguous `param1 ‖ param2` blob: `param1` + // (`p ‖ q ‖ dp ‖ dq`, 2·mod_size) is read from the key/arg2 pointer and + // `param2` (`n ‖ n1q ‖ n2p`, 3·mod_size) is read from arg3. Standard + // (non-CRT) keys are a single `d ‖ n` blob read from arg2 with arg3 = 0. + let (key_ok, arg3) = if rsa_is_crt(key_type) { + let param2_off = rsa_crt_param1_len(key_type); + ( + key.len() >= param2_off + 3 * mod_size, + key.as_ptr() as u32 + param2_off as u32, + ) + } else { + (!key.is_empty(), 0) + }; + Self::ensure_cmd_input(key_ok && input.len() == mod_size && output.len() == mod_size)?; self.execute_cmd( rsa_priv_opcode(key_type), output.as_mut_ptr() as u32, input.as_ptr() as u32, key.as_ptr() as u32, - 0, + arg3, ) .await } diff --git a/fw/plat/uno/fw/drivers/upka/src/opcode.rs b/fw/plat/uno/fw/drivers/upka/src/opcode.rs index 8d826eacd..552789261 100644 --- a/fw/plat/uno/fw/drivers/upka/src/opcode.rs +++ b/fw/plat/uno/fw/drivers/upka/src/opcode.rs @@ -227,6 +227,23 @@ fn rsa_opcode_index(key_type: UpkaRsaKeyType) -> usize { } } +/// Whether the selected RSA key type uses the CRT (Chinese Remainder Theorem) +/// key format. +/// +/// # Parameters +/// +/// - `key_type`: RSA key type selector. +/// +/// # Returns +/// +/// - `true` for CRT key types, `false` for standard (non-CRT) key types. +pub(crate) fn rsa_is_crt(key_type: UpkaRsaKeyType) -> bool { + matches!( + key_type, + UpkaRsaKeyType::Rsa2048Crt | UpkaRsaKeyType::Rsa3072Crt | UpkaRsaKeyType::Rsa4096Crt + ) +} + /// Return the RSA private-key exponentiation opcode for the selected key type. /// /// # Parameters @@ -238,17 +255,33 @@ fn rsa_opcode_index(key_type: UpkaRsaKeyType) -> usize { /// - Hardware opcode for RSA private exponentiation (CRT or standard). pub(crate) fn rsa_priv_opcode(key_type: UpkaRsaKeyType) -> u32 { let index = rsa_opcode_index(key_type); - let is_crt = matches!( - key_type, - UpkaRsaKeyType::Rsa2048Crt | UpkaRsaKeyType::Rsa3072Crt | UpkaRsaKeyType::Rsa4096Crt - ); - if is_crt { + if rsa_is_crt(key_type) { RSA_OPCODES[index].2 } else { RSA_OPCODES[index].0 } } +/// Return the length in bytes of a CRT private key's `param1` block +/// (`p ‖ q ‖ dp ‖ dq`), which is four half-operands of `mod_size / 2` bytes, +/// i.e. `2 * mod_size`. +/// +/// The hardware reads a CRT private key as two sub-blocks: `param1` from the +/// key (arg2) pointer and `param2` (`n ‖ n1q ‖ n2p`, `3 * mod_size` bytes) from +/// the arg3 pointer. This helper gives the offset of `param2` within a +/// contiguous `param1 ‖ param2` key blob. +/// +/// # Parameters +/// +/// - `key_type`: RSA key type selector. +/// +/// # Returns +/// +/// - `param1` length in bytes (`2 * mod_size`). +pub(crate) fn rsa_crt_param1_len(key_type: UpkaRsaKeyType) -> usize { + 2 * rsa_mod_size(key_type) +} + /// Return the RSA public-key exponentiation opcode for the selected key type. /// /// # Parameters diff --git a/fw/plat/uno/fw/pal/src/self_test/mod.rs b/fw/plat/uno/fw/pal/src/self_test/mod.rs index de905eb39..d21ddaa55 100644 --- a/fw/plat/uno/fw/pal/src/self_test/mod.rs +++ b/fw/plat/uno/fw/pal/src/self_test/mod.rs @@ -18,10 +18,11 @@ //! //! # Status //! -//! AES-256-CBC, HKDF, and KBKDF are implemented. Further tests (per-engine -//! ECDSA / ECDH / RSA, and the RNG/DRBG FW-mode KAT) are appended to -//! [`run_pre_op`] as they are added — each is a direct call, with per-PKA-engine -//! tests wrapped in a `for engine in 0..PKA_ENGINES` loop. +//! AES-256-CBC, HKDF, KBKDF, and per-engine RSA-2048 mod-exp (standard and CRT) +//! are implemented. Further tests (per-engine ECDSA / ECDH, and the RNG/DRBG +//! FW-mode KAT) are appended to [`run_pre_op`] as they are added — each is a +//! direct call, with per-PKA-engine tests wrapped in a +//! `for engine in 0..PKA_ENGINES` loop. mod aes_cbc; mod kdf; @@ -45,5 +46,8 @@ pub(crate) async fn run_pre_op(pal: &UnoHsmPal, io: &UnoHsmIo) -> HsmResult<()> for engine in 0..pka::PKA_ENGINES { pka::run_rsa_mod_exp_on_engine(pal, io, engine).await?; } + for engine in 0..pka::PKA_ENGINES { + pka::run_rsa_mod_exp_crt_on_engine(pal, io, engine).await?; + } Ok(()) } diff --git a/fw/plat/uno/fw/pal/src/self_test/pka.rs b/fw/plat/uno/fw/pal/src/self_test/pka.rs index 9c58b559c..f6aaa5920 100644 --- a/fw/plat/uno/fw/pal/src/self_test/pka.rs +++ b/fw/plat/uno/fw/pal/src/self_test/pka.rs @@ -8,12 +8,12 @@ //! [`UpkaDriver::acquire_engine`] and run the operation on that engine, so the //! caller can cover all [`PKA_ENGINES`] engines. //! -//! The initial test is the RSA-2048 modular-exponentiation (private-key) -//! known-answer test, ported from the reference firmware's -//! `rsa_mod_exp_self_test`: it computes `plaintext = c^d mod n` on the pinned -//! engine and compares against the expected plaintext `k`. Operands are staged -//! into the self-test IO slot's DMA buffer via the bump allocator (see -//! [`crate::self_test`]). +//! The RSA tests are ported from the reference firmware's +//! `rsa_mod_exp_self_test` / `rsa_mod_exp_crt_self_test`: they compute +//! `plaintext = c^d mod n` on the pinned engine — using either a standard +//! `d ‖ n` key or a CRT `param1 ‖ param2` key — and compare against the expected +//! plaintext `k`. Operands are staged into the self-test IO slot's DMA buffer +//! via the bump allocator (see [`crate::self_test`]). use azihsm_fw_hsm_pal_traits::HsmAlloc; use azihsm_fw_hsm_pal_traits::HsmError; @@ -23,6 +23,7 @@ use azihsm_fw_uno_drivers_upka::UpkaRsaKeyType; use azihsm_fw_uno_reg_soc::io_gsram::UPKA_ENGINE_CMD_COUNT; use azihsm_fw_uno_trace::tracing::error; +use super::vectors::RSA_2K_CRT_KAT; use super::vectors::RSA_2K_MOD_EXP_KAT; use crate::UnoHsmIo; use crate::UnoHsmPal; @@ -65,8 +66,6 @@ pub(super) async fn run_rsa_mod_exp_on_engine( outcome?; release?; - // KAT vectors are public, fixed test data — a plain slice comparison is - // correct; no constant-time compare is needed. if &output[..] != v.k { error!( "selftest", @@ -80,3 +79,57 @@ pub(super) async fn run_rsa_mod_exp_on_engine( }) .await } + +/// Runs the RSA-2048 CRT mod-exp (private-key) known-answer test on PKA engine +/// `engine`. +/// +/// Ported from the reference firmware's `rsa_mod_exp_crt_self_test`. The CRT +/// private key is staged as a contiguous `param1 ‖ param2` blob: `param1` +/// (`p ‖ q ‖ dp ‖ dq`, `2 * RSA_2K_LEN`) is read from the key pointer and +/// `param2` (`n ‖ n1q ‖ n2p`, `3 * RSA_2K_LEN`) is read by the hardware from the +/// arg3 pointer that [`rsa_mod_exp_priv`](azihsm_fw_uno_drivers_upka::UpkaEngine::rsa_mod_exp_priv) +/// derives for CRT key types. Computes `c^d mod n` on the pinned engine and +/// compares against the expected plaintext. Returns +/// [`HsmError::SelfTestKatMismatch`] on a mismatch, or any error surfaced by the +/// PKA engine / allocator. +pub(super) async fn run_rsa_mod_exp_crt_on_engine( + pal: &UnoHsmPal, + io: &UnoHsmIo, + engine: u8, +) -> HsmResult<()> { + let v = &RSA_2K_CRT_KAT; + + pal.alloc_scoped_async(io, async |scope| { + // CRT private key blob: `param1` (p‖q‖dp‖dq) followed by `param2` + // (n‖n1q‖n2p). The driver passes the blob base as arg2 (param1) and + // `base + param1.len()` as arg3 (param2) for CRT key types. + let key = scope.dma_alloc(v.param1.len() + v.param2.len())?; + key[..v.param1.len()].copy_from_slice(v.param1); + key[v.param1.len()..].copy_from_slice(v.param2); + // Input ciphertext `c` and output plaintext buffer. + let input = scope.dma_alloc(RSA_2K_LEN)?; + input.copy_from_slice(v.c); + let output = scope.dma_alloc_zeroed(RSA_2K_LEN)?; + + // Pin the requested engine for the operation, release afterwards. + let mut eng = pal.pka.acquire_engine(engine).await?; + let outcome = eng + .rsa_mod_exp_priv(UpkaRsaKeyType::Rsa2048Crt, &*key, &*input, &mut *output) + .await; + let release = eng.release().await; + outcome?; + release?; + + if &output[..] != v.k { + error!( + "selftest", + HsmError::SelfTestKatMismatch, + "RSA-2K CRT mod-exp KAT mismatch on engine" + ); + return Err(HsmError::SelfTestKatMismatch); + } + + Ok::<(), HsmError>(()) + }) + .await +} diff --git a/fw/plat/uno/fw/pal/src/self_test/vectors.rs b/fw/plat/uno/fw/pal/src/self_test/vectors.rs index d1cee70c5..2ab11fa1f 100644 --- a/fw/plat/uno/fw/pal/src/self_test/vectors.rs +++ b/fw/plat/uno/fw/pal/src/self_test/vectors.rs @@ -265,3 +265,164 @@ pub(super) const RSA_2K_MOD_EXP_KAT: RsaModExpKat = RsaModExpKat { 0x00, ], }; + +/// RSA-2048 CRT modular-exponentiation (private-key) known-answer vector. +/// +/// Exercises the raw RSA-CRT private-key operation on a PKA engine: the +/// hardware reads `param1` (`p ‖ q ‖ dp ‖ dq`, 512 bytes) from the key +/// pointer and `param2` (`n ‖ n1q ‖ n2p`, 768 bytes) from the arg3 pointer, +/// computes `plaintext = c^d mod n`, and the result is compared against `k`. +/// Byte-for-byte identical to the reference firmware's +/// `KAT_RSA_TEST_2K_CRT_TEST_VECTORS` (the trailing `param2.ext` field is not +/// read by the operation and is omitted). All values are little-endian, +/// matching the LE-native PKA wire format. +pub(super) struct Rsa2kCrtKat { + /// CRT parameter block 1: `p ‖ q ‖ dp ‖ dq` (512 bytes). + pub param1: &'static [u8], + + /// CRT parameter block 2: `n ‖ n1q ‖ n2p` (768 bytes). + pub param2: &'static [u8], + + /// Ciphertext input `c` (256 bytes). + pub c: &'static [u8], + + /// Expected plaintext output `k` (256 bytes). + pub k: &'static [u8], +} + +/// RSA-2048 CRT mod-exp (private-key) known-answer test vector. +pub(super) const RSA_2K_CRT_KAT: Rsa2kCrtKat = Rsa2kCrtKat { + // param1 = p ‖ q ‖ dp ‖ dq (4 × 128 bytes) + param1: &[ + 0xd5, 0x31, 0x82, 0x4a, 0xbb, 0x1a, 0xd1, 0x6b, 0xc3, 0x8e, 0x3f, 0x13, 0x52, 0x07, 0x3b, + 0x3c, 0xa1, 0x04, 0x3e, 0xc3, 0xc8, 0x6a, 0xf9, 0x87, 0x53, 0x8c, 0x82, 0x96, 0x9e, 0xa9, + 0x97, 0x3d, 0x66, 0xa7, 0xc0, 0x53, 0x25, 0xce, 0x05, 0x50, 0x20, 0x5c, 0x94, 0x6a, 0xc1, + 0xc9, 0xab, 0x92, 0x90, 0xdf, 0xc1, 0xf1, 0xd1, 0xbb, 0xd1, 0x0a, 0xa0, 0x82, 0xd9, 0xf4, + 0x16, 0xb1, 0x33, 0x12, 0x54, 0x78, 0x2e, 0x55, 0x82, 0xa2, 0x73, 0xd5, 0x8f, 0x2a, 0xc2, + 0x6c, 0x2c, 0xfa, 0x27, 0x56, 0x08, 0x57, 0x7c, 0xec, 0x17, 0x20, 0xff, 0x3d, 0xe2, 0xc3, + 0xfb, 0x9b, 0x31, 0x98, 0xff, 0xb3, 0x72, 0x42, 0x7a, 0x30, 0x42, 0x6f, 0xfa, 0x05, 0xb8, + 0x7c, 0xfa, 0xc6, 0xdd, 0xb7, 0x33, 0xec, 0x38, 0xdf, 0x67, 0x45, 0x5e, 0xdc, 0xd4, 0x19, + 0xb4, 0xbc, 0x59, 0x0b, 0xd7, 0xb4, 0x9f, 0xff, 0xe9, 0x7c, 0x1a, 0xda, 0xc7, 0x43, 0x14, + 0x89, 0xd1, 0xb2, 0x39, 0xbb, 0x4f, 0x4a, 0xf2, 0x49, 0x24, 0x6c, 0x18, 0xc6, 0x36, 0xfa, + 0x4e, 0x9c, 0x75, 0x9e, 0x68, 0xee, 0x54, 0x2e, 0x15, 0x4c, 0x9d, 0x98, 0x89, 0x90, 0xce, + 0x34, 0x17, 0xfb, 0x5d, 0xce, 0xd9, 0x95, 0x62, 0x02, 0x50, 0x4e, 0x85, 0xce, 0xdc, 0x76, + 0x25, 0xfd, 0xe9, 0x11, 0x97, 0x6a, 0xad, 0x19, 0x32, 0xf0, 0xfa, 0xd2, 0xce, 0xaa, 0xfc, + 0xbc, 0x46, 0x53, 0x09, 0x3a, 0x9a, 0x77, 0x74, 0x65, 0x9a, 0x9b, 0x2a, 0x01, 0x2b, 0x6b, + 0x09, 0xc4, 0xe7, 0x7d, 0x68, 0x41, 0xe0, 0x5f, 0xd3, 0x72, 0x51, 0x19, 0x8c, 0xcf, 0x8a, + 0xaf, 0x00, 0x30, 0x53, 0x33, 0xba, 0x31, 0x75, 0xa1, 0x84, 0x8c, 0x2f, 0x5c, 0xa0, 0xd8, + 0xf5, 0xe7, 0x5d, 0x85, 0x8c, 0xc8, 0x0e, 0x6f, 0x95, 0x69, 0xa3, 0x4a, 0x43, 0xb2, 0x7a, + 0xf9, 0xb9, 0xa8, 0x38, 0xcc, 0x66, 0x57, 0x44, 0x3e, 0x19, 0x0a, 0x38, 0x32, 0xc7, 0xe0, + 0x79, 0x75, 0x35, 0x43, 0xf6, 0x66, 0x79, 0xb3, 0xd0, 0x02, 0xa7, 0x46, 0x32, 0x5f, 0x29, + 0x75, 0xdb, 0x75, 0x7f, 0x7a, 0xaa, 0x9b, 0x7e, 0xb7, 0x3c, 0xc8, 0xae, 0x3a, 0x2d, 0x30, + 0x35, 0x22, 0x8e, 0x4f, 0x1d, 0x93, 0x25, 0x64, 0xb6, 0x72, 0xc4, 0x75, 0xdf, 0x4c, 0x71, + 0x7b, 0xde, 0xba, 0x6e, 0x6e, 0x83, 0xe1, 0x93, 0x8d, 0x58, 0x87, 0xdd, 0x2a, 0x47, 0xb4, + 0xe1, 0xd6, 0x8e, 0xf9, 0x3e, 0x3c, 0x62, 0x62, 0x1c, 0x99, 0x21, 0xf7, 0xa9, 0xb7, 0x07, + 0x2d, 0xf4, 0xd0, 0x62, 0xab, 0xb1, 0x1d, 0xb6, 0xa6, 0x0e, 0xbc, 0xc6, 0x18, 0x3f, 0xa7, + 0x44, 0xae, 0xd6, 0x4d, 0x37, 0x0a, 0x26, 0x1e, 0x6d, 0x26, 0xb2, 0xda, 0x80, 0x29, 0xd2, + 0xd2, 0xd2, 0x3c, 0xbe, 0x24, 0x3d, 0x06, 0x8a, 0x73, 0x21, 0x63, 0x3f, 0x36, 0xbd, 0xa6, + 0xc1, 0x74, 0x2b, 0x84, 0x52, 0x0c, 0xc5, 0x21, 0x95, 0xe6, 0xa8, 0x80, 0x15, 0x87, 0x5b, + 0x33, 0xf8, 0x15, 0x4b, 0xc8, 0x65, 0xdc, 0x89, 0x6c, 0x04, 0x76, 0xdc, 0x21, 0xc5, 0x77, + 0x7c, 0xbe, 0x32, 0xaa, 0x50, 0xb8, 0xc4, 0xe4, 0x6c, 0x69, 0x90, 0xc8, 0x10, 0x48, 0x3d, + 0xb9, 0x76, 0x7d, 0xfc, 0x1c, 0x40, 0x98, 0x20, 0x75, 0xc1, 0xd4, 0x33, 0xd6, 0x82, 0x88, + 0x3f, 0xed, 0xcc, 0x05, 0x43, 0x8f, 0x58, 0x97, 0x71, 0x5d, 0xd4, 0x38, 0x2f, 0x8e, 0xe6, + 0x0a, 0x96, 0xa3, 0xce, 0x47, 0x24, 0xa1, 0x0f, 0xb8, 0x38, 0x98, 0x79, 0x62, 0x6d, 0x56, + 0xf8, 0xf1, 0x0d, 0x4e, 0x50, 0x39, 0xe6, 0x06, 0xa2, 0xa5, 0xee, 0x84, 0x98, 0xd8, 0x22, + 0xfb, 0x1d, 0xbd, 0x71, 0x43, 0x42, 0xd8, 0xd8, 0xfb, 0xe9, 0xd3, 0xb2, 0x61, 0xd8, 0xd9, + 0xf1, 0xb5, + ], + // param2 = n ‖ n1q ‖ n2p (3 × 256 bytes) + param2: &[ + 0xdd, 0x86, 0x44, 0x52, 0xc9, 0xf7, 0x9f, 0x7e, 0xe9, 0xaf, 0x12, 0x1d, 0xa7, 0xa2, 0x18, + 0x07, 0x86, 0x61, 0x24, 0xee, 0x33, 0xfe, 0x80, 0x66, 0x38, 0x45, 0x91, 0x9c, 0x4c, 0xa3, + 0xd7, 0xbe, 0xaa, 0x28, 0xac, 0xbe, 0xe0, 0x75, 0x4e, 0x16, 0xc0, 0x8b, 0x4d, 0x2e, 0xaa, + 0xf1, 0x4b, 0x35, 0x78, 0x0b, 0xb5, 0x43, 0x05, 0x8c, 0xa3, 0x31, 0xc3, 0x55, 0x88, 0x86, + 0x5c, 0x52, 0x15, 0xfc, 0x9b, 0x3c, 0xd1, 0x51, 0x70, 0x1f, 0xdd, 0x7c, 0xb8, 0x59, 0xf7, + 0x16, 0x0e, 0x15, 0x4e, 0xac, 0x2b, 0x00, 0xeb, 0x94, 0x62, 0xe8, 0x6b, 0x2f, 0x7a, 0xcc, + 0x95, 0x07, 0x7c, 0xd9, 0xaa, 0x6c, 0xc7, 0x09, 0xfa, 0x94, 0x6f, 0xbf, 0x00, 0x59, 0xb1, + 0x0a, 0x6d, 0xef, 0x6d, 0x24, 0x3e, 0x22, 0x02, 0x01, 0x3d, 0xf8, 0x45, 0x2d, 0xc3, 0x74, + 0x0c, 0x0b, 0xb6, 0x68, 0x56, 0xe6, 0x52, 0x15, 0x46, 0x65, 0x69, 0xc6, 0x36, 0x1c, 0x56, + 0xc4, 0x27, 0x44, 0x8e, 0xee, 0xea, 0x85, 0xf5, 0x6e, 0x7a, 0x08, 0x29, 0x4a, 0x34, 0xe5, + 0xe8, 0x63, 0x39, 0x5e, 0x3e, 0xe6, 0x7d, 0xa4, 0xc3, 0x09, 0x61, 0x84, 0xd8, 0x20, 0x15, + 0x97, 0x20, 0x15, 0x27, 0x55, 0xbd, 0x1d, 0xe3, 0xd2, 0x12, 0xa5, 0xda, 0xfc, 0xd7, 0x7c, + 0xb7, 0x5b, 0x30, 0x40, 0x52, 0x36, 0x66, 0xd5, 0x59, 0xd1, 0xa5, 0xeb, 0x57, 0x8f, 0xd7, + 0x0b, 0xb9, 0xf3, 0x07, 0x27, 0xe7, 0x1d, 0xfa, 0x4b, 0xeb, 0x00, 0xe2, 0xca, 0xdc, 0x83, + 0xc6, 0xcf, 0xd2, 0x20, 0x8e, 0xf0, 0x48, 0x2f, 0x48, 0xa6, 0xc1, 0x3f, 0x17, 0xaa, 0x5f, + 0x86, 0x97, 0x39, 0xff, 0x91, 0xbd, 0xe7, 0x3a, 0x8c, 0x91, 0x59, 0x9d, 0x5b, 0x87, 0xc6, + 0xfd, 0x2d, 0x9c, 0x51, 0xc8, 0x98, 0xdf, 0x7e, 0xdf, 0xe1, 0x96, 0x95, 0x01, 0xdb, 0x1c, + 0xf9, 0x36, 0x73, 0xa3, 0xe2, 0x94, 0xcc, 0xc7, 0x7f, 0x76, 0x0d, 0xf9, 0xec, 0xaa, 0x19, + 0xc9, 0x54, 0xb6, 0x8b, 0x27, 0x27, 0xae, 0x8f, 0xfa, 0xe8, 0xa7, 0xf0, 0x85, 0xab, 0x16, + 0x79, 0x83, 0xf1, 0xfe, 0x04, 0x72, 0x4a, 0x47, 0x53, 0xe9, 0x03, 0xea, 0xaa, 0xc5, 0x0c, + 0x3a, 0x86, 0x8a, 0x0c, 0x80, 0x4d, 0xa7, 0xc2, 0x19, 0x6f, 0x7f, 0x11, 0xa7, 0xdc, 0xac, + 0xce, 0x14, 0x1c, 0x93, 0x54, 0x1f, 0xcf, 0xaa, 0x25, 0x69, 0xb1, 0x69, 0xf8, 0x52, 0x75, + 0xb6, 0xc9, 0x57, 0xfa, 0x2e, 0x79, 0x32, 0x1c, 0xb5, 0xfb, 0xb2, 0xdb, 0xc6, 0x49, 0xeb, + 0xc4, 0xf7, 0xfd, 0xfa, 0x8b, 0xa6, 0x16, 0x04, 0x12, 0xb7, 0x45, 0x66, 0xe4, 0x08, 0xcc, + 0xe3, 0xcb, 0xeb, 0x83, 0x9d, 0x30, 0x93, 0xba, 0x47, 0x60, 0xe2, 0x49, 0xc9, 0x00, 0x56, + 0x9f, 0x22, 0x63, 0xb1, 0xa8, 0x98, 0x0f, 0xd5, 0x7f, 0x60, 0x99, 0xbc, 0x4c, 0x32, 0xe6, + 0x0c, 0x55, 0x33, 0x04, 0x9e, 0xad, 0xc3, 0x09, 0x80, 0x70, 0x84, 0x4f, 0x38, 0x01, 0x12, + 0xf6, 0xaa, 0x89, 0x83, 0x21, 0xba, 0x9d, 0x3b, 0x15, 0x85, 0x9b, 0xc9, 0xa5, 0xfc, 0x57, + 0x9c, 0x18, 0x8c, 0xaa, 0xf2, 0xc7, 0x04, 0x81, 0x2e, 0x18, 0x16, 0x8c, 0xac, 0xd4, 0xa0, + 0x96, 0x4a, 0xfe, 0x29, 0xc4, 0x3c, 0x38, 0xc9, 0xc7, 0xc6, 0xa2, 0xcf, 0xa9, 0xc5, 0x09, + 0xde, 0xbf, 0x74, 0x52, 0x6e, 0xb2, 0x9e, 0x27, 0x2a, 0x1a, 0xfe, 0x5d, 0xd6, 0x6c, 0xa0, + 0x70, 0x12, 0x08, 0xdb, 0x5d, 0x92, 0x9c, 0x1e, 0xca, 0x9b, 0x91, 0x34, 0x1f, 0xbe, 0x2d, + 0x42, 0x8f, 0x1c, 0x97, 0xc4, 0x94, 0xb7, 0x87, 0xa4, 0x5a, 0x35, 0xa8, 0xd7, 0x70, 0x29, + 0x77, 0x12, 0x10, 0x7d, 0x94, 0xdc, 0xb4, 0x9f, 0xc4, 0x52, 0xbf, 0x11, 0xe1, 0x1b, 0x39, + 0x07, 0x1a, 0xa8, 0x13, 0xa1, 0x6f, 0x34, 0x2b, 0xd8, 0xfe, 0x72, 0xa2, 0x19, 0x30, 0xfc, + 0x88, 0x4f, 0xb2, 0xcf, 0xd5, 0xfc, 0xc6, 0x85, 0x6e, 0x86, 0x7d, 0x90, 0x54, 0x0b, 0xf1, + 0x35, 0x2a, 0x54, 0xcd, 0xab, 0x23, 0x3a, 0x74, 0x99, 0x22, 0x65, 0x12, 0xd6, 0xe0, 0x87, + 0x21, 0x70, 0x6b, 0xc1, 0x28, 0xf8, 0xbd, 0x0d, 0x81, 0xeb, 0x1c, 0x24, 0x20, 0x1c, 0x79, + 0xdb, 0xb7, 0x47, 0x36, 0x82, 0xa7, 0x7c, 0x6d, 0x26, 0x2c, 0x07, 0x6e, 0x73, 0x84, 0x65, + 0xe4, 0x40, 0x4d, 0xb6, 0x1a, 0x1f, 0x33, 0xf9, 0xe3, 0x35, 0x99, 0xaf, 0x0c, 0xa5, 0xe5, + 0x8e, 0x07, 0x9e, 0x09, 0x81, 0x4d, 0x04, 0x56, 0xc3, 0xf7, 0x42, 0x4f, 0x09, 0xdb, 0xf7, + 0x8c, 0xcd, 0x3e, 0x81, 0x6b, 0xd0, 0xf3, 0xaa, 0x67, 0xba, 0xa0, 0x5a, 0xae, 0x7c, 0x2c, + 0x6d, 0xd5, 0xe9, 0xa7, 0x04, 0xc0, 0xbd, 0xd6, 0x7d, 0x95, 0xe5, 0xcb, 0xac, 0x79, 0x04, + 0x36, 0x49, 0x6f, 0xf4, 0x3f, 0xf0, 0x40, 0x27, 0x7c, 0x75, 0xfe, 0xf5, 0xb8, 0xf0, 0x48, + 0x22, 0xef, 0x3d, 0xda, 0xb5, 0x3c, 0x84, 0x48, 0x42, 0x8f, 0x3e, 0x6e, 0x97, 0xde, 0xdb, + 0xc8, 0x78, 0x7e, 0x94, 0x6a, 0x34, 0x8d, 0xb8, 0x9c, 0xb4, 0xba, 0xfc, 0x18, 0x2e, 0x28, + 0x37, 0xe6, 0x6c, 0x5d, 0x06, 0x7c, 0x15, 0xfe, 0x9c, 0x0d, 0x93, 0x2e, 0xd6, 0x41, 0x92, + 0x85, 0xf9, 0x4b, 0x44, 0xa1, 0x99, 0x74, 0x48, 0xf6, 0xcf, 0x31, 0xed, 0xa2, 0x0b, 0x5e, + 0x3c, 0x13, 0xb4, 0xc7, 0xf7, 0xc2, 0xfb, 0x53, 0x2a, 0x65, 0xac, 0x14, 0x8d, 0x20, 0x59, + 0x7c, 0x1d, 0xf7, 0x7a, 0xa2, 0x3a, 0xfd, 0x05, 0x60, 0x96, 0x31, 0x5c, 0xb1, 0xc5, 0xea, + 0x5d, 0x4f, 0xeb, 0x1d, 0x1f, 0xbd, 0xeb, 0xe3, 0x3f, 0xba, 0x8c, 0x22, 0x85, 0xb4, 0xe5, + 0xa1, 0x15, 0xdf, + ], + c: &[ + 0x2a, 0xd3, 0x97, 0x6a, 0xe8, 0x42, 0x2e, 0x1a, 0x19, 0xe4, 0x4d, 0xa2, 0xb3, 0xf5, 0x63, + 0x2e, 0xc0, 0x19, 0x5a, 0x58, 0x46, 0xee, 0x82, 0x53, 0x06, 0x80, 0x28, 0x74, 0x37, 0x6e, + 0xd1, 0x54, 0x2a, 0x41, 0x46, 0x41, 0xc5, 0xf1, 0x25, 0xfa, 0x34, 0xf4, 0x80, 0x9d, 0x49, + 0x1d, 0x11, 0xb1, 0xb3, 0x22, 0xa6, 0x23, 0xe3, 0x92, 0x6f, 0x53, 0x61, 0xbe, 0x3d, 0xed, + 0xf6, 0x50, 0x17, 0xf5, 0x95, 0xf5, 0x5c, 0xa8, 0x6e, 0x27, 0x42, 0x69, 0x14, 0xc8, 0x64, + 0x14, 0x27, 0x95, 0xc9, 0xfa, 0x4d, 0xcb, 0xf9, 0xc5, 0x8c, 0x36, 0x28, 0x0c, 0x34, 0xdf, + 0x74, 0xfa, 0x38, 0x45, 0xc1, 0x73, 0xe0, 0xd9, 0x4d, 0xdc, 0xc4, 0xf3, 0x68, 0x05, 0x6e, + 0x1f, 0x90, 0x72, 0x93, 0x86, 0x67, 0xf8, 0xbc, 0xe5, 0xf6, 0x39, 0xd9, 0xfb, 0xfa, 0xc8, + 0x46, 0x6b, 0xf4, 0x8f, 0xec, 0xcb, 0x7e, 0x42, 0xaf, 0x6e, 0xee, 0x3d, 0xeb, 0xb8, 0x65, + 0xdd, 0x90, 0x91, 0x63, 0xd1, 0xd6, 0x68, 0xc1, 0x99, 0xbc, 0x0b, 0x73, 0xc1, 0xd2, 0x70, + 0xd9, 0x4b, 0x84, 0x26, 0xf0, 0x05, 0x8e, 0xe3, 0x11, 0x7a, 0xee, 0x9f, 0x33, 0xae, 0xcf, + 0xd5, 0xe7, 0xc3, 0x83, 0x15, 0x36, 0xcb, 0x4d, 0x3f, 0x56, 0xd7, 0x6b, 0xed, 0x19, 0x9b, + 0x53, 0xc6, 0xb3, 0xd7, 0xeb, 0x77, 0x44, 0x70, 0x02, 0xc2, 0x47, 0x97, 0x03, 0x88, 0x82, + 0x30, 0x0b, 0xac, 0xe3, 0xf3, 0x7d, 0x9c, 0x8b, 0xa9, 0x1b, 0x0d, 0x28, 0x65, 0xa7, 0x7f, + 0xbc, 0xc5, 0x31, 0x01, 0xc3, 0x4d, 0xe4, 0x7a, 0x18, 0x20, 0x10, 0x11, 0x7f, 0xcd, 0xf8, + 0x41, 0xbe, 0xab, 0xcf, 0x30, 0x75, 0x74, 0x89, 0x03, 0xec, 0xc1, 0x69, 0x35, 0x73, 0x15, + 0x0a, 0xd8, 0x80, 0xb7, 0x13, 0xa3, 0x52, 0x71, 0xdd, 0xd9, 0x41, 0x1d, 0x2d, 0x23, 0x63, + 0xc0, + ], + k: &[ + 0xdf, 0x26, 0xb1, 0x63, 0xc2, 0x78, 0x76, 0x7a, 0x79, 0x82, 0xa9, 0x89, 0x46, 0x2a, 0x38, + 0x65, 0x47, 0xb4, 0x9b, 0x0c, 0xe2, 0xa5, 0xe2, 0x9f, 0x71, 0xd9, 0xfe, 0x9c, 0x30, 0xe1, + 0x4e, 0x5e, 0x66, 0xa1, 0x6c, 0xd9, 0xd1, 0x28, 0x38, 0x90, 0x55, 0xd1, 0x23, 0xd9, 0xe7, + 0x9a, 0xbf, 0x54, 0xb5, 0xc2, 0x86, 0xa7, 0x9a, 0xa8, 0xe9, 0x7e, 0xc0, 0xe1, 0x9b, 0xc1, + 0x95, 0x9a, 0x1d, 0x15, 0xf1, 0x2f, 0x8c, 0x97, 0x87, 0x0b, 0xa9, 0xd6, 0x8c, 0xc1, 0x28, + 0x11, 0xa5, 0x6a, 0x3b, 0xb1, 0x14, 0x40, 0x61, 0x08, 0x25, 0x79, 0x6a, 0x49, 0xd4, 0x68, + 0xcd, 0xc9, 0xc2, 0xd0, 0x2d, 0x76, 0x59, 0x8a, 0x27, 0x97, 0x3d, 0x59, 0x60, 0xc5, 0xf5, + 0x0b, 0xce, 0x28, 0xd8, 0xd3, 0x45, 0xf4, 0x3c, 0xe5, 0x43, 0x25, 0xdb, 0x52, 0xfe, 0x79, + 0x88, 0x24, 0xae, 0xad, 0x11, 0xbb, 0x16, 0xfa, 0x76, 0x68, 0x57, 0xd0, 0x4a, 0x4a, 0xf7, + 0xd4, 0x68, 0x67, 0x2f, 0x16, 0xd9, 0x0e, 0x73, 0x96, 0x04, 0x6a, 0x46, 0xf8, 0x15, 0x69, + 0x3e, 0x85, 0xb1, 0xce, 0x54, 0x64, 0xda, 0x92, 0x70, 0x18, 0x1f, 0x82, 0x33, 0x3b, 0x07, + 0x15, 0x05, 0x7b, 0xbe, 0x8d, 0x61, 0xd4, 0x00, 0x50, 0x5f, 0x0e, 0x44, 0x87, 0x81, 0x78, + 0x2b, 0xf7, 0xc0, 0xe5, 0x2a, 0x1d, 0xd9, 0xe6, 0x75, 0x8f, 0xd3, 0x48, 0x2d, 0x90, 0xd3, + 0xcf, 0xcc, 0xf4, 0x22, 0x32, 0xcf, 0x35, 0x7e, 0x59, 0xa4, 0xd4, 0x9f, 0xd4, 0xd5, 0xf4, + 0x0c, 0x9e, 0x74, 0x33, 0x1e, 0x12, 0xc9, 0xcb, 0x53, 0x2c, 0x39, 0xe8, 0xd7, 0x02, 0x77, + 0x4a, 0x4f, 0x84, 0xf0, 0x1d, 0xe6, 0x72, 0x72, 0x16, 0x9c, 0x9d, 0x1e, 0xd1, 0xcd, 0x61, + 0x8f, 0x69, 0xff, 0x61, 0x49, 0x57, 0xef, 0x83, 0x66, 0x8e, 0xdc, 0x2d, 0x7e, 0xd6, 0x14, + 0xbf, + ], +};