Repository: chipsalliance/caliptra-sw
Pull request #578 (2023-08-02 16:22:36)

Wycheproof: hmac
This implements:
- A test mailbox command to verify HMAC
- Running hmac sha384 tests over the test HMAC mailbox



jhand2: From the CI run

```
error: function `handle_hmac_verify` is never used
  --> runtime/src/verify.rs:56:15
   |
56 | pub(crate) fn handle_hmac_verify(drivers: &mut Drivers, cmd_args: &[u8]) -> CaliptraResult<()> {
   |               ^^^^^^^^^^^^^^^^^^
```

To mimic the CI locally, I usually run this one-liner: `cargo clippy --all-targets -- -D warnings && cargo test && cargo fmt`
jhand2: Sorry for the delay in reviewing this. When you fix the merge conflict feel free to message me on slack and I'll try to merge it asap before any more conflicts arise :)

Patch:
diff --git a/error/src/lib.rs b/error/src/lib.rs
index 83626e8d..08248efd 100644
--- a/error/src/lib.rs
+++ b/error/src/lib.rs
@@ -288,6 +288,7 @@ impl CaliptraError {
     pub const RUNTIME_GLOBAL_NMI: CaliptraError = CaliptraError::new_const(0x000E000B);
     pub const RUNTIME_GLOBAL_EXCEPTION: CaliptraError = CaliptraError::new_const(0x000E000C);
     pub const RUNTIME_GLOBAL_PANIC: CaliptraError = CaliptraError::new_const(0x000E000D);
+    pub const RUNTIME_HMAC_VERIFY_FAILED: CaliptraError = CaliptraError::new_const(0x000E000E);
 
     /// FMC Errors
     pub const FMC_GLOBAL_NMI: CaliptraError = CaliptraError::new_const(0x000F0001);
diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs
index 0d5a3896..5f5a77a4 100644
--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -15,8 +15,8 @@ use mailbox::Mailbox;
 pub mod mailbox_api;
 pub use mailbox_api::{
     CommandId, EcdsaVerifyReq, FipsVersionResp, FwInfoResp, GetIdevCsrResp, GetLdevCertResp,
-    InvokeDpeCommandReq, InvokeDpeCommandResp, MailboxReqHeader, MailboxResp, MailboxRespHeader,
-    StashMeasurementReq, StashMeasurementResp, TestGetFmcAliasCertResp,
+    HmacVerifyReq, InvokeDpeCommandReq, InvokeDpeCommandResp, MailboxReqHeader, MailboxResp,
+    MailboxRespHeader, StashMeasurementReq, StashMeasurementResp, TestGetFmcAliasCertResp,
 };
 
 #[cfg(feature = "test_only_commands")]
@@ -44,6 +44,9 @@ use caliptra_registers::{
 };
 use zerocopy::{AsBytes, FromBytes};
 
+#[cfg(feature = "test_only_commands")]
+use crate::verify::HmacVerifyCmd;
+
 const RUNTIME_BOOT_STATUS_BASE: u32 = 0x600;
 
 /// Statuses used by ROM to log dice derivation progress.
@@ -65,6 +68,7 @@ pub struct Drivers<'a> {
     pub mbox: Mailbox,
     pub sha_acc: Sha512AccCsr,
     pub ecdsa: Ecc384,
+    pub hmac: Hmac384,
     pub data_vault: DataVault,
     pub soc_ifc: SocIfcReg,
     pub regions: MemoryRegions,
@@ -114,6 +118,7 @@ impl<'a> Drivers<'a> {
             mbox: Mailbox::new(MboxCsr::new()),
             sha_acc: Sha512AccCsr::new(),
             ecdsa: Ecc384::new(EccReg::new()),
+            hmac: Hmac384::new(HmacReg::new()),
             data_vault: DataVault::new(DvReg::new()),
             soc_ifc: SocIfcReg::new(),
             regions: MemoryRegions::new(),
@@ -175,6 +180,8 @@ fn handle_command(drivers: &mut Drivers) -> CaliptraResult<MboxStatusE> {
         CommandId::TEST_ONLY_GET_FMC_ALIAS_CERT => {
             TestGetFmcAliasCertCmd::execute(&drivers.data_vault)
         }
+        #[cfg(feature = "test_only_commands")]
+        CommandId::TEST_ONLY_HMAC384_VERIFY => HmacVerifyCmd::execute(drivers, cmd_bytes),
         CommandId::VERSION => FipsVersionCmd::execute(drivers),
         CommandId::SELF_TEST => FipsSelfTestCmd::execute(drivers),
         CommandId::SHUTDOWN => FipsShutdownCmd::execute(drivers),
diff --git a/runtime/src/mailbox_api.rs b/runtime/src/mailbox_api.rs
index fc6dcd56..3f37c131 100644
--- a/runtime/src/mailbox_api.rs
+++ b/runtime/src/mailbox_api.rs
@@ -14,6 +14,7 @@ impl CommandId {
     pub const STASH_MEASUREMENT: Self = Self(0x4D454153); // "MEAS"
     pub const INVOKE_DPE: Self = Self(0x44504543); // "DPEC"
     pub const FW_INFO: Self = Self(0x494E464F); // "INFO"
+    pub const TEST_ONLY_HMAC384_VERIFY: Self = Self(0x484D4143); // "HMAC"
 
     // TODO: Remove this and merge with GET_LDEV_CERT once that is implemented
     pub const TEST_ONLY_GET_LDEV_CERT: Self = Self(0x4345524c); // "CERL"
@@ -170,6 +171,18 @@ pub struct EcdsaVerifyReq {
 }
 // No command-specific output args
 
+// TEST_ONLY_HMAC384_SIGNATURE_VERIFY
+#[repr(C)]
+#[derive(Debug, AsBytes, FromBytes, PartialEq, Eq)]
+pub struct HmacVerifyReq {
+    pub hdr: MailboxReqHeader,
+    pub key: [u8; 48],
+    pub tag: [u8; 48],
+    pub len: u32,
+    pub msg: [u8; 256],
+}
+// No command-specific output args
+
 // STASH_MEASUREMENT
 #[repr(C)]
 #[derive(Debug, AsBytes, FromBytes, PartialEq, Eq)]
diff --git a/runtime/src/verify.rs b/runtime/src/verify.rs
index 4158c1b4..1b15464d 100644
--- a/runtime/src/verify.rs
+++ b/runtime/src/verify.rs
@@ -1,10 +1,19 @@
 // Licensed under the Apache-2.0 license
 
+#[cfg(feature = "test_only_commands")]
+use crate::HmacVerifyReq;
 use crate::{Drivers, EcdsaVerifyReq, MailboxResp};
 use caliptra_drivers::{
     Array4x12, CaliptraError, CaliptraResult, Ecc384PubKey, Ecc384Result, Ecc384Scalar,
     Ecc384Signature,
 };
+
+#[cfg(feature = "test_only_commands")]
+use caliptra_drivers::{Hmac384Data, Hmac384Key, Trng};
+#[cfg(feature = "test_only_commands")]
+use caliptra_registers::{
+    csrng::CsrngReg, entropy_src::EntropySrcReg, soc_ifc::SocIfcReg, soc_ifc_trng::SocIfcTrngReg,
+};
 use zerocopy::FromBytes;
 
 pub struct EcdsaVerifyCmd;
@@ -39,3 +48,45 @@ impl EcdsaVerifyCmd {
         Ok(MailboxResp::default())
     }
 }
+
+/// Handle the `TEST_ONLY_HMAC_SHA384_VERIFY` mailbox command
+#[cfg(feature = "test_only_commands")]
+pub struct HmacVerifyCmd;
+#[cfg(feature = "test_only_commands")]
+impl HmacVerifyCmd {
+    #[cfg(feature = "test_only_commands")]
+    pub(crate) fn execute(drivers: &mut Drivers, cmd_args: &[u8]) -> CaliptraResult<MailboxResp> {
+        if let Some(cmd) = HmacVerifyReq::read_from(cmd_args) {
+            let key = Array4x12::from(cmd.key);
+            let key = Hmac384Key::from(&key);
+            let mut out_tag = Array4x12::default();
+            let Ok(len) = usize::try_from(cmd.len) else {
+                return Err(CaliptraError::RUNTIME_HMAC_VERIFY_FAILED);
+            };
+            if len > cmd.msg.len() {
+                return Err(CaliptraError::RUNTIME_HMAC_VERIFY_FAILED);
+            }
+            let data = Hmac384Data::from(&cmd.msg[0..len]);
+            let mut trng = unsafe {
+                Trng::new(
+                    CsrngReg::new(),
+                    EntropySrcReg::new(),
+                    SocIfcTrngReg::new(),
+                    &SocIfcReg::new(),
+                )
+            }?;
+
+            drivers
+                .hmac
+                .hmac(&key, &data, &mut trng, (&mut out_tag).into())?;
+
+            if out_tag != Array4x12::from(cmd.tag) {
+                return Err(CaliptraError::RUNTIME_HMAC_VERIFY_FAILED);
+            }
+        } else {
+            return Err(CaliptraError::RUNTIME_INSUFFICIENT_MEMORY);
+        };
+
+        Ok(MailboxResp::default())
+    }
+}


Test patch:
diff --git a/runtime/tests/hmac.rs b/runtime/tests/hmac.rs
new file mode 100644
index 00000000..d55e9319
--- /dev/null
+++ b/runtime/tests/hmac.rs
@@ -0,0 +1,98 @@
+// Licensed under the Apache-2.0 license.
+pub mod common;
+
+use caliptra_hw_model::HwModel;
+use caliptra_runtime::{CommandId, HmacVerifyReq, MailboxReqHeader, MailboxRespHeader};
+use common::run_rt_test;
+use zerocopy::{AsBytes, FromBytes};
+
+#[test]
+fn hmac_cmd_run_wycheproof() {
+    let mut model = run_rt_test(None);
+
+    model
+        .step_until_output_contains("Caliptra RT listening for mailbox commands...")
+        .unwrap();
+
+    #[allow(dead_code)]
+    #[derive(Debug)]
+    struct WycheproofResults {
+        id: usize,
+        comment: String,
+    }
+
+    // Collect all the errors and print at the end
+    let mut wyche_fail: Vec<WycheproofResults> = Vec::new();
+    let mut wyche_ran: Vec<WycheproofResults> = Vec::new();
+    let test_set = wycheproof::mac::TestSet::load(wycheproof::mac::TestName::HmacSha384).unwrap();
+
+    for test_groups in test_set.test_groups {
+        for test in &test_groups.tests {
+            // The mailbox is only implemented for keys and tags of 384 bits
+            if test.key.len() != 48 || test.tag.len() != 48 {
+                continue;
+            }
+            wyche_ran.push(WycheproofResults {
+                id: test.tc_id,
+                comment: test.comment.to_string(),
+            });
+            let mut msg = [0; 256];
+            msg[..test.msg.len()].copy_from_slice(test.msg.as_slice());
+            let cmd = HmacVerifyReq {
+                hdr: MailboxReqHeader { chksum: 0 },
+                key: test.key[..].try_into().unwrap(),
+                tag: test.tag[..].try_into().unwrap(),
+                len: test.msg.len().try_into().unwrap(),
+                msg,
+            };
+            let checksum = caliptra_common::checksum::calc_checksum(
+                u32::from(CommandId::TEST_ONLY_HMAC384_VERIFY),
+                &cmd.as_bytes()[4..],
+            );
+            let cmd = HmacVerifyReq {
+                hdr: MailboxReqHeader { chksum: checksum },
+                ..cmd
+            };
+            let resp = model.mailbox_execute(
+                u32::from(CommandId::TEST_ONLY_HMAC384_VERIFY),
+                cmd.as_bytes(),
+            );
+            match test.result {
+                wycheproof::TestResult::Valid | wycheproof::TestResult::Acceptable => match resp {
+                    Err(_) | Ok(None) => {
+                        wyche_fail.push(WycheproofResults {
+                            id: test.tc_id,
+                            comment: test.comment.to_string(),
+                        });
+                    }
+                    Ok(Some(resp)) => {
+                        // Verify the checksum and FIPS status
+                        let resp_hdr = MailboxRespHeader::read_from(resp.as_slice()).unwrap();
+                        assert_eq!(
+                            resp_hdr.fips_status,
+                            MailboxRespHeader::FIPS_STATUS_APPROVED
+                        );
+                        // Checksum is just going to be 0 because FIPS_STATUS_APPROVED is 0
+                        assert_eq!(resp_hdr.chksum, 0);
+                    }
+                },
+                wycheproof::TestResult::Invalid => {
+                    if resp.is_ok() {
+                        wyche_fail.push(WycheproofResults {
+                            id: test.tc_id,
+                            comment: test.comment.to_string(),
+                        });
+                    }
+                }
+            }
+        }
+    }
+    println!("Executed wycheproof tests:\n{:#?}", wyche_ran);
+    if !wyche_fail.is_empty() {
+        panic!(
+            "Number of failed tests {}:\n{:#?}",
+            wyche_fail.len(),
+            wyche_fail
+        );
+    }
+}
diff --git a/runtime/tests/integration_tests.rs b/runtime/tests/integration_tests.rs
index 12f886d3..baf8fc52 100644
--- a/runtime/tests/integration_tests.rs
+++ b/runtime/tests/integration_tests.rs
@@ -128,7 +128,7 @@ fn test_fw_info() {
 }
 
 #[test]
-fn test_verify_cmd() {
+fn test_ecdsa_verify_cmd() {
     let mut model = run_rom_test("mbox");
 
     model.step_until(|m| {