use std::{collections::HashSet, sync::Arc};
use KmipKeyMaterial::TransparentRSAPublicKey;
use async_trait::async_trait;
use cosmian_kmip::{
SafeBigInt,
kmip_0::kmip_types::{CryptographicUsageMask, State},
kmip_2_1::{
extra::tagging::{SYSTEM_TAG_PRIVATE_KEY, SYSTEM_TAG_PUBLIC_KEY, SYSTEM_TAG_SYMMETRIC_KEY},
kmip_attributes::Attributes,
kmip_data_structures::{KeyBlock, KeyMaterial as KmipKeyMaterial, KeyValue},
kmip_objects::{Object, ObjectType, PrivateKey, PublicKey, SymmetricKey},
kmip_types::{CryptographicAlgorithm, KeyFormatType},
},
};
use cosmian_logger::{debug, error, trace, warn};
use num_bigint_dig::{BigInt, Sign};
use zeroize::Zeroizing;
use crate::{
AtomicOperation, CryptoAlgorithm, CryptoOracle, HSM, HsmKeyAlgorithm, HsmKeypairAlgorithm,
HsmObject, HsmObjectFilter, InterfaceError, InterfaceResult, KeyMaterial, KeyType,
ObjectWithMetadata, ObjectsStore, SigningAlgorithm,
crypto_oracle::{EncryptedContent, KeyMetadata},
};
#[derive(Clone)]
pub struct HsmStore {
hsm: Arc<dyn HSM + Send + Sync>,
hsm_admin: Vec<String>,
vendor_id: String,
prefix: String,
}
impl HsmStore {
pub fn new(
hsm: Arc<dyn HSM + Send + Sync>,
hsm_admin: &[String],
vendor_id: &str,
prefix: &str,
) -> Self {
Self {
hsm,
hsm_admin: hsm_admin.to_owned(),
vendor_id: vendor_id.to_owned(),
prefix: prefix.to_owned(),
}
}
fn is_admin(&self, user: &str) -> bool {
self.hsm_admin.iter().any(|a| a == "*" || a == user)
}
fn owner_name(&self) -> &str {
self.hsm_admin
.iter()
.find(|a| a.as_str() != "*")
.map_or("admin", String::as_str)
}
}
#[async_trait(?Send)]
impl ObjectsStore for HsmStore {
async fn create(
&self,
uid: Option<String>,
owner: &str,
object: &Object,
attributes: &Attributes,
_tags: &HashSet<String>,
) -> InterfaceResult<String> {
if !self.is_admin(owner) {
return Err(InterfaceError::Unauthorized(
"Only the HSM Admin can create HSM objects".to_owned(),
));
}
let uid = uid.as_ref().ok_or_else(|| {
InterfaceError::InvalidRequest(
format!("An HSM create request must have a uid in the form of 'hsm::<slot_id>::<key_id>'. Got {uid:?}"
))
})?;
let (slot_id, key_id) = parse_uid_with_prefix(uid, &self.prefix)?;
if object.object_type() != ObjectType::SymmetricKey {
return Err(InterfaceError::InvalidRequest(
"Only symmetric keys can be created on the HSM in this server".to_owned(),
));
}
let algorithm = attributes.cryptographic_algorithm.as_ref().ok_or_else(|| {
InterfaceError::InvalidRequest(
"Create: HSM keys must have a cryptographic algorithm specified".to_owned(),
)
})?;
if *algorithm != CryptographicAlgorithm::AES {
return Err(InterfaceError::InvalidRequest(
"Only AES symmetric keys can be created on the HSM in this server".to_owned(),
));
}
let key_length = attributes.cryptographic_length.as_ref().ok_or_else(|| {
InterfaceError::InvalidRequest(
"Symmetric key must have a cryptographic length specified".to_owned(),
)
})?;
self.hsm
.create_key(
slot_id,
key_id.as_bytes(),
HsmKeyAlgorithm::AES,
usize::try_from(*key_length).map_err(|e| {
InterfaceError::InvalidRequest(format!("Invalid key length: {e}"))
})?,
attributes.sensitive.unwrap_or(false),
)
.await?;
debug!("Created HSM AES Key of length {key_length} with id {uid}",);
Ok(uid.to_owned())
}
async fn retrieve(&self, uid: &str) -> InterfaceResult<Option<ObjectWithMetadata>> {
let (slot_id, key_id) = parse_uid_with_prefix(uid, &self.prefix)?;
match self.hsm.export(slot_id, key_id.as_bytes()).await {
Ok(Some(hsm_object)) => {
let mut owm =
to_object_with_metadata(&hsm_object, uid, self.owner_name(), &self.vendor_id)?;
if let Ok(Some(meta)) = self.hsm.get_key_metadata(slot_id, key_id.as_bytes()).await
{
let attrs = owm.attributes_mut();
attrs.rotate_name = meta.rotate_name;
attrs.rotate_generation = meta.rotate_generation;
if let (Some(start), Some(end)) = (meta.start_date, meta.end_date) {
let days = (end - start).whole_days();
if days > 0 {
attrs.rotate_interval = Some(days * crate::SECS_PER_DAY);
}
}
}
Ok(Some(owm))
}
Ok(None) => Ok(None),
Err(e) => {
debug!(
"HSM key {uid} export failed ({e}); falling back to metadata-only stub for \
attribute operations"
);
let meta = self
.hsm
.get_key_metadata(slot_id, key_id.as_bytes())
.await?;
let Some(meta) = meta else {
return Ok(None);
};
let attrs = build_sensitive_stub_attributes(&meta);
let object = build_sensitive_stub_object(&meta);
Ok(Some(ObjectWithMetadata::new(
uid.to_owned(),
object,
self.owner_name().to_owned(),
State::Active,
attrs,
)))
}
}
}
async fn retrieve_tags(&self, _uid: &str) -> InterfaceResult<HashSet<String>> {
Ok(HashSet::new())
}
async fn update_object(
&self,
uid: &str,
_object: &Object,
_attributes: &Attributes,
_tags: Option<&HashSet<String>>,
) -> InterfaceResult<()> {
warn!(
"ModifyAttribute/SetAttribute on HSM key {uid}: attribute update accepted but not \
persisted to PKCS#11 slot (HSM does not support KMIP attribute storage)"
);
Ok(())
}
async fn update_state(&self, _uid: &str, _state: State) -> InterfaceResult<()> {
Err(InterfaceError::InvalidRequest(
"Update state is not supported for HSMs".to_owned(),
))
}
async fn delete(&self, uid: &str) -> InterfaceResult<()> {
let (slot_id, key_id) = parse_uid_with_prefix(uid, &self.prefix)?;
self.hsm.delete(slot_id, key_id.as_bytes()).await?;
Ok(())
}
async fn atomic(
&self,
user: &str,
operations: &[AtomicOperation],
) -> InterfaceResult<Vec<String>> {
if let Some((uid, _object, attributes, _tags)) = is_rsa_keypair_creation(operations) {
debug!("Creating RSA keypair with uid: {uid}");
if !self.is_admin(user) {
return Err(InterfaceError::Unauthorized(
"Only the HSM Admin can create HSM keypairs".to_owned(),
));
}
let (slot_id, sk_id) = parse_uid_with_prefix(&uid, &self.prefix)?;
let pk_id = sk_id.clone() + SYSTEM_TAG_PUBLIC_KEY;
self.hsm
.create_keypair(
slot_id,
sk_id.as_bytes(),
pk_id.as_bytes(),
HsmKeypairAlgorithm::RSA,
usize::try_from(attributes.cryptographic_length.unwrap_or(2048)).map_err(
|e| InterfaceError::InvalidRequest(format!("Invalid key length: {e}")),
)?,
attributes.sensitive.unwrap_or_default(),
)
.await?;
return Ok(vec![
format!("{}::{slot_id}::{sk_id}", self.prefix),
format!("{}::{slot_id}::{pk_id}", self.prefix),
]);
}
if operations
.iter()
.all(|op| matches!(op, AtomicOperation::UpdateObject(_)))
{
for op in operations {
if let AtomicOperation::UpdateObject((uid, object, attrs, tags)) = op {
self.update_object(uid, object, attrs, tags.as_ref())
.await?;
}
}
return Ok(vec![]);
}
Err(InterfaceError::InvalidRequest(
"HSM atomic operations only support RSA keypair creations for now".to_owned(),
))
}
async fn is_object_owned_by(&self, _uid: &str, owner: &str) -> InterfaceResult<bool> {
let is_admin = self.is_admin(owner);
debug!("Is {owner} an HSM admin? {}", is_admin);
Ok(is_admin)
}
async fn list_uids_for_tags(
&self,
_tags: &HashSet<String>,
) -> InterfaceResult<HashSet<String>> {
Ok(HashSet::new())
}
async fn find(
&self,
researched_attributes: Option<&Attributes>,
state: Option<State>,
user: &str,
user_must_be_owner: bool,
vendor_id: &str,
) -> InterfaceResult<Vec<(String, State, Attributes)>> {
let slot_ids = self.hsm.get_available_slot_list().await?;
let mut uids = Vec::new();
if user_must_be_owner && !self.is_admin(user) {
debug!(
"User '{}' is not an HSM admin; skipping HSM keys for ownership query",
user
);
return Ok(uids);
}
let mut search_attributes = researched_attributes.cloned().unwrap_or_else(|| {
debug!("No researched_attributes provided. Defaulting to empty filter attributes");
Attributes::default()
});
match check_basic_compatibility(vendor_id, &search_attributes, state) {
Ok(()) => {}
Err(e) => {
debug!("{e}");
return Ok(uids);
}
}
let object_filter = match HsmObjectFilter::try_from(&search_attributes) {
Ok(object_filter) => object_filter,
Err(e) => {
debug!("HSM find: incompatible filter, skipping HSM search: {e}");
return Ok(uids);
}
};
let key_size_filter = search_attributes.get_cryptographic_length();
let key_id_filter = match search_attributes.unique_identifier {
Some(unique_identifier) => {
let Some(str) = unique_identifier.as_str() else {
return Ok(uids);
};
Some(str.to_owned())
}
None => None,
};
for slot_id in slot_ids {
let found = self
.hsm
.find(slot_id, object_filter.clone())
.await
.unwrap_or(vec![]);
for object_id in found {
trace!("Getting metadata for: {:02X?}", object_id);
let object_meta = self
.hsm
.get_key_metadata(slot_id, &object_id)
.await
.unwrap_or_default();
if let Some(expected_key_size) = key_size_filter {
if let Some(ref meta) = object_meta {
if meta.key_length_in_bits != expected_key_size {
continue;
}
} else {
continue;
}
}
let object_string = match str::from_utf8(&object_id) {
Ok(object_string) => object_string,
Err(err) => {
error!("Failed to decode object_id {}", err);
continue;
}
};
let uid = format!("{}::{slot_id}::{object_string}", self.prefix);
trace!("Found: {uid}");
if let Some(ref wanted_id) = key_id_filter {
if !uid.eq(wanted_id) {
continue;
}
}
let attrs = build_find_attributes(&object_meta, &object_filter);
uids.push((uid, State::Active, attrs));
}
}
Ok(uids)
}
async fn find_due_for_rotation(
&self,
now: time::OffsetDateTime,
) -> InterfaceResult<Vec<(String, String)>> {
let today = now.date();
let slot_ids = self.hsm.get_available_slot_list().await?;
let mut due_uids = Vec::new();
for slot_id in slot_ids {
let found = self
.hsm
.find(slot_id, HsmObjectFilter::Any)
.await
.unwrap_or_default();
for object_id in found {
let Some(meta) = self
.hsm
.get_key_metadata(slot_id, &object_id)
.await
.unwrap_or_default()
else {
continue;
};
let Some(end_date) = meta.end_date else {
continue;
};
if today >= end_date {
let Ok(object_string) = std::str::from_utf8(&object_id) else {
continue;
};
let uid = format!("{}::{slot_id}::{object_string}", self.prefix);
due_uids.push((uid, String::new()));
}
}
}
Ok(due_uids)
}
async fn find_by_rotate_name(
&self,
name: &str,
generation: Option<i32>,
_owner: &str,
) -> InterfaceResult<Vec<(String, Attributes)>> {
let slot_ids = self.hsm.get_available_slot_list().await?;
let mut results = Vec::new();
for slot_id in slot_ids {
let found = self
.hsm
.find(slot_id, HsmObjectFilter::Any)
.await
.unwrap_or_default();
for object_id in found {
let Some(meta) = self
.hsm
.get_key_metadata(slot_id, &object_id)
.await
.unwrap_or_default()
else {
continue;
};
let Some(ref key_rotate_name) = meta.rotate_name else {
continue;
};
if key_rotate_name != name {
continue;
}
if let Some(gen_filter) = generation {
if meta.rotate_generation != Some(gen_filter) {
continue;
}
}
let Ok(object_string) = std::str::from_utf8(&object_id) else {
continue;
};
let uid = format!("{}::{slot_id}::{object_string}", self.prefix);
let attrs = build_keyset_attributes(&meta);
results.push((uid, attrs));
}
}
Ok(results)
}
async fn set_key_label(&self, uid: &str, label: &str) -> InterfaceResult<()> {
let (slot_id, key_id) = parse_uid_with_prefix(uid, &self.prefix)?;
self.hsm
.set_key_label(slot_id, key_id.as_bytes(), label)
.await
}
async fn set_key_rotation_dates(
&self,
uid: &str,
start_date: Option<time::Date>,
end_date: Option<time::Date>,
) -> InterfaceResult<()> {
let (slot_id, key_id) = parse_uid_with_prefix(uid, &self.prefix)?;
self.hsm
.set_key_dates(slot_id, key_id.as_bytes(), start_date, end_date)
.await
}
async fn count_all_non_destroyed(&self) -> InterfaceResult<u64> {
self.count_non_destroyed_keys().await
}
async fn count_non_destroyed_keys(&self) -> InterfaceResult<u64> {
let slot_ids = self
.hsm
.get_available_slot_list()
.await
.unwrap_or_else(|e| {
warn!("HSM count_non_destroyed_keys: failed to list slots: {e}");
vec![]
});
let mut total: u64 = 0;
for slot_id in slot_ids {
match self.hsm.find(slot_id, HsmObjectFilter::Any).await {
Ok(keys) => {
total = total.saturating_add(u64::try_from(keys.len()).unwrap_or(u64::MAX));
}
Err(e) => {
debug!("HSM count_non_destroyed_keys: slot {slot_id} query failed: {e}");
}
}
}
Ok(total)
}
async fn find_wrapped_by(
&self,
_wrapping_key_uid: &str,
_user: &str,
) -> InterfaceResult<Vec<(String, State, Attributes)>> {
Ok(vec![])
}
}
#[async_trait]
impl CryptoOracle for HsmStore {
async fn encrypt(
&self,
uid: &str,
data: &[u8],
cryptographic_algorithm: Option<CryptoAlgorithm>,
authenticated_encryption_additional_data: Option<&[u8]>,
) -> InterfaceResult<EncryptedContent> {
if authenticated_encryption_additional_data.is_some() {
return Err(InterfaceError::InvalidRequest(
"Additional authenticated data are not supported on HSMs for now".to_owned(),
));
}
let (mut slot_id, mut key_id) = parse_uid_with_prefix(uid, &self.prefix)?;
let supported_algorithms = self.hsm.get_supported_algorithms(slot_id).await?;
let cryptographic_algorithm = if let Some(ca) = cryptographic_algorithm {
ca
} else {
debug!("Using default algorithm to encrypt");
match self.hsm.get_key_type(slot_id, key_id.as_bytes()).await? {
None => {
return Err(InterfaceError::InvalidRequest(format!(
"The key type of key: {uid}, cannot be determined"
)));
}
Some(key_type) => match key_type {
KeyType::AesKey => CryptoAlgorithm::get_aes_algorithm(&supported_algorithms)?,
KeyType::RsaPublicKey => {
CryptoAlgorithm::get_rsa_algorithm(&supported_algorithms)?
}
KeyType::RsaPrivateKey => {
let pk_uid = format!("{uid}_pk");
debug!(
"encrypt: an RSA private key {uid} was specified. Trying to use \
public key {pk_uid} for encryption"
);
(slot_id, key_id) = parse_uid_with_prefix(&pk_uid, &self.prefix)?;
self.hsm
.get_key_type(slot_id, key_id.as_bytes())
.await?
.ok_or_else(|| {
InterfaceError::InvalidRequest(format!(
"The key {uid} is a private key, but no public key {pk_uid} \
is available"
))
})?;
CryptoAlgorithm::get_rsa_algorithm(&supported_algorithms)?
}
},
}
};
self.hsm
.encrypt(slot_id, key_id.as_bytes(), cryptographic_algorithm, data)
.await
}
async fn decrypt(
&self,
uid: &str,
data: &[u8],
cryptographic_algorithm: Option<CryptoAlgorithm>,
authenticated_encryption_additional_data: Option<&[u8]>,
) -> InterfaceResult<Zeroizing<Vec<u8>>> {
if authenticated_encryption_additional_data.is_some() {
return Err(InterfaceError::InvalidRequest(
"Additional authenticated data are not supported on HSMs for now".to_owned(),
));
}
let (slot_id, key_id) = parse_uid_with_prefix(uid, &self.prefix)?;
let supported_algorithms = self.hsm.get_supported_algorithms(slot_id).await?;
let cryptographic_algorithm = if let Some(ca) = cryptographic_algorithm {
ca
} else {
debug!("Using default algorithm to decrypt");
match self.hsm.get_key_type(slot_id, key_id.as_bytes()).await? {
None => {
return Err(InterfaceError::InvalidRequest(
"The key {}type is not known".to_owned(),
));
}
Some(key_type) => match key_type {
KeyType::AesKey => CryptoAlgorithm::get_aes_algorithm(&supported_algorithms)?,
KeyType::RsaPrivateKey => {
CryptoAlgorithm::get_rsa_algorithm(&supported_algorithms)?
}
KeyType::RsaPublicKey => {
return Err(InterfaceError::Default(
"An RSA public key cannot be used to decrypt".to_owned(),
));
}
},
}
};
self.hsm
.decrypt(slot_id, key_id.as_bytes(), cryptographic_algorithm, data)
.await
}
async fn get_key_type(&self, uid: &str) -> InterfaceResult<Option<KeyType>> {
let (slot_id, key_id) = parse_uid_with_prefix(uid, &self.prefix)?;
self.hsm.get_key_type(slot_id, key_id.as_bytes()).await
}
async fn get_key_metadata(&self, uid: &str) -> InterfaceResult<Option<KeyMetadata>> {
let (slot_id, key_id) = parse_uid_with_prefix(uid, &self.prefix)?;
self.hsm.get_key_metadata(slot_id, key_id.as_bytes()).await
}
async fn sign(
&self,
uid: &str,
data: &[u8],
cryptographic_parameters: Option<
&cosmian_kmip::kmip_2_1::kmip_types::CryptographicParameters,
>,
) -> InterfaceResult<Vec<u8>> {
let (slot_id, key_id) = parse_uid_with_prefix(uid, &self.prefix)?;
let key_type = self.hsm.get_key_type(slot_id, key_id.as_bytes()).await?;
match key_type {
Some(KeyType::RsaPrivateKey) => {}
Some(other) => {
return Err(InterfaceError::InvalidRequest(format!(
"Sign: key {uid} is a {other:?}, expected an RSA private key"
)));
}
None => {
return Err(InterfaceError::InvalidRequest(format!(
"Sign: key {uid} not found on the HSM"
)));
}
}
let algorithm = SigningAlgorithm::from_kmip(cryptographic_parameters)?;
debug!("sign: using algorithm {algorithm:?} for key {uid}");
self.hsm
.sign(slot_id, key_id.as_bytes(), algorithm, data)
.await
}
async fn signature_verify(
&self,
uid: &str,
_data: &[u8],
_signature: &[u8],
_cryptographic_parameters: Option<
&cosmian_kmip::kmip_2_1::kmip_types::CryptographicParameters,
>,
) -> InterfaceResult<bool> {
Err(InterfaceError::NotSupported(format!(
"SignatureVerify via HSM is not yet implemented for key: {uid}"
)))
}
async fn mac(
&self,
uid: &str,
_data: &[u8],
_cryptographic_parameters: Option<
&cosmian_kmip::kmip_2_1::kmip_types::CryptographicParameters,
>,
) -> InterfaceResult<Vec<u8>> {
Err(InterfaceError::NotSupported(format!(
"MAC via HSM is not yet implemented for key: {uid}"
)))
}
async fn mac_verify(
&self,
uid: &str,
_data: &[u8],
_mac_data: &[u8],
_cryptographic_parameters: Option<
&cosmian_kmip::kmip_2_1::kmip_types::CryptographicParameters,
>,
) -> InterfaceResult<bool> {
Err(InterfaceError::NotSupported(format!(
"MACVerify via HSM is not yet implemented for key: {uid}"
)))
}
}
fn build_sensitive_stub_attributes(meta: &KeyMetadata) -> Attributes {
let (algorithm, obj_type, usage_mask, key_format_type) = match meta.key_type {
KeyType::AesKey => (
CryptographicAlgorithm::AES,
ObjectType::SymmetricKey,
CryptographicUsageMask::Encrypt
| CryptographicUsageMask::Decrypt
| CryptographicUsageMask::WrapKey
| CryptographicUsageMask::UnwrapKey
| CryptographicUsageMask::KeyAgreement,
KeyFormatType::Raw,
),
KeyType::RsaPrivateKey => (
CryptographicAlgorithm::RSA,
ObjectType::PrivateKey,
CryptographicUsageMask::Decrypt
| CryptographicUsageMask::UnwrapKey
| CryptographicUsageMask::Sign,
KeyFormatType::PKCS1,
),
KeyType::RsaPublicKey => (
CryptographicAlgorithm::RSA,
ObjectType::PublicKey,
CryptographicUsageMask::Encrypt
| CryptographicUsageMask::WrapKey
| CryptographicUsageMask::Verify,
KeyFormatType::PKCS1,
),
};
let rotate_interval = match (meta.start_date, meta.end_date) {
(Some(start), Some(end)) => {
let days = (end - start).whole_days();
if days > 0 {
Some(days * crate::SECS_PER_DAY)
} else {
None
}
}
_ => None,
};
Attributes {
cryptographic_algorithm: Some(algorithm),
cryptographic_length: Some(i32::try_from(meta.key_length_in_bits).unwrap_or_default()),
object_type: Some(obj_type),
cryptographic_usage_mask: Some(usage_mask),
key_format_type: Some(key_format_type),
sensitive: Some(true),
rotate_name: meta.rotate_name.clone(),
rotate_generation: meta.rotate_generation,
rotate_interval,
..Attributes::default()
}
}
fn build_sensitive_stub_object(meta: &KeyMetadata) -> Object {
let length = i32::try_from(meta.key_length_in_bits).unwrap_or_default();
match meta.key_type {
KeyType::AesKey => {
let attributes = Attributes {
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
cryptographic_length: Some(length),
object_type: Some(ObjectType::SymmetricKey),
sensitive: Some(true),
..Attributes::default()
};
Object::SymmetricKey(SymmetricKey {
key_block: KeyBlock {
key_format_type: KeyFormatType::TransparentSymmetricKey,
key_compression_type: None,
key_value: Some(KeyValue::Structure {
key_material: KmipKeyMaterial::TransparentSymmetricKey {
key: zeroize::Zeroizing::new(vec![]),
},
attributes: Some(attributes),
}),
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
cryptographic_length: Some(length),
key_wrapping_data: None,
},
})
}
KeyType::RsaPrivateKey | KeyType::RsaPublicKey => {
let obj_type = if meta.key_type == KeyType::RsaPrivateKey {
ObjectType::PrivateKey
} else {
ObjectType::PublicKey
};
let attributes = Attributes {
cryptographic_algorithm: Some(CryptographicAlgorithm::RSA),
cryptographic_length: Some(length),
object_type: Some(obj_type),
sensitive: Some(true),
..Attributes::default()
};
Object::SymmetricKey(SymmetricKey {
key_block: KeyBlock {
key_format_type: KeyFormatType::TransparentSymmetricKey,
key_compression_type: None,
key_value: Some(KeyValue::Structure {
key_material: KmipKeyMaterial::TransparentSymmetricKey {
key: zeroize::Zeroizing::new(vec![]),
},
attributes: Some(attributes),
}),
cryptographic_algorithm: Some(CryptographicAlgorithm::RSA),
cryptographic_length: Some(length),
key_wrapping_data: None,
},
})
}
}
}
fn build_keyset_attributes(meta: &KeyMetadata) -> Attributes {
let mut attrs = build_find_attributes(&Some(meta.clone()), &HsmObjectFilter::Any);
attrs.rotate_name.clone_from(&meta.rotate_name);
attrs.rotate_generation = meta.rotate_generation;
attrs
}
fn build_find_attributes(meta: &Option<KeyMetadata>, filter: &HsmObjectFilter) -> Attributes {
let mut attrs = Attributes::default();
if let Some(m) = meta {
attrs.cryptographic_length = Some(i32::try_from(m.key_length_in_bits).unwrap_or_default());
match m.key_type {
KeyType::AesKey => {
attrs.cryptographic_algorithm = Some(CryptographicAlgorithm::AES);
attrs.object_type = Some(ObjectType::SymmetricKey);
attrs.key_format_type = Some(KeyFormatType::Raw);
}
KeyType::RsaPrivateKey => {
attrs.cryptographic_algorithm = Some(CryptographicAlgorithm::RSA);
attrs.object_type = Some(ObjectType::PrivateKey);
attrs.key_format_type = Some(KeyFormatType::PKCS1);
}
KeyType::RsaPublicKey => {
attrs.cryptographic_algorithm = Some(CryptographicAlgorithm::RSA);
attrs.object_type = Some(ObjectType::PublicKey);
attrs.key_format_type = Some(KeyFormatType::PKCS1);
}
}
} else {
match filter {
HsmObjectFilter::AesKey => {
attrs.cryptographic_algorithm = Some(CryptographicAlgorithm::AES);
attrs.object_type = Some(ObjectType::SymmetricKey);
attrs.key_format_type = Some(KeyFormatType::Raw);
}
HsmObjectFilter::RsaKey => {
attrs.cryptographic_algorithm = Some(CryptographicAlgorithm::RSA);
}
HsmObjectFilter::RsaPrivateKey => {
attrs.cryptographic_algorithm = Some(CryptographicAlgorithm::RSA);
attrs.object_type = Some(ObjectType::PrivateKey);
attrs.key_format_type = Some(KeyFormatType::PKCS1);
}
HsmObjectFilter::RsaPublicKey => {
attrs.cryptographic_algorithm = Some(CryptographicAlgorithm::RSA);
attrs.object_type = Some(ObjectType::PublicKey);
attrs.key_format_type = Some(KeyFormatType::PKCS1);
}
HsmObjectFilter::Any => {}
}
}
attrs
}
fn check_basic_compatibility(
vendor_id: &str,
researched_attributes: &Attributes,
state: Option<State>,
) -> InterfaceResult<()> {
if let Some(s) = state {
if s != State::Active {
return Err(InterfaceError::Default(format!(
"Unsupported state for HSMs: expected Active, got {s:?}"
)));
}
}
if researched_attributes.link.is_some() {
return Err(InterfaceError::Default(
"Unsupported attribute for HSMs: link".to_owned(),
));
}
if !researched_attributes.get_tags(vendor_id).is_empty() {
return Err(InterfaceError::Default(
"Unsupported attribute for HSMs: tags".to_owned(),
));
}
if researched_attributes.object_group.is_some() {
return Err(InterfaceError::Default(
"Unsupported attribute for HSMs: object_group".to_owned(),
));
}
if researched_attributes.object_group_member.is_some() {
return Err(InterfaceError::Default(
"Unsupported attribute for HSMs: object_group_member".to_owned(),
));
}
if researched_attributes.comment.is_some() {
return Err(InterfaceError::Default(
"Unsupported attribute for HSMs: comment".to_owned(),
));
}
if researched_attributes.contact_information.is_some() {
return Err(InterfaceError::Default(
"Unsupported attribute for HSMs: contact_information".to_owned(),
));
}
if let Some(critical) = researched_attributes.critical {
if critical {
return Err(InterfaceError::Default(
"Unsupported attribute for HSMs: critical = true".to_owned(),
));
}
}
if researched_attributes.description.is_some() {
return Err(InterfaceError::Default(
"Unsupported attribute for HSMs: description".to_owned(),
));
}
if researched_attributes.digest.is_some() {
return Err(InterfaceError::Default(
"Unsupported attribute for HSMs: digest".to_owned(),
));
}
if researched_attributes.short_unique_identifier.is_some() {
return Err(InterfaceError::Default(
"Unsupported attribute for HSMs: short_unique_identifier".to_owned(),
));
}
if researched_attributes.cryptographic_usage_mask.is_some() {
return Err(InterfaceError::Default(
"Unsupported attribute for HSMs: cryptographic_usage_mask".to_owned(),
));
}
if researched_attributes.x_509_certificate_identifier.is_some() {
return Err(InterfaceError::Default(
"Unsupported attribute for HSMs: x_509_certificate_identifier".to_owned(),
));
}
if researched_attributes.x_509_certificate_issuer.is_some() {
return Err(InterfaceError::Default(
"Unsupported attribute for HSMs: x_509_certificate_issuer".to_owned(),
));
}
if researched_attributes.x_509_certificate_subject.is_some() {
return Err(InterfaceError::Default(
"Unsupported attribute for HSMs: x_509_certificate_subject".to_owned(),
));
}
if researched_attributes
.name
.as_ref()
.is_some_and(|names| !names.is_empty())
{
return Err(InterfaceError::Default(
"Unsupported attribute for HSMs: name".to_owned(),
));
}
if researched_attributes
.application_specific_information
.is_some()
{
return Err(InterfaceError::Default(
"Unsupported attribute for HSMs: application_specific_information".to_owned(),
));
}
Ok(())
}
fn is_rsa_keypair_creation(
operations: &[AtomicOperation],
) -> Option<(String, Object, Attributes, HashSet<String>)> {
operations.iter().find_map(|op| match op {
AtomicOperation::Create((uid, object, attributes, tags)) => {
if object.object_type() != ObjectType::PrivateKey {
return None;
}
if !attributes
.cryptographic_algorithm
.as_ref()
.is_some_and(|algorithm| *algorithm == CryptographicAlgorithm::RSA)
{
return None;
}
Some((
uid.clone(),
object.clone(),
attributes.clone(),
tags.clone(),
))
}
_ => None,
})
}
fn parse_uid_with_prefix(uid: &str, prefix: &str) -> Result<(usize, String), InterfaceError> {
let rest = uid
.strip_prefix(&format!("{prefix}::"))
.ok_or_else(|| {
InterfaceError::InvalidRequest(format!(
"An HSM request must have a uid in the form of '{prefix}::<slot_id>::<key_id>', got: {uid}"
))
})?;
let (slot_id, key_id) = rest.split_once("::").ok_or_else(|| {
InterfaceError::InvalidRequest(format!(
"An HSM request must have a uid in the form of '{prefix}::<slot_id>::<key_id>', got: {uid}"
))
})?;
let slot_id = slot_id.parse::<usize>().map_err(|e| {
InterfaceError::InvalidRequest(format!("The slot_id must be a valid unsigned integer: {e}"))
})?;
Ok((slot_id, key_id.to_owned()))
}
fn to_object_with_metadata(
hsm_object: &HsmObject,
uid: &str,
user: &str,
vendor_id: &str,
) -> InterfaceResult<ObjectWithMetadata> {
match hsm_object.key_material() {
KeyMaterial::AesKey(bytes) => {
let length: i32 = 8 * i32::try_from(bytes.len())
.map_err(|e| InterfaceError::InvalidRequest(format!("Invalid key length: {e}")))?;
let mut attributes = Attributes {
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
cryptographic_length: Some(length),
object_type: Some(ObjectType::SymmetricKey),
cryptographic_usage_mask: Some(
CryptographicUsageMask::Encrypt
| CryptographicUsageMask::Decrypt
| CryptographicUsageMask::WrapKey
| CryptographicUsageMask::UnwrapKey
| CryptographicUsageMask::KeyAgreement,
),
..Attributes::default()
};
let mut tags: HashSet<String> =
serde_json::from_str(hsm_object.id()).unwrap_or_else(|_| HashSet::new());
tags.insert(SYSTEM_TAG_SYMMETRIC_KEY.to_owned());
attributes
.set_tags(vendor_id, tags)
.map_err(|e| InterfaceError::InvalidRequest(format!("Invalid tags: {e}")))?;
let kmip_key_material = KmipKeyMaterial::TransparentSymmetricKey { key: bytes.clone() };
let object = Object::SymmetricKey(SymmetricKey {
key_block: KeyBlock {
key_format_type: KeyFormatType::TransparentSymmetricKey,
key_compression_type: None,
key_value: Some(KeyValue::Structure {
key_material: kmip_key_material,
attributes: Some(attributes.clone()),
}),
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
cryptographic_length: Some(
8 * i32::try_from(bytes.len()).map_err(|e| {
InterfaceError::InvalidRequest(format!("Invalid key length: {e}"))
})?,
),
key_wrapping_data: None,
},
});
Ok(ObjectWithMetadata::new(
uid.to_owned(),
object,
user.to_owned(),
State::Active,
attributes,
))
}
KeyMaterial::RsaPrivateKey(km) => {
let mut attributes = Attributes {
cryptographic_algorithm: Some(CryptographicAlgorithm::RSA),
cryptographic_length: Some(
8 * i32::try_from(km.modulus.len()).map_err(|e| {
InterfaceError::InvalidRequest(format!("Invalid key length: {e}"))
})?,
),
object_type: Some(ObjectType::PrivateKey),
cryptographic_usage_mask: Some(
CryptographicUsageMask::Decrypt
| CryptographicUsageMask::UnwrapKey
| CryptographicUsageMask::Sign,
),
..Attributes::default()
};
let mut tags: HashSet<String> =
serde_json::from_str(hsm_object.id()).unwrap_or_else(|_| HashSet::new());
tags.insert(SYSTEM_TAG_PRIVATE_KEY.to_owned());
attributes
.set_tags(vendor_id, tags)
.map_err(|e| InterfaceError::InvalidRequest(format!("Invalid tags: {e}")))?;
let kmip_key_material = KmipKeyMaterial::TransparentRSAPrivateKey {
modulus: Box::new(BigInt::from_bytes_be(Sign::Plus, km.modulus.as_slice())),
private_exponent: Some(Box::new(SafeBigInt::from_bytes_be(
km.private_exponent.as_slice(),
))),
public_exponent: Some(Box::new(BigInt::from_bytes_be(
Sign::Plus,
km.public_exponent.as_slice(),
))),
p: Some(Box::new(SafeBigInt::from_bytes_be(km.prime_1.as_slice()))),
q: Some(Box::new(SafeBigInt::from_bytes_be(km.prime_2.as_slice()))),
prime_exponent_p: Some(Box::new(SafeBigInt::from_bytes_be(
km.exponent_1.as_slice(),
))),
prime_exponent_q: Some(Box::new(SafeBigInt::from_bytes_be(
km.exponent_2.as_slice(),
))),
c_r_t_coefficient: Some(Box::new(SafeBigInt::from_bytes_be(
km.coefficient.as_slice(),
))),
};
let object = Object::PrivateKey(PrivateKey {
key_block: KeyBlock {
key_format_type: KeyFormatType::TransparentRSAPrivateKey,
key_compression_type: None,
key_value: Some(KeyValue::Structure {
key_material: kmip_key_material,
attributes: Some(attributes.clone()),
}),
cryptographic_algorithm: Some(CryptographicAlgorithm::RSA),
cryptographic_length: Some(
8 * i32::try_from(km.modulus.len()).map_err(|e| {
InterfaceError::InvalidRequest(format!("Invalid key length: {e}"))
})?,
),
key_wrapping_data: None,
},
});
Ok(ObjectWithMetadata::new(
uid.to_owned(),
object,
user.to_owned(),
State::Active,
attributes,
))
}
KeyMaterial::RsaPublicKey(km) => {
let mut attributes = Attributes {
cryptographic_algorithm: Some(CryptographicAlgorithm::RSA),
cryptographic_length: Some(
i32::try_from(km.modulus.len()).map_err(|e| {
InterfaceError::InvalidRequest(format!("Invalid key length: {e}"))
})? * 8,
),
object_type: Some(ObjectType::PublicKey),
cryptographic_usage_mask: Some(
CryptographicUsageMask::Encrypt
| CryptographicUsageMask::WrapKey
| CryptographicUsageMask::Verify,
),
..Attributes::default()
};
let mut tags: HashSet<String> =
serde_json::from_str(hsm_object.id()).unwrap_or_else(|_| HashSet::new());
tags.insert(SYSTEM_TAG_PUBLIC_KEY.to_owned());
attributes
.set_tags(vendor_id, tags)
.map_err(|e| InterfaceError::InvalidRequest(format!("Invalid tags: {e}")))?;
let kmip_key_material = TransparentRSAPublicKey {
modulus: Box::new(BigInt::from_bytes_be(Sign::Plus, km.modulus.as_slice())),
public_exponent: Box::new(BigInt::from_bytes_be(
Sign::Plus,
km.public_exponent.as_slice(),
)),
};
let object = Object::PublicKey(PublicKey {
key_block: KeyBlock {
key_format_type: KeyFormatType::TransparentRSAPublicKey,
key_compression_type: None,
key_value: Some(KeyValue::Structure {
key_material: kmip_key_material,
attributes: Some(attributes.clone()),
}),
cryptographic_algorithm: Some(CryptographicAlgorithm::RSA),
cryptographic_length: Some(
i32::try_from(km.modulus.len()).map_err(|e| {
InterfaceError::InvalidRequest(format!("Invalid key length: {e}"))
})? * 8,
),
key_wrapping_data: None,
},
});
Ok(ObjectWithMetadata::new(
uid.to_owned(),
object,
user.to_owned(),
State::Active,
attributes,
))
}
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use async_trait::async_trait;
use cosmian_kmip::kmip_2_1::{
kmip_attributes::Attributes,
kmip_types::{Name, NameType},
};
use zeroize::Zeroizing;
use super::check_basic_compatibility;
use crate::{
CryptoAlgorithm, HSM, HsmKeyAlgorithm, HsmKeypairAlgorithm, HsmObject, HsmObjectFilter,
InterfaceError, InterfaceResult, KeyMetadata, KeyType, ObjectsStore, SigningAlgorithm,
crypto_oracle::EncryptedContent, hsm::HsmStore,
};
mockall::mock! {
pub Hsm {}
#[async_trait]
impl HSM for Hsm {
async fn get_available_slot_list(&self) -> InterfaceResult<Vec<usize>>;
async fn find(
&self,
slot_id: usize,
object_filter: HsmObjectFilter,
) -> InterfaceResult<Vec<Vec<u8>>>;
async fn get_supported_algorithms(
&self,
slot_id: usize,
) -> InterfaceResult<Vec<CryptoAlgorithm>>;
async fn create_key(
&self,
slot_id: usize,
id: &[u8],
algorithm: HsmKeyAlgorithm,
key_length_in_bits: usize,
sensitive: bool,
) -> InterfaceResult<()>;
async fn create_keypair(
&self,
slot_id: usize,
sk_id: &[u8],
pk_id: &[u8],
algorithm: HsmKeypairAlgorithm,
key_length_in_bits: usize,
sensitive: bool,
) -> InterfaceResult<()>;
async fn export(
&self,
slot_id: usize,
object_id: &[u8],
) -> InterfaceResult<Option<HsmObject>>;
async fn delete(&self, slot_id: usize, object_id: &[u8]) -> InterfaceResult<()>;
async fn encrypt(
&self,
slot_id: usize,
key_id: &[u8],
algorithm: CryptoAlgorithm,
data: &[u8],
) -> InterfaceResult<EncryptedContent>;
async fn decrypt(
&self,
slot_id: usize,
key_id: &[u8],
algorithm: CryptoAlgorithm,
data: &[u8],
) -> InterfaceResult<Zeroizing<Vec<u8>>>;
async fn get_key_type(
&self,
slot_id: usize,
key_id: &[u8],
) -> InterfaceResult<Option<KeyType>>;
async fn get_key_metadata(
&self,
slot_id: usize,
key_id: &[u8],
) -> InterfaceResult<Option<KeyMetadata>>;
async fn sign(
&self,
slot_id: usize,
key_id: &[u8],
algorithm: SigningAlgorithm,
data: &[u8],
) -> InterfaceResult<Vec<u8>>;
async fn generate_random(
&self,
slot_id: usize,
len: usize,
) -> InterfaceResult<Vec<u8>>;
async fn seed_random(&self, slot_id: usize, seed: &[u8]) -> InterfaceResult<()>;
async fn set_key_dates(
&self,
slot_id: usize,
key_id: &[u8],
start_date: Option<time::Date>,
end_date: Option<time::Date>,
) -> InterfaceResult<()>;
async fn set_key_label(
&self,
slot_id: usize,
key_id: &[u8],
label: &str,
) -> InterfaceResult<()>;
fn hsm_lib(&self) -> Option<&'static dyn std::any::Any> { None }
}
}
#[test]
fn test_name_filter_rejected_for_hsm() {
let attrs = Attributes {
name: Some(vec![Name {
name_value: "test-duplicate".to_owned(),
name_type: NameType::UninterpretedTextString,
}]),
..Default::default()
};
let result = check_basic_compatibility("cosmian", &attrs, None);
assert!(
matches!(result, Err(InterfaceError::Default(ref msg)) if msg.contains("name")),
"Expected name attribute to be rejected for HSM, got: {result:?}"
);
}
#[test]
fn test_no_name_filter_compatible() {
use cosmian_kmip::kmip_2_1::kmip_objects::ObjectType;
let attrs = Attributes {
object_type: Some(ObjectType::SymmetricKey),
..Default::default()
};
let result = check_basic_compatibility("cosmian", &attrs, None);
assert!(
result.is_ok(),
"Expected ObjectType-only filter to be compatible with HSM, got: {result:?}"
);
}
#[tokio::test]
async fn test_count_all_non_destroyed_delegates_to_count_non_destroyed_keys()
-> InterfaceResult<()> {
let mut mock = MockHsm::new();
mock.expect_get_available_slot_list()
.returning(|| Ok(vec![0, 1]));
mock.expect_find()
.returning(|slot_id, _filter| match slot_id {
0 => Ok(vec![vec![0], vec![1], vec![2]]), 1 => Ok(vec![vec![0], vec![1]]), _ => Ok(vec![]),
});
let store = HsmStore::new(Arc::new(mock), &["admin".to_owned()], "cosmian", "hsm");
let via_all = store.count_all_non_destroyed().await?;
let via_keys = store.count_non_destroyed_keys().await?;
if via_all != 5 {
return Err(InterfaceError::Default(format!(
"count_all_non_destroyed should return 5 (3+2), got {via_all}"
)));
}
if via_all != via_keys {
return Err(InterfaceError::Default(format!(
"count_all_non_destroyed ({via_all}) must equal count_non_destroyed_keys \
({via_keys}) for HsmStore"
)));
}
Ok(())
}
}