diff --git a/fw/pal/traits/src/error.rs b/fw/pal/traits/src/error.rs index 2913fc736..ac5ec14a1 100644 --- a/fw/pal/traits/src/error.rs +++ b/fw/pal/traits/src/error.rs @@ -345,6 +345,7 @@ 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). @@ -430,6 +431,10 @@ pub enum HsmError { /// opted in via policy. SdPeerCloningNotAllowed = 0x0870010A, + /// A cryptographic algorithm self-test (CAST) produced output that + /// did not match its known-answer vector. + SelfTestKatMismatch = 0x0870010B, + // Firmware-internal diagnostic codes logged by the CPU fault and panic // exception handlers (`azihsm_fw_uno_fault`). These are not DDI protocol // statuses: they use the PAL diagnostic facility (`0x08F`) to stay clear of diff --git a/fw/plat/uno/fw/app/src/main.rs b/fw/plat/uno/fw/app/src/main.rs index 6327f2fc5..2858c7bb3 100644 --- a/fw/plat/uno/fw/app/src/main.rs +++ b/fw/plat/uno/fw/app/src/main.rs @@ -37,6 +37,10 @@ #![no_std] #![no_main] +// The `handle_io` Embassy task future is a deeply nested async state machine; +// with `trace-uart` enabled its layout computation exceeds rustc's default +// recursion limit of 128. Raise it to give headroom. +#![recursion_limit = "256"] mod trampoline; diff --git a/fw/plat/uno/fw/drivers/rng/src/api.rs b/fw/plat/uno/fw/drivers/rng/src/api.rs index 017ece003..39d5470c8 100644 --- a/fw/plat/uno/fw/drivers/rng/src/api.rs +++ b/fw/plat/uno/fw/drivers/rng/src/api.rs @@ -17,13 +17,18 @@ use azihsm_fw_hsm_pal_traits::DmaBuf; use azihsm_fw_static_ref::StaticRef; +use azihsm_fw_uno_error::HsmError; use azihsm_fw_uno_error::HsmResult; use azihsm_fw_uno_reg_soc::rng::regs::RngRegs; use azihsm_fw_uno_reg_soc::rng::APT_CUTOFF; use azihsm_fw_uno_reg_soc::rng::CHISQ_CUTOFF; use azihsm_fw_uno_reg_soc::rng::CLK_DIV_MSB; use azihsm_fw_uno_reg_soc::rng::CTRL; +use azihsm_fw_uno_reg_soc::rng::FWIN_DATA; +use azihsm_fw_uno_reg_soc::rng::FWOUT_DATA; +use azihsm_fw_uno_reg_soc::rng::GENERATE_INTERVAL; use azihsm_fw_uno_reg_soc::rng::REPCNT_CUTOFF; +use azihsm_fw_uno_reg_soc::rng::RESEED_INTERVAL; use azihsm_fw_uno_reg_soc::rng::RNG_BASE; use azihsm_fw_uno_reg_soc::rng::RN_DATA; use azihsm_fw_uno_reg_soc::rng::STATUS; @@ -53,6 +58,113 @@ const REGS: StaticRef = unsafe { StaticRef::new(RNG_BASE as *const RngR /// and an imprecise bus fault on the `CTRL` MMIO write. const RNG_INIT_DELAY_NOPS: u32 = 100_000 * 450 / 2; +/// Upper bound on wait-loop poll iterations in [`RngDriver::self_test`]. +/// +/// The reference firmware bounds each DRBG wait with a ~4 µs `Tcon::tsc()` +/// counter that Uno has no equivalent for. This poll-iteration budget is the +/// stand-in: generous enough never to trip during a healthy DRBG run (which +/// completes in microseconds), but finite so a stuck engine fails the CAST at +/// the boot gate instead of hanging forever. +const RNG_SELF_TEST_MAX_SPINS: u32 = 1_000_000; + +/// DRBG generate interval (in generate cycles) used during the FW-mode KAT. +const DRBG_GENERATE_INTERVAL: u32 = 2; + +/// DRBG reseed interval (in generate cycles) used during the FW-mode KAT. +const DRBG_RESEED_INTERVAL: u32 = 2; + +/// DRBG FW-mode self-test input vector (seed material). +/// +/// Ported byte-for-byte from the reference firmware +/// (`drivers/crypto/rng/src/rng.rs::RNG_SELF_TEST_INPUT`). +const RNG_SELF_TEST_INPUT: [u32; 16] = [ + 0x2cb8_5c71, + 0xdef8_49bf, + 0x5346_88e3, + 0x03bf_f6bd, + 0x9923_dfd1, + 0x28e0_a0d7, + 0xf38f_606c, + 0xcd88_cb0b, + 0xd41f_21da, + 0x16b2_d32f, + 0x041b_8db2, + 0xb5b5_2b5b, + 0x1700_01ef, + 0x602d_910b, + 0x5a17_e20f, + 0xf9a0_b0b9, +]; + +/// Expected DRBG FW-mode generate output for [`RNG_SELF_TEST_INPUT`]. +/// +/// Ported byte-for-byte from the reference firmware +/// (`RNG_SELF_TEST_EXPECTED_OUTPUT`). +const RNG_SELF_TEST_EXPECTED_OUTPUT: [u32; 16] = [ + 0x2df5_26c1, + 0x4ed2_fea1, + 0xe03e_4e33, + 0x773b_820d, + 0x5363_125e, + 0x5731_b848, + 0xf922_7325, + 0x8364_e0f5, + 0xc0fd_533e, + 0x1572_b04f, + 0x678f_4cdc, + 0xf989_cd2b, + 0x580a_18c2, + 0xe9d9_8573, + 0x4789_01b9, + 0xf6aa_61ed, +]; + +/// DRBG FW-mode self-test reseed input vector. +/// +/// Ported byte-for-byte from the reference firmware +/// (`RNG_SELF_TEST_RESEED_INPUT`). +const RNG_SELF_TEST_RESEED_INPUT: [u32; 16] = [ + 0x9fc0_ef1b, + 0x69bd_5cee, + 0x0536_d040, + 0x84e3_24ac, + 0xe803_252f, + 0x51f6_cb45, + 0x9f42_5836, + 0x85a1_550b, + 0x1ebf_fe37, + 0xcca0_724b, + 0x77ec_8fcc, + 0x1cd9_9781, + 0x0022_6942, + 0x3add_2d04, + 0xb677_7a10, + 0xac8f_0743, +]; + +/// Expected DRBG FW-mode reseed output for [`RNG_SELF_TEST_RESEED_INPUT`]. +/// +/// Ported byte-for-byte from the reference firmware +/// (`RNG_SELF_TEST_EXPECTED_RESEED_OUTPUT`). +const RNG_SELF_TEST_EXPECTED_RESEED_OUTPUT: [u32; 16] = [ + 0xa6fc_aa14, + 0xd9cb_3539, + 0x9ba9_b84e, + 0x9864_e8ff, + 0x079b_82c3, + 0x32d3_18f6, + 0x8fac_5900, + 0x73fd_9211, + 0xe2bd_a914, + 0xaafe_a144, + 0x523b_af42, + 0x97f2_049e, + 0x3221_4767, + 0xa1fd_de1c, + 0x141d_a708, + 0x14b6_5a29, +]; + /// TRBG / DRBG calibration parameters. /// /// [`RngCalibration::DEFAULT`] provides the silicon-specific values @@ -190,4 +302,176 @@ impl RngDriver { } } } + + /// DRBG FW-mode known-answer self-test (pre-operational CAST). + /// + /// Ported as-is from the reference firmware + /// (`drivers/crypto/rng/src/rng.rs::self_test`). Drives the DRBG through + /// its firmware-mode back door: seeds the FW input FIFO with a fixed + /// vector, runs instantiate + generate, and compares the FW output FIFO + /// against the expected known answer; then exercises the reseed path with + /// a second fixed vector. The generate/reseed intervals are saved on entry + /// and restored on every path. On the success path the full saved control + /// word is additionally written back; an early-exit (failure) path instead + /// returns the control word to normal DRBG mode by clearing the + /// FW-mode/instantiate/generate bits it set (matching the reference + /// firmware, which heads to a self-test error state on failure). Either + /// way the RNG is left in its normal operating mode. + /// + /// # Returns + /// * `Ok(())` if both the generate and reseed outputs match their vectors. + /// + /// # Errors + /// * [`HsmError::SelfTestKatMismatch`] on any output mismatch, DRBG fault, + /// or wait-loop timeout. + /// + /// # Timeout + /// The reference bounds each wait with a ~4 µs `Tcon::tsc()` counter that + /// Uno lacks; here each wait loop is bounded by [`RNG_SELF_TEST_MAX_SPINS`] + /// poll iterations instead. Exhausting the budget fails the test (never a + /// hang), so a stuck DRBG surfaces a clean CAST failure at the boot gate. + pub fn self_test(&self) -> HsmResult<()> { + // Before switching to FW mode, ensure the DRBG is not actively reading + // entropy — FW mode cannot be entered while an entropy read is in + // flight. + Self::spin_while(|| REGS.status.is_set(STATUS::ENTROPY_FIFO_READ))?; + + // Save the production register state so it can be restored on exit. + let saved_generate_interval = REGS.generate_interval.get(); + let saved_reseed_interval = REGS.reseed_interval.get(); + let saved_ctrl = REGS.ctrl.get(); + + // Shorten the generate/reseed intervals so the KAT exercises a reseed. + REGS.generate_interval + .write(GENERATE_INTERVAL::VALUE.val(DRBG_GENERATE_INTERVAL)); + REGS.reseed_interval + .write(RESEED_INTERVAL::VALUE.val(DRBG_RESEED_INTERVAL)); + + // Enter FW mode and kick off instantiate + generate. + REGS.ctrl.modify( + CTRL::ENABLE::SET + + CTRL::FW_MODE::SET + + CTRL::DRBG_INSTANTIATE::SET + + CTRL::DRBG_GENERATE::SET + + CTRL::DRBG_UNINSTANTIATE::CLEAR, + ); + + // Restore the RNG to normal mode; run on every early-exit path. + let cleanup = || { + REGS.ctrl.modify( + CTRL::DRBG_GENERATE::CLEAR + CTRL::DRBG_INSTANTIATE::CLEAR + CTRL::FW_MODE::CLEAR, + ); + REGS.generate_interval.set(saved_generate_interval); + REGS.reseed_interval.set(saved_reseed_interval); + }; + + // Fill the input FIFO; once full the DRBG starts processing it. + for word in RNG_SELF_TEST_INPUT { + REGS.fwin_data.write(FWIN_DATA::DATA.val(word)); + } + + Self::wait_for_drbg_instantiate().inspect_err(|_e| cleanup())?; + Self::wait_for_drbg_generate().inspect_err(|_e| cleanup())?; + Self::compare_output(&RNG_SELF_TEST_EXPECTED_OUTPUT).inspect_err(|_e| cleanup())?; + + // Reseed path: wait for the DRBG to request a reseed, feed it the + // reseed vector, then wait for it to complete. + Self::wait_till_status_is_set_to_busy().inspect_err(|_e| cleanup())?; + Self::wait_till_reseed_is_set().inspect_err(|_e| cleanup())?; + + for word in RNG_SELF_TEST_RESEED_INPUT { + REGS.fwin_data.write(FWIN_DATA::DATA.val(word)); + } + + Self::wait_for_reseed_clear().inspect_err(|_e| cleanup())?; + Self::wait_for_busy_status_clear().inspect_err(|_e| cleanup())?; + + // Compare the reseed output (the reference does not discard here). + for word in RNG_SELF_TEST_EXPECTED_RESEED_OUTPUT { + if REGS.fwout_data.read(FWOUT_DATA::DATA) != word { + cleanup(); + return Err(HsmError::SelfTestKatMismatch); + } + } + + // Restore the RNG to its normal operating mode. + REGS.generate_interval.set(saved_generate_interval); + REGS.reseed_interval.set(saved_reseed_interval); + REGS.ctrl.set(saved_ctrl); + REGS.ctrl.modify( + CTRL::FW_MODE::CLEAR + CTRL::DRBG_INSTANTIATE::CLEAR + CTRL::DRBG_GENERATE::CLEAR, + ); + + Ok(()) + } + + /// Poll `busy` until it returns `false`, bounded by + /// [`RNG_SELF_TEST_MAX_SPINS`]. Returns [`HsmError::SelfTestKatMismatch`] + /// if the budget is exhausted (the Uno stand-in for the reference's + /// `Tcon::tsc()` timeout). + fn spin_while bool>(busy: F) -> HsmResult<()> { + let mut budget = RNG_SELF_TEST_MAX_SPINS; + while busy() { + budget -= 1; + if budget == 0 { + return Err(HsmError::SelfTestKatMismatch); + } + } + Ok(()) + } + + /// Wait for the DRBG instantiate operation to finish, failing early on a + /// DRBG fault. + fn wait_for_drbg_instantiate() -> HsmResult<()> { + let mut budget = RNG_SELF_TEST_MAX_SPINS; + while REGS.status.is_set(STATUS::DRBG_INST_BUSY) { + if REGS.status.is_set(STATUS::DRBG_FAULT_ERROR) { + return Err(HsmError::SelfTestKatMismatch); + } + budget -= 1; + if budget == 0 { + return Err(HsmError::SelfTestKatMismatch); + } + } + Ok(()) + } + + /// Wait for the DRBG generate operation to complete (`STATUS.BUSY` clear). + fn wait_for_drbg_generate() -> HsmResult<()> { + Self::spin_while(|| REGS.status.is_set(STATUS::BUSY)) + } + + /// Wait for `STATUS.BUSY` to be set (a reseed request is pending). + fn wait_till_status_is_set_to_busy() -> HsmResult<()> { + Self::spin_while(|| !REGS.status.is_set(STATUS::BUSY)) + } + + /// Wait for the DRBG reseed-busy flag to be set. + fn wait_till_reseed_is_set() -> HsmResult<()> { + Self::spin_while(|| !REGS.status.is_set(STATUS::DRBG_RESEED_BUSY)) + } + + /// Wait for the DRBG reseed-busy flag to clear. + fn wait_for_reseed_clear() -> HsmResult<()> { + Self::spin_while(|| REGS.status.is_set(STATUS::DRBG_RESEED_BUSY)) + } + + /// Wait for `STATUS.BUSY` to clear. + fn wait_for_busy_status_clear() -> HsmResult<()> { + Self::spin_while(|| REGS.status.is_set(STATUS::BUSY)) + } + + /// Read and discard the first 16 FW-output words, then compare the next 16 + /// against `expected`. Mirrors the reference `compare_output`. + fn compare_output(expected: &[u32; 16]) -> HsmResult<()> { + for _ in 0..expected.len() { + let _ = REGS.fwout_data.read(FWOUT_DATA::DATA); + } + for &word in expected { + if REGS.fwout_data.read(FWOUT_DATA::DATA) != word { + return Err(HsmError::SelfTestKatMismatch); + } + } + Ok(()) + } } diff --git a/fw/plat/uno/fw/drivers/sha/src/api.rs b/fw/plat/uno/fw/drivers/sha/src/api.rs index 0339293f6..ede366139 100644 --- a/fw/plat/uno/fw/drivers/sha/src/api.rs +++ b/fw/plat/uno/fw/drivers/sha/src/api.rs @@ -622,8 +622,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/drivers/upka/src/engine.rs b/fw/plat/uno/fw/drivers/upka/src/engine.rs index a82f96c06..1878e39c7 100644 --- a/fw/plat/uno/fw/drivers/upka/src/engine.rs +++ b/fw/plat/uno/fw/drivers/upka/src/engine.rs @@ -387,7 +387,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 at least `2 * mod_size` bytes long; 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. /// @@ -404,16 +407,31 @@ 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 { + // Standard keys are a single `d ‖ n` blob (2·mod_size) read from + // arg2; the buffer must be long enough for the hardware's full + // fetch to stay in bounds. + (key.len() >= 2 * mod_size, 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 db6d32a7e..3bd94bf0f 100644 --- a/fw/plat/uno/fw/drivers/upka/src/opcode.rs +++ b/fw/plat/uno/fw/drivers/upka/src/opcode.rs @@ -341,6 +341,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 @@ -352,17 +369,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/alloc.rs b/fw/plat/uno/fw/pal/src/alloc.rs index 2f43d200e..9f0a39068 100644 --- a/fw/plat/uno/fw/pal/src/alloc.rs +++ b/fw/plat/uno/fw/pal/src/alloc.rs @@ -33,15 +33,30 @@ 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. +/// +/// Unlike host and admin slots, this slot is **DMA-only**: it has +/// `SRAM_IO_BUF` (Dma) backing but no `DTCM_IO_BUF` (NonDma) entry — +/// `DTCM_IO_BUF` covers only the 33 host+admin slots (see +/// `dtcm_map.rdl`). NonDma allocations against this slot are rejected +/// by the bump allocator so an accidental one cannot address past the +/// `DTCM_IO_BUF` region into the crashdump/status area. +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. @@ -196,6 +211,14 @@ fn bump( size: usize, align: usize, ) -> HsmResult<(usize, &'static mut [u8])> { + // The self-test slot is DMA-only: it has SRAM_IO_BUF (Dma) backing but no + // DTCM_IO_BUF (NonDma) entry. Reject NonDma allocations rather than + // computing a DTCM address past the buffer region, which would overlap the + // crashdump/status area. + if heap == NONDMA && io_index == SELF_TEST_IO_INDEX { + return Err(HsmError::InvalidArg); + } + let (base_ptr, cap) = heap_base_cap(io_index, heap); let w = wm(pal, io_index, heap); let mark = w.with(|v| *v).min(cap); diff --git a/fw/plat/uno/fw/pal/src/crypto/ecc_det.rs b/fw/plat/uno/fw/pal/src/crypto/ecc_det.rs index f47a67885..bc27f7ab5 100644 --- a/fw/plat/uno/fw/pal/src/crypto/ecc_det.rs +++ b/fw/plat/uno/fw/pal/src/crypto/ecc_det.rs @@ -261,6 +261,46 @@ impl UnoHsmPal { d: &DmaBuf, r: &mut DmaBuf, s: &mut DmaBuf, + ) -> HsmResult<()> { + self.sign_with_k_inner(io, curve, k, digest, d, r, s, None) + .await + } + + /// As [`Self::ecc_sign_with_k`], but pinned to a specific PKA `engine` + /// (0-based index) instead of any free engine. + /// + /// Used by the per-engine ECDSA self-test (CAST) to validate every UPKA + /// engine in turn; production callers use [`Self::ecc_sign_with_k`]. + #[allow(clippy::too_many_arguments)] + pub(crate) async fn ecc_sign_with_k_on_engine( + &self, + io: &impl HsmIo, + curve: UpkaEccCurve, + engine: u8, + k: &DmaBuf, + digest: &DmaBuf, + d: &DmaBuf, + r: &mut DmaBuf, + s: &mut DmaBuf, + ) -> HsmResult<()> { + self.sign_with_k_inner(io, curve, k, digest, d, r, s, Some(engine)) + .await + } + + /// Shared implementation of [`Self::ecc_sign_with_k`] / + /// [`Self::ecc_sign_with_k_on_engine`]: `engine` selects a specific PKA + /// engine (`Some`) or any free one (`None`). + #[allow(clippy::too_many_arguments)] + async fn sign_with_k_inner( + &self, + io: &impl HsmIo, + curve: UpkaEccCurve, + k: &DmaBuf, + digest: &DmaBuf, + d: &DmaBuf, + r: &mut DmaBuf, + s: &mut DmaBuf, + engine: Option, ) -> HsmResult<()> { // Implemented for P-384 only (the cert-chain PID leaf is signed with the // P-384 alias key). Other curves are rejected until their constants / @@ -365,11 +405,17 @@ impl UnoHsmPal { EccStep::new(EccStepOp::MontReprOut, a_s, a_s_plus_t, 0), ]; - // Drive the whole sequence on one held engine so the Montgomery - // constant set by each phase stays resident for the ops that follow. - let res = self - .pka - .with_engine(async |eng| { + // Drive the whole sequence on one held engine (a specific one for + // the per-engine self-test, otherwise any free engine) so the + // Montgomery constant set by each phase stays resident for the ops + // that follow. `release` performs the engine wipe, mirroring + // `with_engine` / `with_specific_engine`. + let res = async { + let mut eng = match engine { + Some(id) => self.pka.acquire_engine(id).await?, + None => self.pka.acquire_any().await?, + }; + let outcome = async { eng.ecc_run(curve, &prime_phase).await?; // Reduction is double-width: stage xR into the low half of the @@ -378,8 +424,14 @@ impl UnoHsmPal { xr_wide[..field].copy_from_slice(&xr[..field]); eng.ecc_run(curve, &order_phase).await - }) + } .await; + let release = eng.release().await; + outcome?; + release?; + Ok(()) + } + .await; // Reject a degenerate signature (`r == 0` or `s == 0`) after the full // sequence — RFC 6979 §3.2 requires advancing to the next candidate, diff --git a/fw/plat/uno/fw/pal/src/io.rs b/fw/plat/uno/fw/pal/src/io.rs index ad41199ab..058efb060 100644 --- a/fw/plat/uno/fw/pal/src/io.rs +++ b/fw/plat/uno/fw/pal/src/io.rs @@ -34,6 +34,7 @@ use azihsm_fw_hsm_pal_traits::HsmResult; use azihsm_fw_hsm_pal_traits::HsmSqe; use azihsm_fw_static_ref::StaticRef; use azihsm_fw_uno_reg_soc::io_gsram::IO_GSRAM_BASE; +use azihsm_fw_uno_reg_soc::io_gsram::IO_META_COUNT; use azihsm_fw_uno_reg_soc::io_gsram::IO_META_CTLR; use azihsm_fw_uno_reg_soc::io_gsram::IO_META_QUEUE; use azihsm_fw_uno_reg_soc::io_gsram::IoCqEntry; @@ -45,6 +46,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,7 +89,35 @@ 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). + /// + /// # Restrictions + /// + /// This handle is for DMA allocation only. Its index ([`SELF_TEST_IO_INDEX`]) is outside the + /// host queue ranges (`IO_SQ_COUNT`/`IO_CQ_COUNT`/`IO_META_COUNT`), so + /// [`sqe`](HsmIo::sqe) and [`cqe`](HsmIo::cqe) have no backing entry and must not be + /// called on it; the host-IO dispatch that reads them never runs for the self-test. + /// + /// [`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. + /// + /// Only valid for host and admin slots — `IO_META` has + /// [`IO_META_COUNT`] entries (host `0..32` plus [`ADMIN_IO_INDEX`]). + /// The self-test slot ([`SELF_TEST_IO_INDEX`]) has no `IO_META` entry; + /// callers must guard against it (see [`pid`](HsmIo::pid)). #[inline] fn io_meta(&self) -> &IoMetaEntry { &IO_Q.io_meta[self.index as usize] @@ -101,17 +131,36 @@ impl HsmIo for UnoHsmIo { } /// Returns the partition ID (controller_id from IO_META). + /// + /// The self-test slot ([`SELF_TEST_IO_INDEX`]) has no `IO_META` entry and + /// no partition context, so this returns partition `0` for it rather than + /// indexing `IO_META` out of bounds. fn pid(&self) -> HsmPartId { + if self.index as u32 >= IO_META_COUNT { + return HsmPartId::from(0u8); + } HsmPartId::from(self.io_meta().ctlr.read(IO_META_CTLR::CONTROLLER_ID) as u8) } /// Returns the queue ID from IO_META. + /// + /// Returns `0` for the self-test slot ([`SELF_TEST_IO_INDEX`]), which has + /// no `IO_META` entry. fn queue_id(&self) -> u16 { + if self.index as u32 >= IO_META_COUNT { + return 0; + } self.io_meta().queue.read(IO_META_QUEUE::QUEUE_ID) as u16 } /// Returns the queue index from IO_META. + /// + /// Returns `0` for the self-test slot ([`SELF_TEST_IO_INDEX`]), which has + /// no `IO_META` entry. fn queue_idx(&self) -> u16 { + if self.index as u32 >= IO_META_COUNT { + return 0; + } self.io_meta().queue.read(IO_META_QUEUE::QUEUE_INDEX) as u16 } diff --git a/fw/plat/uno/fw/pal/src/lib.rs b/fw/plat/uno/fw/pal/src/lib.rs index e13462e9c..467d4010c 100644 --- a/fw/plat/uno/fw/pal/src/lib.rs +++ b/fw/plat/uno/fw/pal/src/lib.rs @@ -47,6 +47,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 cd0ae716e..0b1e5abb9 100644 --- a/fw/plat/uno/fw/pal/src/pal.rs +++ b/fw/plat/uno/fw/pal/src/pal.rs @@ -563,6 +563,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..3dca97831 --- /dev/null +++ b/fw/plat/uno/fw/pal/src/self_test/aes_cbc.rs @@ -0,0 +1,78 @@ +// 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 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())?; + pal.aes_cbc_enc_dec(io, AesOp::Encrypt, &*key, &*pt, &*iv, &mut *ct_out, None) + .await?; + // 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); + } + + // ── 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())?; + pal.aes_cbc_enc_dec(io, AesOp::Decrypt, &*key, &*ct, &*iv, &mut *pt_out, None) + .await?; + if &pt_out[..] != v.plaintext { + error!( + "selftest", + HsmError::SelfTestKatMismatch, + "AES-CBC decrypt KAT mismatch" + ); + return Err(HsmError::SelfTestKatMismatch); + } + + Ok::<(), HsmError>(()) + }) + .await +} diff --git a/fw/plat/uno/fw/pal/src/self_test/aes_ecb.rs b/fw/plat/uno/fw/pal/src/self_test/aes_ecb.rs new file mode 100644 index 000000000..bda7f333b --- /dev/null +++ b/fw/plat/uno/fw/pal/src/self_test/aes_ecb.rs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! AES-256-ECB cryptographic algorithm self-test (CAST). +//! +//! Runs a fixed NIST SP 800-38A known-answer vector through the HSM AES +//! engine's ECB-decrypt path and compares the recovered plaintext against the +//! expected value. 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 super::vectors::AES_ECB_256_KAT; +use crate::UnoHsmIo; +use crate::UnoHsmPal; + +/// Runs the AES-256-ECB known-answer test (decrypt direction) against the HSM +/// AES engine. +/// +/// Decrypts the KAT ciphertext and verifies the recovered plaintext. Returns +/// [`HsmError::SelfTestKatMismatch`] on any mismatch (or any error surfaced by +/// the AES engine / allocator). +pub(super) async fn run_aes_ecb(pal: &UnoHsmPal, io: &UnoHsmIo) -> HsmResult<()> { + let v = &AES_ECB_256_KAT; + + pal.alloc_scoped_async(io, async |scope| { + // Stage the key and ciphertext into DMA-visible memory. + let key = scope.dma_alloc(v.key.len())?; + key.copy_from_slice(v.key); + let ct = scope.dma_alloc(v.ciphertext.len())?; + ct.copy_from_slice(v.ciphertext); + let pt_out = scope.dma_alloc_zeroed(v.plaintext.len())?; + + pal.aes_ecb_enc_dec(io, AesOp::Decrypt, &*key, &*ct, &mut *pt_out) + .await?; + + // KAT vectors are public, fixed test data — a plain slice comparison is + // correct; no constant-time compare is needed. + if &pt_out[..] != v.plaintext { + error!( + "selftest", + HsmError::SelfTestKatMismatch, + "AES-ECB decrypt KAT mismatch" + ); + return Err(HsmError::SelfTestKatMismatch); + } + + Ok::<(), HsmError>(()) + }) + .await +} diff --git a/fw/plat/uno/fw/pal/src/self_test/aes_kwp.rs b/fw/plat/uno/fw/pal/src/self_test/aes_kwp.rs new file mode 100644 index 000000000..928df8222 --- /dev/null +++ b/fw/plat/uno/fw/pal/src/self_test/aes_kwp.rs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! AES Key Wrap with Padding (RFC 5649) cryptographic algorithm self-test +//! (CAST). +//! +//! Runs a fixed NIST CAVP known-answer vector through the HSM AES engine's +//! KWP-AD (authenticated decryption / unwrap) path and compares the recovered +//! plaintext against the expected value. This validates the same engine path +//! the production RSA-unwrap flow depends on. 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::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 super::vectors::AES_KWP_256_KAT; +use crate::UnoHsmIo; +use crate::UnoHsmPal; + +/// Runs the AES-256 KWP (RFC 5649) known-answer test against the HSM AES +/// engine. +/// +/// Unwraps the KAT `wrapped` blob under the KAT `key` and verifies both the +/// recovered length (the Message Length Indicator) and the recovered plaintext +/// bytes. Returns [`HsmError::SelfTestKatMismatch`] on any mismatch (or any +/// error surfaced by the AES engine / allocator). +pub(super) async fn run_aes_kwp(pal: &UnoHsmPal, io: &UnoHsmIo) -> HsmResult<()> { + let v = &AES_KWP_256_KAT; + + pal.alloc_scoped_async(io, async |scope| { + // Stage the KEK and wrapped blob into DMA-visible memory. + let key = scope.dma_alloc(v.key.len())?; + key.copy_from_slice(v.key); + let input = scope.dma_alloc(v.wrapped.len())?; + input.copy_from_slice(v.wrapped); + + // Output must be at least `wrapped.len() - 8` (the AIV semiblock is + // stripped). For this vector that equals the plaintext length. + let out = scope.dma_alloc_zeroed(v.wrapped.len() - 8)?; + let mli = pal.aes_kwp_unwrap(io, &*key, &*input, &mut *out).await?; + + // KAT vectors are public, fixed test data — a plain slice comparison is + // correct; no constant-time compare is needed. + if mli != v.plaintext.len() || &out[..mli] != v.plaintext { + error!( + "selftest", + HsmError::SelfTestKatMismatch, + "AES-KWP unwrap KAT mismatch" + ); + return Err(HsmError::SelfTestKatMismatch); + } + + Ok::<(), HsmError>(()) + }) + .await +} 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 +} 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..75ef4fe40 --- /dev/null +++ b/fw/plat/uno/fw/pal/src/self_test/mod.rs @@ -0,0 +1,67 @@ +// 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 +//! [`UnoHsmIo::self_test`]. 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, AES-256-ECB, AES-256-KWP, HKDF, KBKDF, per-engine RSA-2048 +//! mod-exp (standard and CRT), ECDH-P384, ECDSA-P384, and the RNG/DRBG FW-mode +//! KAT are implemented. Further tests 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 aes_ecb; +mod aes_kwp; +mod kdf; +mod pka; +mod vectors; + +use azihsm_fw_hsm_pal_traits::HsmResult; + +use crate::UnoHsmIo; +use crate::UnoHsmPal; + +/// 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?; + aes_ecb::run_aes_ecb(pal, io).await?; + aes_kwp::run_aes_kwp(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?; + } + for engine in 0..pka::PKA_ENGINES { + pka::run_rsa_mod_exp_crt_on_engine(pal, io, engine).await?; + } + for engine in 0..pka::PKA_ENGINES { + pka::run_ecdh_on_engine(pal, io, engine).await?; + } + for engine in 0..pka::PKA_ENGINES { + pka::run_ecdsa_on_engine(pal, io, engine).await?; + } + // The DRBG FW-mode KAT is state-destructive; it saves and restores the + // production DRBG registers internally. Run it last (matching the reference + // `preops_cast` ordering) and before any live RNG consumer exists. + pal.rng.self_test()?; + 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..6b217a47a --- /dev/null +++ b/fw/plat/uno/fw/pal/src/self_test/pka.rs @@ -0,0 +1,366 @@ +// 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 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; +use azihsm_fw_hsm_pal_traits::HsmHash; +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_drivers_upka::UpkaEccCurve; +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::ECDH_384_KAT; +use super::vectors::ECDH_P384_PRIME_LE; +use super::vectors::ECDSA_384_SIGN_KAT; +use super::vectors::OAEP_KEK_SELF_TEST; +use super::vectors::RSA_2K_CRT_KAT; +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; + +/// NIST P-384 field element / shared-secret size in bytes. +const ECDH_384_LEN: usize = 48; + +/// NIST P-384 field-element size in bytes (point coordinate / scalar / digest). +const P384_FIELD_LEN: usize = 48; + +/// Runs the RSA-2048 mod-exp (private-key) known-answer test on PKA engine +/// `engine`, followed by the OAEP-decode (SHA-256) KEK check. +/// +/// Computes `c^d mod n` on the pinned engine and compares the result against +/// the expected plaintext, then OAEP-decodes that plaintext block and compares +/// the recovered KEK against the expected value (mirroring the reference +/// firmware's `rsa_mod_exp_self_test` + `decode_oaep_kek_self_test`). Returns +/// [`HsmError::SelfTestKatMismatch`] on a mismatch, or any error surfaced by +/// the PKA / SHA engine or the 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?; + + if &output[..] != v.k { + error!( + "selftest", + HsmError::SelfTestKatMismatch, + "RSA-2K mod-exp KAT mismatch on engine" + ); + return Err(HsmError::SelfTestKatMismatch); + } + + // ── OAEP-decode tail (SHA-256, empty label) ────────────────────────── + // Ported from the reference `decode_oaep_kek_self_test`: the mod-exp + // output block is little-endian, so it is reversed into the big-endian + // OAEP-encoded message `EM = 0x00 ‖ maskedSeed ‖ maskedDB`, then + // OAEP-decoded with the SHA/MGF1 primitives (identical steps to the + // production `rsa_oaep_decrypt`) and the recovered 16-byte KEK is + // compared against the expected value. This also exercises SHA-256 and + // MGF1 on the HSM SHA engine. + const H_LEN: usize = 32; // SHA-256 digest length. + let em = scope.dma_alloc(RSA_2K_LEN)?; + for (i, &b) in output[..RSA_2K_LEN].iter().enumerate() { + em[RSA_2K_LEN - 1 - i] = b; + } + + if em[0] != 0x00 { + error!( + "selftest", + HsmError::SelfTestKatMismatch, + "RSA-2K OAEP leading byte nonzero on engine" + ); + return Err(HsmError::SelfTestKatMismatch); + } + + // Recover seed: seed = maskedSeed XOR MGF(maskedDB, hLen). + { + let (seed, db) = em[1..RSA_2K_LEN].split_at_mut(H_LEN); + pal.mgf1_xor(io, HsmHashAlgo::Sha256, db, seed).await?; + } + // Recover DB: DB = maskedDB XOR MGF(seed, dbLen). + { + let (seed, db) = em[1..RSA_2K_LEN].split_at_mut(H_LEN); + pal.mgf1_xor(io, HsmHashAlgo::Sha256, seed, db).await?; + } + + // Verify lHash' == SHA-256(empty label). + let label_hash = scope.dma_alloc(H_LEN)?; + let empty = &input[..0]; + pal.hash(io, HsmHashAlgo::Sha256, empty, label_hash, true) + .await?; + let db = &em[1 + H_LEN..RSA_2K_LEN]; + let db_hash: &[u8] = &db[..H_LEN]; + let label_hash_slice: &[u8] = &label_hash[..H_LEN]; + if db_hash != label_hash_slice { + error!( + "selftest", + HsmError::SelfTestKatMismatch, + "RSA-2K OAEP lHash mismatch on engine" + ); + return Err(HsmError::SelfTestKatMismatch); + } + + // DB = lHash' ‖ PS(0x00…) ‖ 0x01 ‖ M — locate the 0x01 separator, then + // recover and check the message M against the expected KEK. + let ps_and_m = &db[H_LEN..]; + let sep = match ps_and_m.iter().position(|&x| x == 0x01) { + Some(s) if ps_and_m[..s].iter().all(|&x| x == 0x00) => s, + _ => { + error!( + "selftest", + HsmError::SelfTestKatMismatch, + "RSA-2K OAEP separator invalid on engine" + ); + return Err(HsmError::SelfTestKatMismatch); + } + }; + let kek: &[u8] = &db[H_LEN + sep + 1..]; + if kek != OAEP_KEK_SELF_TEST { + error!( + "selftest", + HsmError::SelfTestKatMismatch, + "RSA-2K OAEP KEK mismatch on engine" + ); + return Err(HsmError::SelfTestKatMismatch); + } + + Ok::<(), HsmError>(()) + }) + .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 +} + +/// Runs the ECDH P-384 (ECC CDH primitive) known-answer test on PKA engine +/// `engine`. +/// +/// Ported from the reference firmware's `ecdh_self_test`. Derives the shared +/// secret `z = d_iut * Q_cavs` via a single point multiplication on the pinned +/// engine (the driver performs the required per-call Montgomery-constant setup +/// from the curve prime) and compares against the expected `z_iut`. +/// +/// # Endianness +/// +/// The peer public key (`qcavs_x ‖ qcavs_y`), private scalar (`d_iut`), and +/// curve prime are little-endian, matching the PKA operand order, and are fed +/// verbatim. The engine emits the shared secret little-endian; the expected +/// `z_iut` is big-endian, so the little-endian engine output is reversed before +/// comparison (mirroring the production `ecdh_derive`). +/// +/// Returns [`HsmError::SelfTestKatMismatch`] on a mismatch, or any error +/// surfaced by the PKA engine / allocator. +pub(super) async fn run_ecdh_on_engine( + pal: &UnoHsmPal, + io: &UnoHsmIo, + engine: u8, +) -> HsmResult<()> { + let v = &ECDH_384_KAT; + + pal.alloc_scoped_async(io, async |scope| { + // Peer public key as a contiguous `X ‖ Y` block (little-endian). + let pub_key = scope.dma_alloc(ECDH_384_LEN * 2)?; + pub_key[..ECDH_384_LEN].copy_from_slice(v.qcavs_x); + pub_key[ECDH_384_LEN..].copy_from_slice(v.qcavs_y); + // Private scalar (little-endian). + let priv_key = scope.dma_alloc(ECDH_384_LEN)?; + priv_key.copy_from_slice(v.d_iut); + // Curve prime and Montgomery-constant scratch for the point-multiply. + let prime = scope.dma_alloc(ECDH_384_LEN)?; + prime.copy_from_slice(ECDH_P384_PRIME_LE); + let mont_result = scope.dma_alloc(ECDH_384_LEN)?; + // Derived shared-secret output (little-endian). + let secret = scope.dma_alloc_zeroed(ECDH_384_LEN)?; + // Status word for the hardware on-curve validation the driver runs + // between the Montgomery-constant setup and the ECDH compute. + let point_valid = scope.dma_alloc(4)?; + + // Pin the requested engine for the operation, release afterwards. + let mut eng = pal.pka.acquire_engine(engine).await?; + let outcome = eng + .ecdh_derive( + UpkaEccCurve::P384, + &*priv_key, + &*pub_key, + &mut *secret, + &*prime, + &mut *mont_result, + &mut *point_valid, + ) + .await; + let release = eng.release().await; + outcome?; + release?; + + // The engine writes the secret little-endian; `z_iut` is big-endian. + // Compare the reversed output against the expected big-endian value. + let matches = secret[..ECDH_384_LEN].iter().rev().eq(v.z_iut.iter()); + if !matches { + error!( + "selftest", + HsmError::SelfTestKatMismatch, + "ECDH P-384 KAT mismatch on engine" + ); + return Err(HsmError::SelfTestKatMismatch); + } + + Ok::<(), HsmError>(()) + }) + .await +} + +/// Runs the ECDSA P-384 deterministic sign known-answer test on PKA engine +/// `engine`. +/// +/// Ported as-is from the reference firmware's `ecdsa_self_test_internal`: a +/// fixed-nonce ECDSA sign KAT that reproduces the signature `(r, s)` for a +/// fixed private key `d`, nonce `k`, and message digest `e`, then compares it +/// against the reference vectors. The signature is produced by the shared +/// driver primitive [`UnoHsmPal::ecc_sign_with_k_on_engine`], pinned to +/// `engine`, so the self-test and the production PID-cert signer exercise the +/// identical raw-primitive Montgomery sequence. A fixed nonce (rather than the +/// random-nonce `ecc_sign`) is required so the KAT is deterministic and matches +/// the FIPS submission exactly. +/// +/// # Endianness +/// +/// All operands and vectors are little-endian (PKA-native) and fed verbatim; +/// the resulting `r` / `s` are compared against the little-endian expected +/// values with no reversal. +/// +/// Returns [`HsmError::SelfTestKatMismatch`] on a mismatch, or any error +/// surfaced by the PKA engine / allocator. +pub(super) async fn run_ecdsa_on_engine( + pal: &UnoHsmPal, + io: &UnoHsmIo, + engine: u8, +) -> HsmResult<()> { + let v = &ECDSA_384_SIGN_KAT; + + pal.alloc_scoped_async(io, async |scope| { + // Fixed nonce `k`, message digest `e`, and private key `d` + // (little-endian) from the KAT vector; `r` / `s` receive the signature. + let k = scope.dma_alloc(P384_FIELD_LEN)?; + k.copy_from_slice(v.k); + let digest = scope.dma_alloc(P384_FIELD_LEN)?; + digest.copy_from_slice(v.digest); + let priv_key = scope.dma_alloc(P384_FIELD_LEN)?; + priv_key.copy_from_slice(v.private_key); + let r = scope.dma_alloc_zeroed(P384_FIELD_LEN)?; + let s = scope.dma_alloc_zeroed(P384_FIELD_LEN)?; + + // Deterministic fixed-nonce sign on the pinned engine, reusing the + // shared driver primitive so the self-test and the production PID-cert + // signer exercise the identical Montgomery sequence. + pal.ecc_sign_with_k_on_engine(io, UpkaEccCurve::P384, engine, k, digest, priv_key, r, s) + .await?; + + // Compare (r, s) against the expected signature (little-endian, no + // reversal). + if &r[..] != v.r || &s[..] != v.s { + error!( + "selftest", + HsmError::SelfTestKatMismatch, + "ECDSA P-384 sign 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 new file mode 100644 index 000000000..d324fd4c7 --- /dev/null +++ b/fw/plat/uno/fw/pal/src/self_test/vectors.rs @@ -0,0 +1,655 @@ +// 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, + ], +}; + +/// AES Key Wrap with Padding (RFC 5649) known-answer vector. +/// +/// Holds a KEK, the wrapped key data, and the expected recovered plaintext so +/// the self-test can exercise the KWP-AD (authenticated decryption / unwrap) +/// path that the production RSA-unwrap flow relies on. +pub(super) struct AesKwpKat { + /// Key-encryption key (16 / 24 / 32 bytes). + pub key: &'static [u8], + + /// Wrapped key data (AIV ‖ padded plaintext; ≥ 16 bytes, multiple of 8). + pub wrapped: &'static [u8], + + /// Expected recovered plaintext (the Message Length Indicator bytes). + pub plaintext: &'static [u8], +} + +/// AES-256 Key Wrap with Padding known-answer test vector (KWP-AD). +/// +/// Source: NIST CAVP `kwtestvectors` (`KWP_AD_256`, PLAINTEXT LENGTH = 64, +/// COUNT = 0). Unwrapping the 16-byte `wrapped` blob under `key` must recover +/// the 8-byte `plaintext`. +pub(super) const AES_KWP_256_KAT: AesKwpKat = AesKwpKat { + key: &[ + 0x35, 0x17, 0xf0, 0xef, 0xa7, 0xf0, 0xc4, 0xd7, 0x4f, 0x91, 0xaf, 0x83, 0xec, 0xe5, 0xe7, + 0x50, 0x3b, 0xcc, 0x5a, 0xb8, 0x29, 0x07, 0xa6, 0xe4, 0xb7, 0xed, 0x34, 0xd8, 0x7b, 0x69, + 0xab, 0x1d, + ], + wrapped: &[ + 0x0b, 0x06, 0xa9, 0xb6, 0x35, 0xd5, 0x0c, 0xda, 0x9d, 0x42, 0x10, 0xcb, 0x3a, 0x71, 0xf9, + 0x90, + ], + plaintext: &[0x89, 0x7e, 0x04, 0x56, 0xb2, 0x89, 0xad, 0x31], +}; + +/// AES-ECB known-answer vector. +/// +/// Holds a ciphertext/plaintext pair so the self-test can exercise the +/// single-block ECB-decrypt path (used by software AES unwrap paths). +pub(super) struct AesEcbKat { + /// Cipher key (16 / 24 / 32 bytes). + pub key: &'static [u8], + + /// Ciphertext (block multiple). + pub ciphertext: &'static [u8], + + /// Expected plaintext for `ciphertext` under `key`. + pub plaintext: &'static [u8], +} + +/// AES-256-ECB known-answer test vector (decrypt direction). +/// +/// Source: NIST SP 800-38A, F.1.6 (ECB-AES256.Decrypt) — all four blocks. +/// Shares the AES-256 key with [`AES_CBC_256_KAT`]. +pub(super) const AES_ECB_256_KAT: AesEcbKat = AesEcbKat { + 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, + ], + ciphertext: &[ + 0xf3, 0xee, 0xd1, 0xbd, 0xb5, 0xd2, 0xa0, 0x3c, 0x06, 0x4b, 0x5a, 0x7e, 0x3d, 0xb1, 0x81, + 0xf8, 0x59, 0x1c, 0xcb, 0x10, 0xd4, 0x10, 0xed, 0x26, 0xdc, 0x5b, 0xa7, 0x4a, 0x31, 0x36, + 0x28, 0x70, 0xb6, 0xed, 0x21, 0xb9, 0x9c, 0xa6, 0xf4, 0xf9, 0xf1, 0x53, 0xe7, 0xb1, 0xbe, + 0xaf, 0xed, 0x1d, 0x23, 0x30, 0x4b, 0x7a, 0x39, 0xf9, 0xf3, 0xff, 0x06, 0x7d, 0x8d, 0x8f, + 0x9e, 0x24, 0xec, 0xc7, + ], + 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, + ], +}; + +/// 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, + ], +}; + +/// 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, + ], +}; + +/// 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, + ], +}; + +/// Expected KEK recovered by OAEP-decoding (SHA-256, empty label) the RSA-2048 +/// mod-exp plaintext block [`RSA_2K_MOD_EXP_KAT`].k. +/// +/// This is the tail of the reference firmware's RSA mod-exp self-test: after the +/// PKA mod-exp produces the padded plaintext, the SHA engine OAEP-decodes it and +/// the recovered key-encryption key is compared against this constant. Byte-for- +/// byte identical to the reference firmware's `OAEP_KEK_SELF_TEST` +/// (`drivers/crypto/sha/src/sha.rs`). Natural (big-endian) byte order — it is +/// extracted from the big-endian OAEP-encoded message. +pub(super) const OAEP_KEK_SELF_TEST: &[u8] = &[ + 0xf0, 0x81, 0x50, 0x2a, 0xe5, 0x33, 0x54, 0x59, 0x95, 0xdf, 0x87, 0xc4, 0x2a, 0x19, 0x20, 0x21, +]; + +/// 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, + ], +}; + +/// NIST P-384 curve prime modulus in PKA little-endian operand order. +/// +/// Required by the ECDH point-multiply (the per-call Montgomery-constant setup +/// reads it). Identical to `crypto::ecc::PRIME384_LE`; duplicated here to keep +/// the self-test module self-contained (the constant is the immutable NIST +/// P-384 prime and cannot drift). +pub(super) const ECDH_P384_PRIME_LE: &[u8] = &[ + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +]; + +/// ECDH P-384 (ECC CDH primitive) known-answer test vector. +/// +/// Ported from the reference firmware's `KAT_ECDH_TEST_384_TEST_VECTORS` +/// (ECCCDH primitive test vectors, NIST CAVP component testing). The shared +/// secret is computed as `z = d_iut * Q_cavs` (a single point multiplication) +/// and compared against `z_iut`. +/// +/// # Endianness +/// +/// The peer public-key coordinates (`qcavs_x`, `qcavs_y`) and the private +/// scalar (`d_iut`) are **little-endian** — the PKA engine's native operand +/// order — and are fed to the driver verbatim. The expected shared secret +/// `z_iut` is **big-endian**, byte-for-byte identical to the reference vector +/// (which stores it in vault order). The PKA point-multiply emits the secret +/// little-endian, so the runner reverses the engine output to big-endian before +/// comparing (mirroring the production `ecdh_derive`). +pub(super) struct Ecdh384Kat { + /// Peer public-key X coordinate (48 bytes, little-endian). + pub qcavs_x: &'static [u8], + + /// Peer public-key Y coordinate (48 bytes, little-endian). + pub qcavs_y: &'static [u8], + + /// Local private scalar (48 bytes, little-endian). + pub d_iut: &'static [u8], + + /// Expected shared secret (48 bytes, big-endian). + pub z_iut: &'static [u8], +} + +/// ECDH P-384 known-answer test vector. +/// +/// Byte-for-byte identical to the reference firmware's +/// `KAT_ECDH_TEST_384_TEST_VECTORS` (first 48 bytes of each field; the reference +/// arrays are padded to the P-521 maximum length). +pub(super) const ECDH_384_KAT: Ecdh384Kat = Ecdh384Kat { + qcavs_x: &[ + 0x66, 0x00, 0xc5, 0xe0, 0x58, 0x8c, 0xd6, 0xf2, 0xdb, 0x1a, 0x09, 0x00, 0xb4, 0x66, 0x44, + 0x73, 0x72, 0x32, 0x51, 0xe7, 0x7f, 0xa2, 0xfd, 0x2e, 0x59, 0x64, 0xe7, 0x52, 0xaf, 0x9e, + 0x7b, 0x69, 0x47, 0xab, 0x04, 0xae, 0x38, 0x28, 0x5d, 0xb0, 0xe8, 0x5f, 0x3b, 0x0c, 0x97, + 0x6b, 0xc7, 0xa7, + ], + qcavs_y: &[ + 0x7a, 0x43, 0xc8, 0x6f, 0x76, 0x64, 0x8c, 0x46, 0x2b, 0x99, 0x60, 0xb0, 0x32, 0x5a, 0x90, + 0xd0, 0x5e, 0x91, 0x51, 0x34, 0x24, 0xdf, 0xfe, 0x1e, 0x66, 0x9b, 0x74, 0x49, 0x8a, 0xc3, + 0xc4, 0x39, 0x08, 0x1a, 0xb9, 0x69, 0x99, 0x3a, 0xd4, 0xae, 0x79, 0xb8, 0x1c, 0x2e, 0x9f, + 0xf1, 0x68, 0xac, + ], + d_iut: &[ + 0xa1, 0x98, 0xcf, 0xc9, 0x43, 0x4d, 0xab, 0x99, 0xf6, 0x8c, 0xa8, 0x5d, 0x20, 0x3b, 0x46, + 0xad, 0x74, 0x17, 0x66, 0x8a, 0x81, 0xb6, 0x18, 0x56, 0xb1, 0xe1, 0x22, 0x4d, 0x89, 0x38, + 0x8c, 0xeb, 0xa0, 0x6b, 0x91, 0x67, 0xc0, 0x38, 0xad, 0x27, 0x50, 0xd9, 0xf0, 0x68, 0x2a, + 0x12, 0xc3, 0x3c, + ], + z_iut: &[ + 0x5f, 0x9d, 0x29, 0xdc, 0x5e, 0x31, 0xa1, 0x63, 0x06, 0x03, 0x56, 0x21, 0x36, 0x69, 0xc8, + 0xce, 0x13, 0x2e, 0x22, 0xf5, 0x7c, 0x9a, 0x04, 0xf4, 0x0b, 0xa7, 0xfc, 0xea, 0xd4, 0x93, + 0xb4, 0x57, 0xe5, 0x62, 0x1e, 0x76, 0x6c, 0x40, 0xa2, 0xe3, 0xd4, 0xd6, 0xa0, 0x4b, 0x25, + 0xe5, 0x33, 0xf1, + ], +}; + +/// ECDSA P-384 (SHA-256 component) deterministic sign known-answer test vector. +/// +/// Ported verbatim from the reference firmware's `KAT_ECC_TEST_384_TEST_VECTORS` +/// (NIST CAVP 186-3 ECDSA SigGen component test vectors, `[P-384,SHA-256]`, +/// first 48 bytes of each field; the reference arrays are padded to the P-521 +/// maximum length). The self-test replays the reference's raw-primitive sign +/// sequence with the **fixed** nonce `k` and compares the computed `(r, s)` +/// against these expected values. +/// +/// # Endianness +/// +/// All fields are **little-endian** — the PKA engine's native operand order — +/// and are fed to the driver verbatim. The computed `r` and `s` are emitted by +/// the engine little-endian and compared against `r`/`s` directly (no reversal). +pub(super) struct Ecdsa384Kat { + /// Message digest `e` (48 bytes, little-endian). + pub digest: &'static [u8], + + /// Fixed per-signature nonce `k` (48 bytes, little-endian). + pub k: &'static [u8], + + /// Signing private key `d` (48 bytes, little-endian). + pub private_key: &'static [u8], + + /// Expected signature component `r` (48 bytes, little-endian). + pub r: &'static [u8], + + /// Expected signature component `s` (48 bytes, little-endian). + pub s: &'static [u8], +} + +/// ECDSA P-384 deterministic sign known-answer test vector (see [`Ecdsa384Kat`]). +pub(super) const ECDSA_384_SIGN_KAT: Ecdsa384Kat = Ecdsa384Kat { + digest: &[ + 0xd1, 0x3e, 0x7f, 0x7d, 0x01, 0xcd, 0x18, 0x3e, 0x83, 0xc2, 0xfb, 0xe0, 0x00, 0xff, 0x9d, + 0x5f, 0x45, 0x99, 0xb2, 0x72, 0xd1, 0x88, 0xe2, 0x10, 0xda, 0x3f, 0x5d, 0x64, 0x5f, 0x0a, + 0xbd, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + ], + k: &[ + 0x43, 0x24, 0xa3, 0xf3, 0x24, 0x7e, 0x87, 0x04, 0xa8, 0xd4, 0xea, 0x36, 0x3d, 0xcd, 0x0f, + 0xb3, 0xcc, 0x57, 0xc7, 0x6c, 0xaf, 0xe7, 0xd1, 0x0d, 0x8c, 0x79, 0x9b, 0x29, 0xb5, 0x96, + 0x24, 0x93, 0xc0, 0xcd, 0x97, 0x86, 0xd8, 0xd0, 0x27, 0x78, 0x0b, 0x3d, 0x68, 0xc4, 0x25, + 0x5c, 0x0b, 0xc1, + ], + private_key: &[ + 0x6b, 0x9e, 0x7f, 0x6d, 0x47, 0x87, 0xc9, 0x83, 0x77, 0x5a, 0x85, 0x9b, 0xd9, 0xf0, 0x52, + 0x4b, 0x18, 0x30, 0x26, 0x16, 0x58, 0xf0, 0x89, 0x2a, 0xc4, 0x6c, 0x67, 0x74, 0x72, 0x20, + 0xf7, 0x84, 0x2c, 0x83, 0xe0, 0x61, 0x96, 0x56, 0xa6, 0x11, 0xc3, 0x92, 0x45, 0xa3, 0x74, + 0xbc, 0x02, 0xc6, + ], + r: &[ + 0x78, 0xe8, 0x40, 0xd4, 0x46, 0x79, 0x26, 0xc3, 0x2e, 0xaa, 0x88, 0x17, 0x85, 0x61, 0x8c, + 0x97, 0x59, 0x03, 0xab, 0xa0, 0x1d, 0x55, 0x54, 0x90, 0x60, 0xad, 0xc2, 0xeb, 0xd7, 0x7e, + 0x47, 0x48, 0x59, 0x78, 0x02, 0xcd, 0x38, 0x3f, 0x48, 0xd4, 0x86, 0x32, 0xf5, 0xda, 0x0c, + 0xb0, 0x1d, 0xb1, + ], + s: &[ + 0xf2, 0xcb, 0xf8, 0x7d, 0x02, 0xac, 0x48, 0x10, 0x67, 0x87, 0x78, 0x4b, 0xe7, 0x27, 0x8b, + 0xc9, 0xd7, 0x12, 0x65, 0x07, 0x5a, 0x06, 0xf5, 0x2f, 0x76, 0x3a, 0x68, 0x9c, 0x31, 0xe3, + 0xb6, 0xe2, 0xe8, 0x73, 0xe9, 0xfe, 0xa8, 0x12, 0x81, 0xe6, 0x4c, 0x60, 0xb0, 0xc5, 0x73, + 0x78, 0x00, 0x16, + ], +}; 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 bafc706b5..f16e44b97 100644 --- a/fw/plat/uno/fw/reg/soc/src/io_gsram.rs +++ b/fw/plat/uno/fw/reg/soc/src/io_gsram.rs @@ -66,7 +66,7 @@ pub const HSM_SEED_TABLE_COUNT: u32 = 2; pub const HSM_SEED_TABLE_STRIDE: u32 = 0x30; pub const HSM_SEED_TABLE_SIZE: u32 = 0x30; 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; @@ -347,8 +347,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 12157ff5b..912014b6f 100644 --- a/fw/plat/uno/rdl/soc/dtcm_map.rdl +++ b/fw/plat/uno/rdl/soc/dtcm_map.rdl @@ -78,6 +78,9 @@ addrmap hsm_dtcm { default sw = rw; // Per-IO NonDma scratch (33 × 1.5 KB = 49.5 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. // Ends at 0x3B200, below the DEV_ID_CERT_BLOB region. dtcm_io_buf_t DTCM_IO_BUF[33] @ 0x2EC00 += 0x600; diff --git a/fw/plat/uno/rdl/soc/gsram_map.rdl b/fw/plat/uno/rdl/soc/gsram_map.rdl index 96ef00c21..dce10c58e 100644 --- a/fw/plat/uno/rdl/soc/gsram_map.rdl +++ b/fw/plat/uno/rdl/soc/gsram_map.rdl @@ -569,9 +569,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;