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
61 changes: 61 additions & 0 deletions ddi/tbor/types/src/get_unwrapping_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! Host-side wrapper for the TBOR `GetUnwrappingKey` command.
//!
//! `GetUnwrappingKey` is an **in-session** command (Crypto-Officer or
//! Crypto-User) that returns the partition's RSA-2048 **unwrapping**
//! public key, which the host uses to RSA-AES key-wrap a payload for a
//! future `UnwrapKey` import. The unwrapping key is a device-provisioned
//! partition-internal key; only its public half is returned.
//!
//! RSA key generation is expensive, so the key is materialised lazily; an
//! absent key surfaces as `PendingKeyGeneration` (the host retries).

use crate::tbor;

/// TBOR opcode for `GetUnwrappingKey`.
pub const TBOR_OP_GET_UNWRAPPING_KEY: u8 = 0x13;

/// Wire length of the RSA-2048 unwrapping public key in HSM format:
/// `n_le(256) ‖ e_le(4)`.
pub const UNWRAPPING_PUB_KEY_LEN: usize = 256 + 4;

/// Host-facing TBOR `GetUnwrappingKey` request.
#[tbor(opcode = TBOR_OP_GET_UNWRAPPING_KEY, session_ctrl = in_session)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct TborGetUnwrappingKeyReq {
/// Session id this request is bound to. Cross-checked against the
/// SQE-carried session id by the dispatcher.
#[tbor(session_id)]
pub session_id: u16,
}

/// Host-facing TBOR `GetUnwrappingKey` response.
#[tbor(response)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TborGetUnwrappingKeyResp {
/// The RSA-2048 unwrapping public key in HSM wire format
/// (`n_le(256) ‖ e_le(4)`), exactly [`UNWRAPPING_PUB_KEY_LEN`] (260)
/// bytes.
pub pub_key: [u8; UNWRAPPING_PUB_KEY_LEN],
}

