feat(fw): implement DDI GetUnwrappingKey + RsaUnwrap handlers#422
feat(fw): implement DDI GetUnwrappingKey + RsaUnwrap handlers#422jaygmsft wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds firmware-side support for two new key-wrap DDI commands—GetUnwrappingKey and RsaUnwrap—including the PAL trait surface and std-PAL implementations needed to generate/cache an RSA unwrap key, perform RSA-OAEP KEK decryption, AES-KWP payload unwrapping, and import the recovered key material into the partition vault.
Changes:
- Added new DDI handlers for
GetUnwrappingKey(partition-cached unwrap key) andRsaUnwrap(RSA-OAEP + AES-KWP unwrap + vault import). - Extended PAL traits (RSA + partition manager) to support query-alloc-use RSA keygen, RSA priv introspection/pub extraction, and caching the unwrap public key in partition state.
- Implemented std-PAL RSA wire public-key formatting, RSA-OAEP decrypt, AES-KWP unwrap, plus added emu smoke tests for both commands.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| fw/plat/std/pal/src/rsa.rs | Implements std-PAL RSA additions: query-alloc-use keygen, wire-format public key emission, priv-key introspection, OAEP decrypt. |
| fw/plat/std/pal/src/part.rs | Adds per-partition cached unwrap public key storage + getters/setters and reset clearing. |
| fw/plat/std/pal/src/aes.rs | Implements std-PAL aes_kwp_unwrap via the crypto crate. |
| fw/pal/traits/src/part.rs | Extends partition manager trait with unwrap public key query/set APIs. |
| fw/pal/traits/src/crypto/rsa.rs | Refactors RSA keygen API to query-alloc-use, adds helpers and new RSA priv inspection/extraction APIs. |
| fw/core/lib/src/ddi/mbor/rsa_unwrap.rs | New RsaUnwrap DDI handler: OAEP decrypt KEK, KWP unwrap payload, import AES/RSA into vault. |
| fw/core/lib/src/ddi/mbor/mod.rs | Wires GetUnwrappingKey and RsaUnwrap into the DDI MBOR dispatcher. |
| fw/core/lib/src/ddi/mbor/get_unwrapping_key.rs | New GetUnwrappingKey DDI handler: lazy RSA keygen under partition lock + partition caching. |
| fw/core/lib/src/ddi/mbor/aes_generate_key.rs | Exposes/shared build_attrs_for_aes with a local parameter for generate vs unwrap/import paths. |
| fw/core/ddi/mbor/types/src/get_unwrapping_key.rs | Updates MBOR type metadata to encode pub_key as a framed field. |
| ddi/mbor/types/tests/integration/rsa_unwrap_smoke.rs | Adds end-to-end emu smoke tests for RsaUnwrap (AES + RSA cases). |
| ddi/mbor/types/tests/integration/get_unwrapping_key_smoke.rs | Adds end-to-end emu smoke tests for GetUnwrappingKey (happy/stability/no-session). |
| ddi/mbor/types/tests/azihsm_ddi_tests.rs | Registers the new integration smoke test modules. |
75d8d8a to
4ef9a9d
Compare
4ef9a9d to
b66a8a5
Compare
b66a8a5 to
83f14fd
Compare
Add the firmware-side handlers for the two key-wrap DDI commands,
the PAL trait surface that supports them, and matching std-PAL impls.
* DDI handlers:
* `get_unwrapping_key`: returns the partition's RSA-2048 unwrap
key (vault id, wire-format pub key, empty masked_key placeholder).
Lazy generation under the partition lock so concurrent first-call
races never produce two competing keys; cached on partition state
across sessions, regenerated on partition reset.
* `rsa_unwrap`: takes an `OAEP(KEK) || AES-KWP(payload)` wrapped
blob, RSA-OAEP-decrypts the KEK with the partition's unwrap key,
AES-KWP-unwraps the payload with the KEK, and imports the
recovered key into the vault. Supports AES (16/24/32 byte) and
RSA (2k/3k/4k, CRT and non-CRT) imports; ECC import returns
`UnsupportedCmd`. Spec error codes
(`RsaUnwrapOaepDecodeFailed`, `RsaUnwrapAesUnwrapFailed`,
`RsaUnwrapInvalidRequest`, `RsaUnwrapInvalidKek`) are mapped
from the underlying crypto failures.
* PAL trait additions (`HsmRsa`, `HsmPart`):
* `rsa_gen_keypair` refactored to the query-alloc-use shape
(typo `ras_` \u2192 `rsa_`, takes scoped allocator, returns
`(priv_actual, pub_actual)`) — the only existing caller path
(`get_unwrapping_key`) uses the new shape from day one.
* `rsa_priv_key_size(io, key)` introspects an imported priv key.
* `rsa_priv_pub_key(io, key, pub_out)` query-alloc-use extractor
for the wire-format pub key from a serialized priv (lets the
RSA-import path populate the response's optional `pub_key` so
callers can verify the import bytewise without an extra DDI
round-trip).
* `part_unwrapping_pub_key` getter / setter for the cached
wire-format unwrap pub key (mirrors the existing
`part_establish_cred_pub_key` pattern).
* `HsmRsaKey` gains `pub_wire_len`, `pub_exp_len`, and
`priv_key_der_max` helpers.
* std PAL:
* `rsa_gen_keypair` emits wire-LE `n || e` with deterministic
fixed-width padding (256 + 4 = 260 bytes for RSA-2048).
* `rsa_oaep_decrypt` implemented \u2014 LE\u2192BE flip on the
ciphertext (host wraps under PKA-native LE), OpenSSL OAEP
decrypt into a modulus-sized scratch, copy actual plaintext
into caller's slot.
* `aes_kwp_unwrap` implemented (delegates to
`AesKeyWrapPadAlgo::decrypt`).
* `rsa_priv_key_size` / `rsa_priv_pub_key` implemented (parse
PKCS#8 DER, read modulus / extract n,e in wire format).
* Partition state gains `unwrapping_pub_key: [u8; 260]` plus the
cleared-on-reset hookup in `clear_enabled_state`.
* `build_attrs_for_aes` now `pub(super)` with a `local: bool`
parameter so both `aes_generate_key` (true) and the AES unwrap
path (false) can share the usage-bit validation logic.
* Tests: 3 smoke tests for GetUnwrappingKey (happy path, stability
across calls, no-session) and 4 smoke tests for RsaUnwrap (AES-256
with local-encrypt-vs-device-decrypt cross-check; RSA-3K both CRT
and non-CRT with returned-pub-key vs locally-derived-pub-key
bytewise comparison; no-session). 580+ DDI mock tests still pass;
emu smoke set (33) all green.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
83f14fd to
457d39e
Compare
Move `impl From<EccCurve> for usize` from the OpenSSL-only `key_ossl.rs` into the backend-agnostic `ecc/mod.rs` so it is available on the Windows CNG backend, fixing an E0277 build failure in the mbor RSA unwrap smoke test (`local_pub.curve().into()`). Update std PAL and PAL-trait docs to reflect that RSA private keys are now vaulted as raw non-CRT HSM bytes (`n || e || p || q`, `priv_key_hsm_len`) instead of PKCS#8 DER, and add the RSA analogue reference (`rsa_priv_to_hsm`) in the ECC trait docs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
943412d to
7e6e64d
Compare
| // recovered KEK is itself only 16 / 24 / 32 bytes — any other | ||
| // length is a host contract violation (or corrupted blob). | ||
| let label_empty = pal.dma_alloc(io, 0)?; | ||
| let kek_buf = pal.dma_alloc(io, rsa_key_size.max_oaep_message(hash_algo))?; |
There was a problem hiding this comment.
can we decrypt in place instead of doing an alloc?
| }) | ||
| .await | ||
| .map_err(|_| HsmError::RsaUnwrapOaepDecodeFailed)?; | ||
| if !matches!(kek_len, 16 | 24 | 32) { |
There was a problem hiding this comment.
we should limit kek to 32 only
| _ => return Err(HsmError::InvalidArg), | ||
| }; | ||
|
|
||
| let attrs = super::super::key_attrs::prepare_aes( |
| output: &mut DmaBuf, | ||
| ) -> HsmResult<usize> { | ||
| todo!() | ||
| use azihsm_crypto::AesKey; |
There was a problem hiding this comment.
should this be at top of the file.
| use azihsm_crypto::AesKeyWrapPadAlgo; | ||
| use azihsm_crypto::Decrypter; | ||
| use azihsm_crypto::ImportableKey; | ||
|
|
There was a problem hiding this comment.
should this be in aes driver?
| } | ||
| } | ||
|
|
||
| /// Reverse of [`to_ecc_curve`]. |
There was a problem hiding this comment.
This belongs in the driver.
| pk.to_hsm_bytes(&mut out[..hsm_len]) | ||
| .map_err(|_| HsmError::EccExportError)?; | ||
| Ok(hsm_len) | ||
| } |
There was a problem hiding this comment.
we should not be doing der decode in pal
| // fixed-width wire-LE slot. Right-align (leading-zero pad) | ||
| // before reversal so the on-wire layout is independent of how | ||
| // many leading zero bytes OpenSSL stripped. | ||
| let mut n_be = [0u8; 512]; |
There was a problem hiding this comment.
this will blow up async state
Add the firmware-side handlers for the two key-wrap DDI commands, the PAL trait surface that supports them, and matching std-PAL impls.
DDI handlers:
get_unwrapping_key: returns the partition's RSA-2048 unwrap key (vault id, wire-format pub key, empty masked_key placeholder). Lazy generation under the partition lock so concurrent first-call races never produce two competing keys; cached on partition state across sessions, regenerated on partition reset.rsa_unwrap: takes anOAEP(KEK) || AES-KWP(payload)wrapped blob, RSA-OAEP-decrypts the KEK with the partition's unwrap key, AES-KWP-unwraps the payload with the KEK, and imports the recovered key into the vault. Supports AES (16/24/32 byte) and RSA (2k/3k/4k, CRT and non-CRT) imports; ECC import returnsUnsupportedCmd. Spec error codes (RsaUnwrapOaepDecodeFailed,RsaUnwrapAesUnwrapFailed,RsaUnwrapInvalidRequest,RsaUnwrapInvalidKek) are mapped from the underlying crypto failures.PAL trait additions (
HsmRsa,HsmPart):rsa_gen_keypairrefactored to the query-alloc-use shape (typoras_\u2192rsa_, takes scoped allocator, returns(priv_actual, pub_actual)) — the only existing caller path (get_unwrapping_key) uses the new shape from day one.rsa_priv_key_size(io, key)introspects an imported priv key.rsa_priv_pub_key(io, key, pub_out)query-alloc-use extractor for the wire-format pub key from a serialized priv (lets the RSA-import path populate the response's optionalpub_keyso callers can verify the import bytewise without an extra DDI round-trip).part_unwrapping_pub_keygetter / setter for the cached wire-format unwrap pub key (mirrors the existingpart_establish_cred_pub_keypattern).HsmRsaKeygainspub_wire_len,pub_exp_len, andpriv_key_der_maxhelpers.std PAL:
rsa_gen_keypairemits wire-LEn || ewith deterministic fixed-width padding (256 + 4 = 260 bytes for RSA-2048).rsa_oaep_decryptimplemented \u2014 LE\u2192BE flip on the ciphertext (host wraps under PKA-native LE), OpenSSL OAEP decrypt into a modulus-sized scratch, copy actual plaintext into caller's slot.aes_kwp_unwrapimplemented (delegates toAesKeyWrapPadAlgo::decrypt).rsa_priv_key_size/rsa_priv_pub_keyimplemented (parse PKCS#8 DER, read modulus / extract n,e in wire format).unwrapping_pub_key: [u8; 260]plus the cleared-on-reset hookup inclear_enabled_state.build_attrs_for_aesnowpub(super)with alocal: boolparameter so bothaes_generate_key(true) and the AES unwrap path (false) can share the usage-bit validation logic.Tests: 3 smoke tests for GetUnwrappingKey (happy path, stability across calls, no-session) and 4 smoke tests for RsaUnwrap (AES-256 with local-encrypt-vs-device-decrypt cross-check; RSA-3K both CRT and non-CRT with returned-pub-key vs locally-derived-pub-key bytewise comparison; no-session). 580+ DDI mock tests still pass; emu smoke set (33) all green.