Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 12 additions & 21 deletions fw/core/lib/src/ddi/mbor/aes_generate_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;

Expand All @@ -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
Expand All @@ -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,
Expand Down
12 changes: 7 additions & 5 deletions fw/core/lib/src/ddi/mbor/bulk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}

Expand Down Expand Up @@ -63,7 +65,7 @@ pub(crate) async fn commit_key<P: HsmPal>(
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);
}
Expand Down
23 changes: 7 additions & 16 deletions fw/core/lib/src/ddi/mbor/from_ddi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,27 +47,18 @@ pub(crate) fn curve(curve: DdiEccCurve) -> HsmResult<HsmEccCurve> {
}
}

/// 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))
Expand Down
4 changes: 2 additions & 2 deletions fw/core/lib/src/ddi/mbor/hkdf_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) =
Expand Down
4 changes: 2 additions & 2 deletions fw/core/lib/src/ddi/mbor/kbkdf_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) =
Expand Down
4 changes: 3 additions & 1 deletion fw/core/lib/src/ddi/mbor/kdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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` |
//!
Expand Down Expand Up @@ -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<u8>) -> HsmResult<KdfTarget> {
let aes = |kind, out_len| {
Expand All @@ -108,6 +109,7 @@ pub(crate) fn resolve_target(key_type: DdiKeyType, key_len: Option<u8>) -> 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),

Expand Down
15 changes: 8 additions & 7 deletions fw/core/lib/src/ddi/mbor/rsa_unwrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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)?;
Expand Down
30 changes: 12 additions & 18 deletions fw/core/lib/src/ddi/mbor/unmask_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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<u16>, Option<&mut DmaBuf>) =
if is_bulk {
Expand Down