#[cfg(test)]
mod tests {
use azihsm_ddi_tbor_types::TborOpReq;

use super::*;

#[test]
fn request_encodes_session_id() {
let req = TborGetUnwrappingKeyReq { session_id: 11 };
let mut buf = [0u8; 128];
let frame = req.encode_request(&mut buf).expect("encode");
// The session id (11) must appear in the encoded frame.
assert!(
frame.contains(&11),
"encoded frame must carry the session id"
);
}
}
4 changes: 4 additions & 0 deletions ddi/tbor/types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ impl From<SessionControlKind> for u8 {

mod api_rev;
mod evidence;
mod get_unwrapping_key;
mod key_report;
mod part_final;
mod part_info;
Expand All @@ -97,9 +98,11 @@ mod session_close;
mod session_open_finish;
mod session_open_init;
mod status;
mod unwrap_key;

pub use api_rev::*;
pub use evidence::*;
pub use get_unwrapping_key::*;
pub use key_report::*;
pub use part_final::*;
pub use part_info::*;
Expand All @@ -117,6 +120,7 @@ pub use session_close::*;
pub use session_open_finish::*;
pub use session_open_init::*;
pub use status::*;
pub use unwrap_key::*;

/// Trait implemented by host-side TBOR request value types.
///
Expand Down
128 changes: 128 additions & 0 deletions ddi/tbor/types/src/unwrap_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! Host-side wrapper for the TBOR `UnwrapKey` command.
//!
//! `UnwrapKey` is an **in-session** command (Crypto-Officer or
//! Crypto-User) that RSA-AES-unwraps a host-supplied wrapped key
//! (AES / RSA / ECC / HMAC) with the partition's unwrapping key (see
//! [`GetUnwrappingKey`](crate::get_unwrapping_key)) and returns it as a
//! **masked** blob under the requested scope's masking key — plus the
//! re-derived wire public key for RSA / ECC.
//!
//! The `scope`, `key_class`, `key_usage`, and `oaep_hash_algo` are raw 1-byte discriminants
//! (the firmware types them as the `KeyScope` / `KeyClass` / `HashAlgo`
//! open-enums; this host crate is firewalled from the firmware PAL types).

use alloc::vec::Vec;

use crate::tbor;

/// TBOR opcode for `UnwrapKey`.
pub const TBOR_OP_UNWRAP_KEY: u8 = 0x14;

/// Max wrapped-blob length (`RSA-OAEP(KEK) ‖ AES-KWP(key)`).
pub const UNWRAP_WRAPPED_BLOB_MAX_LEN: usize = 3072;
/// Max masked recovered-key envelope length.
pub const UNWRAP_MASKED_KEY_MAX_LEN: usize = 3072;
/// Max recovered public-key length.
pub const UNWRAP_PUB_KEY_MAX_LEN: usize = 520;

/// `KeyClass` discriminant for a raw AES key.
pub const KEY_CLASS_AES: u8 = 0;
/// `KeyClass` discriminant for a DER RSA private key (non-CRT).
pub const KEY_CLASS_RSA: u8 = 1;
/// `KeyClass` discriminant for a DER RSA private key (CRT).
pub const KEY_CLASS_RSA_CRT: u8 = 2;
/// `KeyClass` discriminant for a PKCS#8 DER ECC private key.
pub const KEY_CLASS_ECC: u8 = 3;
/// `KeyClass` discriminant for a variable-length HMAC-SHA-256 key.
pub const KEY_CLASS_HMAC_SHA256: u8 = 4;
/// `KeyClass` discriminant for a variable-length HMAC-SHA-384 key.
pub const KEY_CLASS_HMAC_SHA384: u8 = 5;
/// `KeyClass` discriminant for a variable-length HMAC-SHA-512 key.
pub const KEY_CLASS_HMAC_SHA512: u8 = 6;

/// `KeyUsage` bit: key may encrypt.
pub const KEY_USAGE_ENCRYPT: u8 = 1 << 0;
/// `KeyUsage` bit: key may decrypt.
pub const KEY_USAGE_DECRYPT: u8 = 1 << 1;
/// `KeyUsage` bit: key may sign / compute a MAC.
pub const KEY_USAGE_SIGN: u8 = 1 << 2;
/// `KeyUsage` bit: key may verify a signature / MAC.
pub const KEY_USAGE_VERIFY: u8 = 1 << 3;
/// `KeyUsage` bit: key may derive other keys.
pub const KEY_USAGE_DERIVE: u8 = 1 << 4;
/// `KeyUsage` bit: key may wrap other keys.
pub const KEY_USAGE_WRAP: u8 = 1 << 5;
/// `KeyUsage` bit: key may unwrap other keys.
pub const KEY_USAGE_UNWRAP: u8 = 1 << 6;

/// Host-facing TBOR `UnwrapKey` request.
#[tbor(opcode = TBOR_OP_UNWRAP_KEY, session_ctrl = in_session)]
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct TborUnwrapKeyReq {
/// Session id this request is bound to.
#[tbor(session_id)]
pub session_id: u16,

/// Requested key scope (masks the recovered key), 1-byte `KeyScope`.
pub scope: u8,

/// Class of the wrapped key, 1-byte `KeyClass` (see `KEY_CLASS_*`).
pub key_class: u8,

/// Requested key-usage permissions, 1-byte `KeyUsage` bitfield (see
/// `KEY_USAGE_*`). The device enforces which usage(s) are valid for
/// `key_class`.
pub key_usage: u8,

/// OAEP hash used to wrap the KEK, 1-byte `HashAlgo`.
pub oaep_hash_algo: u8,

/// The RSA-AES-wrapped key (`RSA-OAEP(KEK) ‖ AES-KWP(key)`).
#[tbor(max_len = 3072)]
pub wrapped_blob: Vec<u8>,
}

/// Host-facing TBOR `UnwrapKey` response.
#[tbor(response)]
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct TborUnwrapKeyResp {
/// The recovered key's vault-kind discriminant.
pub key_kind: u8,

/// The recovered key, masked under the scope's masking key.
#[tbor(max_len = 3072)]
pub masked_key: Vec<u8>,

/// The recovered key's wire public key for RSA / ECC; empty for
/// symmetric (AES / HMAC) keys.
#[tbor(max_len = 520)]
pub pub_key: Vec<u8>,
}

#[cfg(test)]
mod tests {
use azihsm_ddi_tbor_types::TborOpReq;

use super::*;

#[test]
fn request_encodes_fields() {
let req = TborUnwrapKeyReq {
session_id: 7,
scope: 0b011,
key_class: KEY_CLASS_HMAC_SHA256,
key_usage: KEY_USAGE_SIGN | KEY_USAGE_VERIFY,
oaep_hash_algo: 1,
wrapped_blob: alloc::vec![0x5Au8; 300],
};
let mut buf = [0u8; 4096];
let frame = req.encode_request(&mut buf).expect("encode");
assert!(
frame.contains(&KEY_CLASS_HMAC_SHA256),
"encoded frame must carry the key-class discriminant",
);
}
}
66 changes: 66 additions & 0 deletions ddi/tbor/types/tests/commands/get_unwrapping_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! Integration tests for the TBOR `GetUnwrappingKey` command.
//!
//! The command returns the partition's RSA-2048 unwrapping public key
//! (HSM wire format `n_le(256) ‖ e_le(4)` = 260 bytes), which the host
//! uses to RSA-AES key-wrap a payload for a future `UnwrapKey` import.
//! The std (emulator) PAL materialises the key lazily on first read.
//!
//! Coverage:
//! * Happy path — returns a full, non-zero 260-byte public key.
//! * Stability — a second call returns the same (stable) key.

#![cfg(feature = "emu")]

use azihsm_ddi_tbor_types::TborGetUnwrappingKeyReq;
use azihsm_ddi_tbor_types::UNWRAPPING_PUB_KEY_LEN;

use crate::commands::sd_sealing_key_gen::finalized_co_session;
use crate::harness::TestCtx;

#[test]
fn get_unwrapping_key_returns_rsa_pub_key_emu() {
let ctx = TestCtx::new();
let session = finalized_co_session(&ctx);

let req = TborGetUnwrappingKeyReq {
session_id: session.session_id,
};
let resp = ctx.tbor(&req).expect("GetUnwrappingKey");

assert_eq!(
resp.pub_key.len(),
UNWRAPPING_PUB_KEY_LEN,
"unwrapping pub key must be the pinned length",
);
// The RSA modulus (first 256 bytes) must be a full, non-zero value.
assert!(
resp.pub_key[..256].iter().any(|&b| b != 0),
"RSA modulus must not be all-zero",
);
// The public exponent (last 4 bytes) must be non-zero.
assert!(
resp.pub_key[256..].iter().any(|&b| b != 0),
"RSA public exponent must not be all-zero",
);
}

#[test]
fn get_unwrapping_key_is_stable_emu() {
let ctx = TestCtx::new();
let session = finalized_co_session(&ctx);

let req = TborGetUnwrappingKeyReq {
session_id: session.session_id,
};
let first = ctx.tbor(&req).expect("first GetUnwrappingKey");
let second = ctx.tbor(&req).expect("second GetUnwrappingKey");

// The unwrapping key is a stable partition key — not regenerated.
assert_eq!(
first.pub_key, second.pub_key,
"the unwrapping key must be stable across calls",
);
}
2 changes: 2 additions & 0 deletions ddi/tbor/types/tests/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod api_rev;
pub mod default_psk_gate;
pub mod forward_compat;
pub mod fw_error_decode;
pub mod get_unwrapping_key;
pub mod key_report;
pub mod open_session;
pub mod part_final;
Expand All @@ -24,3 +25,4 @@ pub mod sd_restore_remote_backup;
pub mod sd_sealing_key_gen;
pub mod session_close;
pub mod unexpected_toc_type;
pub mod unwrap_key;
Loading
Loading