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
14 changes: 14 additions & 0 deletions api/lib/src/algo/ecc/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ impl HsmEccPrivateKey {
// Supported usage flags for ECC private keys in this layer.
let supported_flag = HsmKeyFlags::SIGN | HsmKeyFlags::DERIVE;

// VERIFY is a public-key capability and must not be supplied on private-key props.
if props.can_verify() {
Err(HsmError::InvalidKeyProps)?;
}

// ECC private key must be either a signing key or a derivation key (but not both).
if props.can_sign() == props.can_derive() {
Err(HsmError::InvalidKeyProps)?;
Expand Down Expand Up @@ -89,6 +94,10 @@ impl HsmEccPrivateKey {
Err(HsmError::InvalidKeyProps)?;
}

if priv_props.is_session() != pub_props.is_session() {
Err(HsmError::InvalidKeyProps)?;
}

// private key must be able to sign if public key can verify
if pub_props.can_verify() && !priv_props.can_sign() {
Err(HsmError::InvalidKeyProps)?;
Expand Down Expand Up @@ -120,6 +129,11 @@ impl HsmEccPublicKey {
// Supported usage flags for ECC public keys in this layer.
let supported_flag = HsmKeyFlags::VERIFY | HsmKeyFlags::DERIVE;

// SIGN is a private-key capability and must not be supplied on public-key props.
if props.can_sign() {
Err(HsmError::InvalidKeyProps)?;
}

//check if public key is verifiable or derivable
if props.can_verify() == props.can_derive() {
Err(HsmError::InvalidKeyProps)?;
Expand Down
2 changes: 1 addition & 1 deletion api/lib/src/algo/ecc/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ impl HsmSignOp for HsmEccSignAlgo {
let Some(signature) = signature else {
return Ok(expected_len);
};
if signature.len() != expected_len {
if signature.len() < expected_len {
return Err(HsmError::BufferTooSmall);
}

Expand Down
77 changes: 76 additions & 1 deletion api/native/doc/chapter_06_key_ops.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ azihsm_status azihsm_key_gen_pair(
azihsm_algo *algo,
const azihsm_key_prop_list *priv_key_props,
const azihsm_key_prop_list *pub_key_props,
azihsm_handle *priv_key_handle
azihsm_handle *priv_key_handle,
azihsm_handle *pub_key_handle
);
```
Expand All @@ -52,6 +52,48 @@ azihsm_status azihsm_key_gen_pair(
| [out] priv_key_handle | [azihsm_handle *](#azihsm_handle) | key handle for generated private key |
| [out] pub_key_handle | [azihsm_handle *](#azihsm_handle) | key handle for generated public key |

**Notes**

- `priv_key_handle` and `pub_key_handle` must point to distinct output addresses.
- Passing the same pointer for both outputs returns `AZIHSM_STATUS_INVALID_ARGUMENT`.

**Returns**

`AZIHSM_STATUS_SUCCESS` on success, error code otherwise

## azihsm_key_unwrap_pair

Unwrap a key pair.

```cpp
azihsm_status azihsm_key_unwrap_pair(
azihsm_algo *algo,
azihsm_handle unwrapping_key,
const azihsm_buffer *wrapped_key,
const azihsm_key_prop_list *priv_key_props,
const azihsm_key_prop_list *pub_key_props,
azihsm_handle *priv_key_handle,
azihsm_handle *pub_key_handle
);
```

**Parameters**

| Parameter | Name | Description |
| --------------------- | ---------------------------------------------------- | ------------------------------------ |
| [in] algo | [azihsm_algo *](#azihsm_algo) | algorithm params |
| [in] unwrapping_key | [azihsm_handle](#azihsm_handle) | unwrapping key handle |
| [in] wrapped_key | [const azihsm_buffer *](#azihsm_buffer) | wrapped key pair |
| [in] priv_key_props | [const azihsm_key_prop_list*](#azihsm_key_prop_list) | private key properties |
| [in] pub_key_props | [const azihsm_key_prop_list*](#azihsm_key_prop_list) | public key properties |
| [out] priv_key_handle | [azihsm_handle *](#azihsm_handle) | key handle for unwrapped private key |
| [out] pub_key_handle | [azihsm_handle *](#azihsm_handle) | key handle for unwrapped public key |

**Notes**

- `priv_key_handle` and `pub_key_handle` must point to distinct output addresses.
- Passing the same pointer for both outputs returns `AZIHSM_STATUS_INVALID_ARGUMENT`.

**Returns**

`AZIHSM_STATUS_SUCCESS` on success, error code otherwise
Expand Down Expand Up @@ -86,6 +128,39 @@ azihsm_status azihsm_key_unwrap(

`AZIHSM_STATUS_SUCCESS` on success, error code otherwise

## azihsm_key_unmask_pair

Unmask a key pair.

```cpp
azihsm_status azihsm_key_unmask_pair(
azihsm_handle sess_handle,
azihsm_key_kind key_kind,
const azihsm_buffer *masked_key,
azihsm_handle *priv_key_handle,
azihsm_handle *pub_key_handle
);
```

**Parameters**

| Parameter | Name | Description |
| --------------------- | --------------------------------------- | ---------------------------------- |
| [in] sess_handle | [azihsm_handle](#azihsm_handle) | session handle |
| [in] key_kind | azihsm_key_kind | key kind to unmask (RSA or ECC) |
| [in] masked_key | [const azihsm_buffer *](#azihsm_buffer) | masked key pair |
| [out] priv_key_handle | [azihsm_handle *](#azihsm_handle) | key handle for unmasked private key|
| [out] pub_key_handle | [azihsm_handle *](#azihsm_handle) | key handle for unmasked public key |

**Notes**

- `priv_key_handle` and `pub_key_handle` must point to distinct output addresses.
- Passing the same pointer for both outputs returns `AZIHSM_STATUS_INVALID_ARGUMENT`.

**Returns**

`AZIHSM_STATUS_SUCCESS` on success, error code otherwise

## azihsm_key_derive

Derive a key from an existing key.
Expand Down
3 changes: 3 additions & 0 deletions api/native/doc/chapter_09_data_structs.md
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,9 @@ struct azihsm_algo_rsa_pkcs_pss_params {

RSA OAEP Algorithm parameters.

**Current SDK limitation:** `hash_algo_id` and `mgf_id` must use the same hash function.
Passing mixed OAEP hash/MGF1 combinations returns `AZIHSM_STATUS_INVALID_ARGUMENT`.

```cpp
struct azihsm_algo_rsa_pkcs_oaep_params {
azihsm_algo_id hash_algo_id;
Expand Down
6 changes: 5 additions & 1 deletion api/native/src/algo/ecc/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ use crate::AzihsmHandle;
use crate::AzihsmStatus;
use crate::HANDLE_TABLE;
use crate::handle_table::HandleType;
use crate::utils::validate_algo_params_absent;
use crate::utils::validate_output_buffer;

impl TryFrom<&AzihsmAlgo> for HsmEccKeyGenAlgo {
type Error = AzihsmStatus;

/// Converts a C FFI algorithm specification to HsmEccKeyGenAlgo.
fn try_from(_algo: &AzihsmAlgo) -> Result<Self, Self::Error> {
fn try_from(algo: &AzihsmAlgo) -> Result<Self, Self::Error> {
// EC key-pair generation has no algorithm-specific parameter struct in the C ABI.
// Enforce `params == NULL` and `len == 0` to reject malformed caller input.
validate_algo_params_absent(algo)?;
Ok(HsmEccKeyGenAlgo::default())
}
}
Expand Down
58 changes: 55 additions & 3 deletions api/native/src/algo/rsa/enc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,34 @@ pub struct AzihsmAlgoRsaAesWrapParams {
pub oaep_params: *const AzihsmAlgoRsaPkcsOaepParams,
}

impl AzihsmAlgoRsaAesWrapParams {
/// Validates RSA-AES wrap parameters at the FFI boundary.
///
/// Checks that the AES key size is a supported value (128, 192, or 256 bits),
/// dereferences the nested OAEP parameters pointer, and validates the OAEP
/// parameters.
pub(crate) fn validate(&self) -> Result<(), AzihsmStatus> {
// Validate that the AES key size is supported (128, 192, or 256 bits)
match self.aes_key_bits {
128 | 192 | 256 => {}
_ => Err(AzihsmStatus::InvalidArgument)?,
}

// Dereference and validate the nested OAEP parameters
let oaep_params = deref_ptr(self.oaep_params)?;
oaep_params.validate()
}
}

impl<'a> TryFrom<&'a AzihsmAlgo> for &'a AzihsmAlgoRsaAesWrapParams {
type Error = AzihsmStatus;

#[allow(unsafe_code)]
fn try_from(algo: &'a AzihsmAlgo) -> Result<Self, Self::Error> {
let params = validate_and_cast_algo_params::<AzihsmAlgoRsaAesWrapParams>(algo)?;

// Validate OAEP parameters pointer
validate_ptr(params.oaep_params)?;
// Validate the parameters (including nested OAEP params)
params.validate()?;

Ok(params)
}
Expand Down Expand Up @@ -98,13 +117,46 @@ pub struct AzihsmAlgoRsaPkcsOaepParams {
/// Hash algorithm identifier used for OAEP padding
pub hash_algo_id: AzihsmAlgoId,

/// MGF1 mask generation function identifier
/// MGF1 mask generation function identifier.
///
/// Current SDK limitation: must correspond to the same hash family as
/// `hash_algo_id` (e.g. SHA-256 with MGF1-SHA-256).
pub mgf1_hash_algo_id: AzihsmMgf1Id,

/// Optional label for encryption context (can be null)
pub label: *const AzihsmBuffer,
}

impl AzihsmAlgoRsaPkcsOaepParams {
/// Validates OAEP parameters at the FFI boundary.
///
/// Checks that the hash algorithm and MGF1 hash algorithm form a valid,
/// matching pair (SHA-256/256, SHA-384/384, SHA-512/512), and that the
/// optional label buffer pointer is well-formed.
///
/// Current limitation: OAEP MGF1 is not independently propagated through
/// all layers (native -> DDI -> device/sim). Enforce matching values at
/// the API boundary so behavior is explicit and deterministic.
pub(crate) fn validate(&self) -> Result<(), AzihsmStatus> {
// Enforce that hash_algo_id and mgf1_hash_algo_id are a valid, matching pair.
let hash_matches_mgf1 = matches!(
(self.hash_algo_id, self.mgf1_hash_algo_id),
(AzihsmAlgoId::Sha256, AzihsmMgf1Id::Sha256)
| (AzihsmAlgoId::Sha384, AzihsmMgf1Id::Sha384)
| (AzihsmAlgoId::Sha512, AzihsmMgf1Id::Sha512)
);

//reject mismatched or unsupported hash/MGF1 combinations.
if !hash_matches_mgf1 {
Err(AzihsmStatus::InvalidArgument)?;
}

// Validate that the label buffer pointer is well-formed (if non-null)
buffer_to_optional_slice(self.label)?;
Ok(())
}
}

impl TryFrom<AzihsmHandle> for HsmRsaPublicKey {
type Error = AzihsmStatus;

Expand Down
39 changes: 22 additions & 17 deletions api/native/src/algo/rsa/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,34 @@ pub struct AzihsmAlgoRsaAesKeyWrapParams {
pub oaep_params: *const AzihsmAlgoRsaPkcsOaepParams,
}

impl AzihsmAlgoRsaAesKeyWrapParams {
/// Validates RSA-AES key wrap parameters at the FFI boundary.
///
/// Checks that the AES key size is a supported value (128, 192, or 256 bits),
/// dereferences the nested OAEP parameters pointer, and validates the OAEP
/// parameters.
pub(crate) fn validate(&self) -> Result<(), AzihsmStatus> {
// Validate AES key size
match self.aes_key_bits {
128 | 192 | 256 => {}
_ => Err(AzihsmStatus::InvalidArgument)?,
}

// Dereference and validate the nested OAEP parameters
let oaep_params = deref_ptr(self.oaep_params)?;
oaep_params.validate()
}
}

impl<'a> TryFrom<&'a AzihsmAlgo> for &'a AzihsmAlgoRsaAesKeyWrapParams {
type Error = AzihsmStatus;

#[allow(unsafe_code)]
fn try_from(algo: &'a AzihsmAlgo) -> Result<Self, Self::Error> {
let params = validate_and_cast_algo_params::<AzihsmAlgoRsaAesKeyWrapParams>(algo)?;

// Validate OAEP parameters pointer
validate_ptr(params.oaep_params)?;
//validate parameter
params.validate()?;

Ok(params)
}
Expand All @@ -47,21 +66,7 @@ impl<'a> TryFrom<&'a AzihsmAlgo> for &'a AzihsmAlgoRsaPkcsOaepParams {
fn try_from(algo: &'a AzihsmAlgo) -> Result<Self, Self::Error> {
let params = validate_and_cast_algo_params::<AzihsmAlgoRsaPkcsOaepParams>(algo)?;

// Validate hash algorithm ID
match params.hash_algo_id {
AzihsmAlgoId::Sha256 | AzihsmAlgoId::Sha384 | AzihsmAlgoId::Sha512 => {}
_ => Err(AzihsmStatus::InvalidArgument)?,
}

// Label is optional - if provided, validate the buffer
if !params.label.is_null() {
let label_buf = deref_ptr(params.label)?;

// Validate the buffer has valid data pointer if length > 0
if label_buf.len > 0 {
validate_ptr(label_buf.ptr)?;
}
}
params.validate()?;

Ok(params)
}
Expand Down
12 changes: 6 additions & 6 deletions api/native/src/key_mgmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ pub unsafe extern "C" fn azihsm_key_gen_pair(
pub_key_handle: *mut AzihsmHandle,
) -> AzihsmStatus {
abi_boundary(|| {
validate_ptr(pub_key_handle)?;
validate_ptr(priv_key_handle)?;
//check that output handle pointers are valid and distinct before doing any work
validate_output_handle_ptrs(priv_key_handle, pub_key_handle)?;

let algo = deref_ptr(algo)?;
let props = deref_ptr(pub_key_props)?;
Expand Down Expand Up @@ -301,8 +301,8 @@ pub unsafe extern "C" fn azihsm_key_unwrap_pair(
pub_key_handle: *mut AzihsmHandle,
) -> AzihsmStatus {
abi_boundary(|| {
validate_ptr(priv_key_handle)?;
validate_ptr(pub_key_handle)?;
//check that output handle pointers are valid and distinct before doing any work
validate_output_handle_ptrs(priv_key_handle, pub_key_handle)?;

let algo = deref_mut_ptr(algo)?;
let wrapped_key = deref_ptr(wrapped_key)?;
Expand Down Expand Up @@ -409,8 +409,8 @@ pub unsafe extern "C" fn azihsm_key_unmask_pair(
pub_key_handle: *mut AzihsmHandle,
) -> AzihsmStatus {
abi_boundary(|| {
validate_ptr(priv_key_handle)?;
validate_ptr(pub_key_handle)?;
//check that output handle pointers are valid and distinct before doing any work
validate_output_handle_ptrs(priv_key_handle, pub_key_handle)?;

let session = HsmSession::try_from(sess_handle)?;
let masked_key = deref_ptr(masked_key)?;
Expand Down
37 changes: 37 additions & 0 deletions api/native/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,24 @@ pub(crate) fn validate_algo_params<T>(algo: &AzihsmAlgo) -> Result<(), AzihsmSta
validate_ptr(algo.params)
}

/// Validates that an algorithm descriptor intentionally carries no parameter payload.
///
/// Some algorithm IDs are defined by the C ABI as "parameterless" (there is no
/// corresponding `struct` to parse). For those algorithms, callers must pass:
/// - `algo.params == NULL`
/// - `algo.len == 0`
///
/// This is a strict ABI-shape check at the FFI boundary. It ensures malformed
/// pointer/length combinations are rejected early with `InvalidArgument` instead
/// of being silently ignored.
pub(crate) fn validate_algo_params_absent(algo: &AzihsmAlgo) -> Result<(), AzihsmStatus> {
if !algo.params.is_null() || algo.len != 0 {
Err(AzihsmStatus::InvalidArgument)?;
}

Ok(())
}

pub(crate) fn validate_and_cast_algo_params<T>(algo: &AzihsmAlgo) -> Result<&T, AzihsmStatus> {
validate_algo_params::<T>(algo)?;
cast_ptr::<T>(algo.params)
Expand Down Expand Up @@ -73,6 +91,25 @@ pub(crate) fn assign_ptr<T>(ptr: *mut T, value: T) -> Result<(), AzihsmStatus> {
Ok(())
}

/// Validates that two output handle pointers are non-null and distinct.
///
/// Used at the FFI boundary for APIs that return a key pair (private + public).
/// Rejects null pointers and the case where the caller passes the same pointer
/// for both outputs, which would silently overwrite the first handle.
pub(crate) fn validate_output_handle_ptrs(
first: *mut AzihsmHandle,
second: *mut AzihsmHandle,
) -> Result<(), AzihsmStatus> {
validate_ptr(first)?;
validate_ptr(second)?;

if std::ptr::eq(first, second) {
Err(AzihsmStatus::InvalidArgument)?;
}

Ok(())
}

/// Validate and prepare the caller-provided output buffer.
///
/// - If the buffer is large enough, returns a mutable slice to write into.
Expand Down
Loading
Loading