diff --git a/fw/core/lib/src/ddi/mbor/aes_generate_key.rs b/fw/core/lib/src/ddi/mbor/aes_generate_key.rs index 3431209a4..a99ffea60 100644 --- a/fw/core/lib/src/ddi/mbor/aes_generate_key.rs +++ b/fw/core/lib/src/ddi/mbor/aes_generate_key.rs @@ -4,26 +4,24 @@ //! DDI AesGenerateKey command handler. //! //! Within an open session, generate a fresh random AES key (128 / -//! 192 / 256 bits) or an AES-256-GCM bulk key, persist it in the +//! 192 / 256 bits) or an AES-256 GCM / XTS bulk key, persist it in the //! partition vault — optionally session-scoped so it is torn down by //! [`CloseSession`](super::close_session) — and return the assigned //! `key_id` plus a masked-key envelope that the host may re-import on //! a future session. //! -//! For the GCM bulk kinds (`AesGcmBulk256` / `AesGcmBulk256Unapproved`) -//! the response also carries a `bulk_key_id`. The bulk key is the key -//! consumed by the bulk GCM encrypt/decrypt op; the host addresses -//! it via this `bulk_key_id`. Bulk key material is registered with the +//! For the bulk kinds (`AesGcmBulk256` / `AesGcmBulk256Unapproved` / +//! `AesXtsBulk256`) the response also carries a `bulk_key_id`. The bulk +//! key is the key consumed by the bulk GCM / XTS encrypt/decrypt op; the +//! host addresses it via this `bulk_key_id`. Bulk key material is registered with the //! bulk-crypto backend (see [`bulk::commit_key`](super::bulk)); //! the vault stores only the 2-byte backend handle, and `bulk_key_id` is //! the distinct backend-assigned id, not the vault `key_id`. //! -//! Scope: 128/192/256-bit AES keys and AES-256-GCM bulk keys. The -//! AES-XTS bulk variant is rejected with `InvalidArg`. +//! Scope: 128/192/256-bit AES keys and AES-256 GCM / XTS bulk keys. use azihsm_fw_ddi_mbor_types::aes_generate_key::DdiAesGenerateKeyReq; use azihsm_fw_ddi_mbor_types::aes_generate_key::DdiAesGenerateKeyResp; -use azihsm_fw_ddi_mbor_types::DdiAesKeySize; use super::*; @@ -47,17 +45,10 @@ pub(crate) async fn aes_generate_key<'p, P: HsmPal>( let sess_id = hdr.sess_id.ok_or(HsmError::SessionExpected)?; - // GCM bulk kinds map to a 32-byte AES-256 key and report a - // `bulk_key_id`; non-bulk kinds map to their sized AES vault kind. - let is_bulk = matches!( - body.key_size, - DdiAesKeySize::AesGcmBulk256 | DdiAesKeySize::AesGcmBulk256Unapproved - ); - let (key_len, vault_kind) = if is_bulk { - super::from_ddi::aes_bulk(body.key_size)? - } else { - super::from_ddi::aes(body.key_size)? - }; + // Resolve the vault kind first, then classify it: bulk kinds (GCM / + // XTS) map to a 32-byte AES-256 key and report a `bulk_key_id`. + let (key_len, vault_kind) = super::from_ddi::aes(body.key_size)?; + let is_bulk = super::bulk::is_bulk(vault_kind); let attrs = super::key_attrs::for_aes(&body.key_properties.key_metadata, true)?; // Session-only keys are anonymous — disallow a host-supplied @@ -74,11 +65,11 @@ pub(crate) async fn aes_generate_key<'p, P: HsmPal>( return Err(e); } - // Bulk GCM keys live in the bulk-crypto backend: hand the freshly + // Bulk keys live in the bulk-crypto backend: hand the freshly // generated material to the backend and keep only the 2-byte // `bulk_key_id` reference in the vault. Non-bulk keys are stored // directly. The registration is scoped to the creating session so - // later bulk GCM ops (which carry the session id) match. Scrub the + // later bulk ops (which carry the session id) match. Scrub the // material on commit failure before propagating. let (key_handle, bulk_key_id) = match super::bulk::commit_key( pal, diff --git a/fw/core/lib/src/ddi/mbor/bulk.rs b/fw/core/lib/src/ddi/mbor/bulk.rs index 50f9571bc..f779f7d64 100644 --- a/fw/core/lib/src/ddi/mbor/bulk.rs +++ b/fw/core/lib/src/ddi/mbor/bulk.rs @@ -17,12 +17,14 @@ use super::*; -/// True for the AES-GCM bulk vault kinds whose material lives in the -/// platform bulk-crypto backend rather than the vault. -pub(crate) fn is_gcm_bulk(kind: HsmVaultKeyKind) -> bool { +/// True for the AES bulk vault kinds (GCM / XTS) whose material lives in +/// the platform bulk-crypto backend rather than the vault. +pub(crate) fn is_bulk(kind: HsmVaultKeyKind) -> bool { matches!( kind, - HsmVaultKeyKind::AesGcmBulk256 | HsmVaultKeyKind::AesGcmBulk256Unapproved + HsmVaultKeyKind::AesGcmBulk256 + | HsmVaultKeyKind::AesGcmBulk256Unapproved + | HsmVaultKeyKind::AesXtsBulk256 ) } @@ -63,7 +65,7 @@ pub(crate) async fn commit_key( return Err(e); } }; - if is_gcm_bulk(kind) && bulk_key_id.is_none() { + if is_bulk(kind) && bulk_key_id.is_none() { let _ = pal.vault_key_delete(io, handle).await; return Err(HsmError::UnsupportedCmd); } diff --git a/fw/core/lib/src/ddi/mbor/from_ddi.rs b/fw/core/lib/src/ddi/mbor/from_ddi.rs index 4fbebcce4..4a6ab1e0f 100644 --- a/fw/core/lib/src/ddi/mbor/from_ddi.rs +++ b/fw/core/lib/src/ddi/mbor/from_ddi.rs @@ -47,27 +47,18 @@ pub(crate) fn curve(curve: DdiEccCurve) -> HsmResult { } } -/// Map a [`DdiAesKeySize`] to its raw byte length and the matching -/// non-bulk AES vault kind. Bulk AES variants (XTS / GCM) are -/// rejected with [`HsmError::InvalidArg`]; use [`aes_bulk`] for the -/// GCM bulk kinds. +/// Map a [`DdiAesKeySize`] to its raw byte length and the matching AES +/// vault kind, including the bulk kinds (classify the result with +/// [`bulk::is_bulk`](super::bulk::is_bulk)). The two GCM bulk variants +/// differ only in FIPS posture: `AesGcmBulk256` is FIPS-approved (the device +/// generates the IV internally on encrypt), `AesGcmBulk256Unapproved` uses +/// the host-supplied IV. An unknown size returns [`HsmError::InvalidArg`]. pub(crate) fn aes(size: DdiAesKeySize) -> HsmResult<(usize, HsmVaultKeyKind)> { match size { DdiAesKeySize::Aes128 => Ok((16, HsmVaultKeyKind::Aes128)), DdiAesKeySize::Aes192 => Ok((24, HsmVaultKeyKind::Aes192)), DdiAesKeySize::Aes256 => Ok((32, HsmVaultKeyKind::Aes256)), - _ => Err(HsmError::InvalidArg), - } -} - -/// Map a bulk [`DdiAesKeySize`] to its raw AES-256 byte length and the -/// matching bulk GCM vault kind. The two variants differ only in FIPS -/// posture: `AesGcmBulk256` is FIPS-approved (the device generates the -/// IV internally on encrypt), `AesGcmBulk256Unapproved` uses the -/// host-supplied IV. Non-GCM-bulk sizes return -/// [`HsmError::InvalidArg`]. -pub(crate) fn aes_bulk(size: DdiAesKeySize) -> HsmResult<(usize, HsmVaultKeyKind)> { - match size { + DdiAesKeySize::AesXtsBulk256 => Ok((32, HsmVaultKeyKind::AesXtsBulk256)), DdiAesKeySize::AesGcmBulk256 => Ok((32, HsmVaultKeyKind::AesGcmBulk256)), DdiAesKeySize::AesGcmBulk256Unapproved => { Ok((32, HsmVaultKeyKind::AesGcmBulk256Unapproved)) diff --git a/fw/core/lib/src/ddi/mbor/hkdf_derive.rs b/fw/core/lib/src/ddi/mbor/hkdf_derive.rs index 908f947ea..ecc78459a 100644 --- a/fw/core/lib/src/ddi/mbor/hkdf_derive.rs +++ b/fw/core/lib/src/ddi/mbor/hkdf_derive.rs @@ -94,9 +94,9 @@ pub(crate) async fn hkdf_derive<'p, P: HsmPal>( return Err(e); } - // Commit the derived key: AES-GCM bulk keys are handed to the + // Commit the derived key: AES bulk keys (GCM / XTS) are handed to the // bulk-crypto backend (the vault records only the returned - // `bulk_key_id` handle, carried in the response for later bulk GCM + // `bulk_key_id` handle, carried in the response for later bulk // ops); every other kind is stored directly in the vault. Scrub the // derived material if the commit fails, before propagating the error. let (key_handle, bulk_key_id) = diff --git a/fw/core/lib/src/ddi/mbor/kbkdf_derive.rs b/fw/core/lib/src/ddi/mbor/kbkdf_derive.rs index 5ef099277..cee487bfe 100644 --- a/fw/core/lib/src/ddi/mbor/kbkdf_derive.rs +++ b/fw/core/lib/src/ddi/mbor/kbkdf_derive.rs @@ -86,9 +86,9 @@ pub(crate) async fn kbkdf_counter_hmac_derive<'p, P: HsmPal>( } } - // Commit the derived key: AES-GCM bulk keys are handed to the + // Commit the derived key: AES bulk keys (GCM / XTS) are handed to the // bulk-crypto backend (the vault records only the returned - // `bulk_key_id` handle, carried in the response for later bulk GCM + // `bulk_key_id` handle, carried in the response for later bulk // ops); every other kind is stored directly in the vault. Scrub the // derived material if the commit fails, before propagating the error. let (key_handle, bulk_key_id) = diff --git a/fw/core/lib/src/ddi/mbor/kdf.rs b/fw/core/lib/src/ddi/mbor/kdf.rs index ea3ed0082..457e74518 100644 --- a/fw/core/lib/src/ddi/mbor/kdf.rs +++ b/fw/core/lib/src/ddi/mbor/kdf.rs @@ -22,6 +22,7 @@ //! | Requested `key_type` | Vault kind | Output length | //! |---|---|---| //! | `Aes128` / `Aes192` / `Aes256` | `Aes128` / `Aes192` / `Aes256` | 16 / 24 / 32 | +//! | `AesGcmBulk256` / `AesGcmBulk256Unapproved` / `AesXtsBulk256` | same kind (bulk; registered with the bulk-crypto backend) | 32 | //! | `HmacSha256` / `384` / `512` | `VarLenHmacSha256` / `384` / `512` | 32 / 48 / 64 | //! | `VarHmac256` / `384` / `512` | `VarLenHmacSha256` / `384` / `512` | `key_length` | //! @@ -85,7 +86,7 @@ pub(crate) fn validate_input_secret(kind: HsmVaultKeyKind) -> HsmResult<()> { /// into the vault kind, OKM length, and attribute family. /// /// See the [module docs](self) for the full mapping. Unsupported -/// output types (ECC / RSA / Secret / XTS bulk) return +/// output types (ECC / RSA / Secret) return /// [`HsmError::InvalidKeyType`]. pub(crate) fn resolve_target(key_type: DdiKeyType, key_len: Option) -> HsmResult { let aes = |kind, out_len| { @@ -108,6 +109,7 @@ pub(crate) fn resolve_target(key_type: DdiKeyType, key_len: Option) -> HsmRe DdiKeyType::Aes192 => aes(HsmVaultKeyKind::Aes192, 24), DdiKeyType::Aes256 => aes(HsmVaultKeyKind::Aes256, 32), + DdiKeyType::AesXtsBulk256 => aes(HsmVaultKeyKind::AesXtsBulk256, 32), DdiKeyType::AesGcmBulk256 => aes(HsmVaultKeyKind::AesGcmBulk256, 32), DdiKeyType::AesGcmBulk256Unapproved => aes(HsmVaultKeyKind::AesGcmBulk256Unapproved, 32), diff --git a/fw/core/lib/src/ddi/mbor/rsa_unwrap.rs b/fw/core/lib/src/ddi/mbor/rsa_unwrap.rs index ba2ecadbb..f7c0f1467 100644 --- a/fw/core/lib/src/ddi/mbor/rsa_unwrap.rs +++ b/fw/core/lib/src/ddi/mbor/rsa_unwrap.rs @@ -25,9 +25,9 @@ //! copy), matching the zero-copy `reserve` / `from_layout` pattern used //! by the other key-producing handlers and the reference firmware. This //! keeps the largest RSA-4096 keys within the fixed per-IO DMA budget. -//! The AES, RSA (plain / CRT), and ECC key classes are wired; the AES-GCM -//! bulk variants recover the raw AES key, register it with the bulk-crypto -//! backend, and return its `bulk_key_id` (AES-XTS bulk is not supported). +//! The AES, RSA (plain / CRT), and ECC key classes are wired; the AES bulk +//! variants (GCM / XTS) recover the raw AES key, register it with the +//! bulk-crypto backend, and return its `bulk_key_id`. //! RSA and ECC imports return the imported key's wire public key, //! re-derived from the committed vault key. @@ -87,12 +87,13 @@ pub(crate) async fn rsa_unwrap<'p, P: HsmPal>( // separate property comparison. let unwrap_key_id = HsmKeyId::from(body.key_id); - // AES-256-GCM bulk keys follow a distinct import path: the recovered - // key is handed to the bulk-crypto backend and only its 2-byte + // AES-256 bulk keys (GCM / XTS) follow a distinct import path: the + // recovered key is handed to the bulk-crypto backend and only its 2-byte // `bulk_key_id` handle is kept in the vault (mirroring // [`aes_generate_key`](super::aes_generate_key)'s bulk path). Handle // and return here, before the asymmetric / AES import flow below. if let Some(bulk_kind) = match body.wrapped_blob_key_class { + DdiKeyClass::AesXtsBulk => Some(HsmVaultKeyKind::AesXtsBulk256), DdiKeyClass::AesGcmBulk => Some(HsmVaultKeyKind::AesGcmBulk256), DdiKeyClass::AesGcmBulkUnapproved => Some(HsmVaultKeyKind::AesGcmBulk256Unapproved), _ => None, @@ -115,7 +116,7 @@ pub(crate) async fn rsa_unwrap<'p, P: HsmPal>( // Commit the recovered key: the PAL registers it with the // bulk-crypto backend and stores only the 2-byte handle in the - // vault (scoped to the creating session so later bulk GCM ops + // vault (scoped to the creating session so later bulk ops // match), returning the backend id. RSA-unwrap always produces a // bulk key, so `bulk_key_id` is present. Scrub the recovered // material if the commit fails, before propagating the error. @@ -183,7 +184,7 @@ pub(crate) async fn rsa_unwrap<'p, P: HsmPal>( // imported key's vault attributes. These keys are imported, not // generated on-device, so the `for_*` builders are told `local = false` // (and, as always, never set `internal`). AES, RSA (plain / CRT), and - // ECC are supported; the AES-GCM bulk variants are handled above. + // ECC are supported; the AES bulk variants are handled above. let (key_class, import_attrs) = match body.wrapped_blob_key_class { DdiKeyClass::Aes => { let attrs = super::key_attrs::for_aes(&body.key_properties.key_metadata, false)?; diff --git a/fw/core/lib/src/ddi/mbor/unmask_key.rs b/fw/core/lib/src/ddi/mbor/unmask_key.rs index e9ec48732..a335b16c7 100644 --- a/fw/core/lib/src/ddi/mbor/unmask_key.rs +++ b/fw/core/lib/src/ddi/mbor/unmask_key.rs @@ -65,14 +65,8 @@ pub(crate) async fn unmask_key<'p, P: HsmPal>( .map_err(|_| HsmError::MaskedKeyDecodeFailed)?; // The partition unwrapping key is tagged `RsaUnwrap` and must - // not be re-imported as a general key. AES-XTS bulk keys are not - // supported by this firmware either (generate and derive reject - // them); importing one here would store only the backend handle and - // then re-mask that handle instead of the 32-byte key. - if matches!( - metadata.key_type, - DdiKeyType::RsaUnwrap | DdiKeyType::AesXtsBulk256 - ) { + // not be re-imported as a general key. + if metadata.key_type == DdiKeyType::RsaUnwrap { return Err(HsmError::InvalidKeyType); } @@ -95,16 +89,16 @@ pub(crate) async fn unmask_key<'p, P: HsmPal>( }; // Authenticate-then-decrypt in place, copy out the primary key - // material, and import it — for AES-GCM bulk keys into the bulk-crypto - // backend (the vault records only the returned `bulk_key_id` handle, and the - // 32-byte material is kept in the per-IO arena so it can be re-masked - // below); for every other kind into the vault inside an allocation - // scope so the (multi-KB for RSA) import scratch is freed before the - // response frame is built. The masking key is the per-session masking - // key for session-scoped keys, the partition masking key (MK) - // otherwise; a wrong key (tampered scope) or tampered blob fails the - // HMAC in `unmask` without leaking plaintext. - let is_bulk = super::bulk::is_gcm_bulk(kind); + // material, and import it — for AES bulk keys (GCM / XTS) into the + // bulk-crypto backend (the vault records only the returned `bulk_key_id` + // handle, and the 32-byte material is kept in the per-IO arena so it can + // be re-masked below); for every other kind into the vault inside an + // allocation scope so the (multi-KB for RSA) import scratch is freed + // before the response frame is built. The masking key is the + // per-session masking key for session-scoped keys, the partition masking + // key (MK) otherwise; a wrong key (tampered scope) or tampered blob fails + // the HMAC in `unmask` without leaking plaintext. + let is_bulk = super::bulk::is_bulk(kind); let (key_id, bulk_key_id, bulk_key_buf): (HsmKeyId, Option, Option<&mut DmaBuf>) = if is_bulk {