Skip to content

feat(fw): implement DDI GetUnwrappingKey + RsaUnwrap handlers#422

Open
jaygmsft wants to merge 3 commits into
mainfrom
user/jayg/get_unwrap_key_and_rsa_unwrap
Open

feat(fw): implement DDI GetUnwrappingKey + RsaUnwrap handlers#422
jaygmsft wants to merge 3 commits into
mainfrom
user/jayg/get_unwrap_key_and_rsa_unwrap

Conversation

@jaygmsft

@jaygmsft jaygmsft commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

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.

Copilot AI review requested due to automatic review settings June 3, 2026 04:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) and RsaUnwrap (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.

Comment thread fw/core/lib/src/ddi/mbor/rsa_unwrap.rs Outdated
Comment thread fw/core/lib/src/ddi/mbor/rsa_unwrap.rs Outdated
Comment thread fw/core/lib/src/ddi/mbor/get_unwrapping_key.rs
Comment thread fw/core/lib/src/ddi/mbor/rsa_unwrap.rs Outdated
Comment thread fw/core/lib/src/ddi/mbor/rsa_unwrap.rs Outdated
Comment thread ddi/mbor/types/tests/integration/rsa_unwrap_smoke.rs Dismissed
@jaygmsft jaygmsft force-pushed the user/jayg/get_unwrap_key_and_rsa_unwrap branch from 75d8d8a to 4ef9a9d Compare June 4, 2026 17:25
@jaygmsft jaygmsft marked this pull request as draft June 4, 2026 17:26
Copilot AI review requested due to automatic review settings June 4, 2026 20:38
@jaygmsft jaygmsft force-pushed the user/jayg/get_unwrap_key_and_rsa_unwrap branch from 4ef9a9d to b66a8a5 Compare June 4, 2026 20:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 2 comments.

Comment thread fw/plat/std/pal/src/aes.rs
Comment thread fw/core/lib/src/ddi/mbor/rsa_unwrap/import_rsa.rs Outdated
@jaygmsft jaygmsft force-pushed the user/jayg/get_unwrap_key_and_rsa_unwrap branch from b66a8a5 to 83f14fd Compare June 4, 2026 21:03
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>
Copilot AI review requested due to automatic review settings June 5, 2026 23:48
@jaygmsft jaygmsft force-pushed the user/jayg/get_unwrap_key_and_rsa_unwrap branch from 83f14fd to 457d39e Compare June 5, 2026 23:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 7 comments.

Comment thread fw/core/lib/src/ddi/mbor/get_unwrapping_key.rs
Comment thread fw/plat/std/pal/src/rsa.rs Outdated
Comment thread fw/pal/traits/src/crypto/rsa.rs Outdated
Comment thread fw/pal/traits/src/crypto/ecc.rs Outdated
Comment thread fw/core/lib/src/ddi/mbor/key_attrs.rs
Comment thread fw/core/lib/src/ddi/mbor/key_attrs.rs
Comment thread fw/pal/traits/src/part.rs
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>
@jaygmsft jaygmsft marked this pull request as ready for review June 6, 2026 00:11
Copilot AI review requested due to automatic review settings June 6, 2026 00:11
@jaygmsft jaygmsft enabled auto-merge (squash) June 6, 2026 00:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 1 comment.

Comment thread fw/core/lib/src/ddi/mbor/key_attrs.rs
Copilot AI review requested due to automatic review settings June 6, 2026 00:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 3 comments.

Comment thread fw/core/lib/src/ddi/mbor/get_unwrapping_key.rs Outdated
Comment thread fw/plat/std/pal/src/aes.rs
Comment thread fw/plat/std/pal/src/rsa.rs
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@jaygmsft jaygmsft force-pushed the user/jayg/get_unwrap_key_and_rsa_unwrap branch from 943412d to 7e6e64d Compare June 6, 2026 00:37
// 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))?;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we decrypt in place instead of doing an alloc?

})
.await
.map_err(|_| HsmError::RsaUnwrapOaepDecodeFailed)?;
if !matches!(kek_len, 16 | 24 | 32) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should limit kek to 32 only

_ => return Err(HsmError::InvalidArg),
};

let attrs = super::super::key_attrs::prepare_aes(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is interesting

output: &mut DmaBuf,
) -> HsmResult<usize> {
todo!()
use azihsm_crypto::AesKey;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be at top of the file.

use azihsm_crypto::AesKeyWrapPadAlgo;
use azihsm_crypto::Decrypter;
use azihsm_crypto::ImportableKey;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be in aes driver?

}
}

/// Reverse of [`to_ecc_curve`].

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This belongs in the driver.

pk.to_hsm_bytes(&mut out[..hsm_len])
.map_err(|_| HsmError::EccExportError)?;
Ok(hsm_len)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will blow up async state

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants