use cosmian_kmip::time_normalize;
use cosmian_kms_client::{
KmsClient,
kmip_0::kmip_types::{CryptographicUsageMask, State},
kmip_2_1::{
extra::tagging::VENDOR_ID_COSMIAN,
kmip_attributes::{Attribute, Attributes},
kmip_objects::ObjectType,
kmip_operations::{
Create, CreateResponse, GetAttributes, GetAttributesResponse, ModifyAttribute,
SetAttribute,
},
kmip_types::{
CryptographicAlgorithm, Name, NameType, Tag, UniqueIdentifier, VendorAttribute,
},
},
reexport::cosmian_kms_client_utils::import_utils::KeyUsage,
};
use cosmian_logger::trace;
use strum::IntoEnumIterator;
use test_kms_server::{TestsContext, start_default_test_kms_server};
use crate::{
actions::kms::{
attributes::{
CCryptographicAlgorithm, CLinkType, DeleteAttributesAction, GetAttributesAction,
ModifyAttributesAction, SetAttributesAction, SetOrDeleteAttributes, VendorAttributeCli,
},
symmetric::keys::create_key::CreateKeyAction,
},
error::result::KmsCliResult,
};
fn get_all_attribute_tags() -> Vec<Tag> {
let mut tags = Vec::new();
for tag in Tag::iter() {
tags.push(tag);
}
tags
}
fn get_all_link_types() -> Vec<CLinkType> {
let mut links = Vec::new();
for link_type in CLinkType::iter() {
links.push(link_type);
}
links
}
async fn get_attribute_value(
ctx: &TestsContext,
uid: &str,
tag: Tag,
) -> KmsCliResult<Option<serde_json::Value>> {
let get_attributes = GetAttributesAction {
id: Some(uid.to_owned()),
tags: None,
attribute_tags: get_all_attribute_tags(),
attribute_link_types: get_all_link_types(),
output_file: None,
}
.run(ctx.get_owner_client())
.await?;
Ok(get_attributes.get(&tag.to_string()).cloned())
}
async fn check_modify_attributes(uid: &str, ctx: &TestsContext) -> KmsCliResult<()> {
let client = ctx.get_owner_client();
SetAttributesAction {
requested_attributes: SetOrDeleteAttributes {
id: Some(uid.to_owned()),
cryptographic_length: Some(128),
cryptographic_algorithm: Some(CCryptographicAlgorithm::AES),
key_usage: Some(vec![KeyUsage::Encrypt]),
vendor_attributes: Some(VendorAttributeCli {
vendor_identification: Some(VENDOR_ID_COSMIAN.to_owned()),
attribute_name: Some("my_custom_attr".to_owned()),
attribute_value: Some("AABBCCDD".to_owned()),
}),
..SetOrDeleteAttributes::default()
},
}
.process(client.clone())
.await?;
ModifyAttributesAction {
requested_attributes: SetOrDeleteAttributes {
id: Some(uid.to_owned()),
cryptographic_length: Some(256),
..SetOrDeleteAttributes::default()
},
}
.process(client.clone())
.await?;
let len_val = get_attribute_value(ctx, uid, Tag::CryptographicLength).await?;
assert!(
len_val.is_some(),
"CryptographicLength should be present after modify"
);
let length: i32 = serde_json::from_value(len_val.unwrap())?;
assert_eq!(
length, 256,
"CryptographicLength should be 256 after modify"
);
trace!("CryptographicLength modified successfully to 256");
ModifyAttributesAction {
requested_attributes: SetOrDeleteAttributes {
id: Some(uid.to_owned()),
cryptographic_algorithm: Some(CCryptographicAlgorithm::Chacha20),
..SetOrDeleteAttributes::default()
},
}
.process(client.clone())
.await?;
let algo_val = get_attribute_value(ctx, uid, Tag::CryptographicAlgorithm).await?;
assert!(
algo_val.is_some(),
"CryptographicAlgorithm should be present after modify"
);
let algo: CryptographicAlgorithm = serde_json::from_value(algo_val.unwrap())?;
assert_eq!(
algo,
CryptographicAlgorithm::ChaCha20,
"CryptographicAlgorithm should be ChaCha20 after modify"
);
trace!("CryptographicAlgorithm modified successfully to ChaCha20");
ModifyAttributesAction {
requested_attributes: SetOrDeleteAttributes {
id: Some(uid.to_owned()),
key_usage: Some(vec![KeyUsage::Encrypt, KeyUsage::Decrypt]),
..SetOrDeleteAttributes::default()
},
}
.process(client.clone())
.await?;
let mask_val = get_attribute_value(ctx, uid, Tag::CryptographicUsageMask).await?;
assert!(
mask_val.is_some(),
"CryptographicUsageMask should be present after modify"
);
let mask: CryptographicUsageMask = serde_json::from_value(mask_val.unwrap())?;
assert!(
mask.contains(CryptographicUsageMask::Encrypt),
"CryptographicUsageMask should include Encrypt"
);
assert!(
mask.contains(CryptographicUsageMask::Decrypt),
"CryptographicUsageMask should include Decrypt"
);
trace!("CryptographicUsageMask modified successfully to Encrypt|Decrypt");
ModifyAttributesAction {
requested_attributes: SetOrDeleteAttributes {
id: Some(uid.to_owned()),
vendor_attributes: Some(VendorAttributeCli {
vendor_identification: Some(VENDOR_ID_COSMIAN.to_owned()),
attribute_name: Some("my_custom_attr".to_owned()),
attribute_value: Some("EEFF0011".to_owned()),
}),
..SetOrDeleteAttributes::default()
},
}
.process(client.clone())
.await?;
let vendor_val = get_attribute_value(ctx, uid, Tag::VendorExtension).await?;
assert!(
vendor_val.is_some(),
"VendorExtension should be present after modify"
);
let vas: Vec<VendorAttribute> = serde_json::from_value(vendor_val.unwrap())?;
assert!(!vas.is_empty(), "VendorExtension list should not be empty");
trace!("VendorAttribute modified successfully");
let uid_ref = UniqueIdentifier::TextString(uid.to_owned());
client
.set_attribute(SetAttribute {
unique_identifier: Some(uid_ref.clone()),
new_attribute: Attribute::Name(Name {
name_value: "initial-name".to_owned(),
name_type: NameType::UninterpretedTextString,
}),
})
.await?;
client
.modify_attribute(ModifyAttribute {
unique_identifier: Some(uid_ref.clone()),
new_attribute: Attribute::Name(Name {
name_value: "modified-name".to_owned(),
name_type: NameType::UninterpretedTextString,
}),
})
.await?;
let GetAttributesResponse { attributes, .. } = client
.get_attributes(GetAttributes {
unique_identifier: Some(uid_ref.clone()),
attribute_reference: None,
})
.await?;
let names = attributes.name.unwrap_or_default();
assert!(
names.iter().any(|n| n.name_value == "modified-name"),
"Name should contain 'modified-name' after modify, got: {names:?}"
);
trace!("Name attribute modified successfully to 'modified-name'");
let result = client
.modify_attribute(ModifyAttribute {
unique_identifier: Some(uid_ref.clone()),
new_attribute: Attribute::State(State::Active),
})
.await;
assert!(
result.is_err(),
"ModifyAttribute(State) should be rejected as read-only"
);
trace!("State attribute correctly rejected as read-only");
DeleteAttributesAction {
requested_attributes: SetOrDeleteAttributes {
id: Some(uid.to_owned()),
cryptographic_length: Some(0),
key_usage: Some(vec![KeyUsage::Encrypt, KeyUsage::Decrypt]),
vendor_attributes: Some(VendorAttributeCli {
vendor_identification: Some(VENDOR_ID_COSMIAN.to_owned()),
attribute_name: Some("my_custom_attr".to_owned()),
attribute_value: Some("EEFF0011".to_owned()),
}),
..SetOrDeleteAttributes::default()
},
attribute_tags: None,
}
.process(client.clone())
.await?;
Ok(())
}
async fn create_preactive_symmetric_key(client: &KmsClient) -> KmsCliResult<String> {
let future_activation = time_normalize()? + time::Duration::hours(2);
let response: CreateResponse = client
.create(Create {
object_type: ObjectType::SymmetricKey,
attributes: Attributes {
activation_date: Some(future_activation),
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
cryptographic_length: Some(256),
cryptographic_usage_mask: Some(
CryptographicUsageMask::Encrypt | CryptographicUsageMask::Decrypt,
),
object_type: Some(ObjectType::SymmetricKey),
..Default::default()
},
protection_storage_masks: None,
})
.await?;
Ok(response.unique_identifier.to_string())
}
async fn check_modify_activation_date(ctx: &TestsContext) -> KmsCliResult<()> {
let client = ctx.get_owner_client();
let uid = create_preactive_symmetric_key(&client).await?;
let initial_ts = (time_normalize()? + time::Duration::hours(2)).unix_timestamp();
SetAttributesAction {
requested_attributes: SetOrDeleteAttributes {
id: Some(uid.clone()),
activation_date: Some(initial_ts),
..SetOrDeleteAttributes::default()
},
}
.process(client.clone())
.await?;
let new_ts = (time_normalize()? + time::Duration::hours(3)).unix_timestamp();
ModifyAttributesAction {
requested_attributes: SetOrDeleteAttributes {
id: Some(uid.clone()),
activation_date: Some(new_ts),
..SetOrDeleteAttributes::default()
},
}
.process(client.clone())
.await?;
let date_val = get_attribute_value(ctx, &uid, Tag::ActivationDate).await?;
assert!(
date_val.is_some(),
"ActivationDate should be present after modify on Pre-Active key"
);
let stored_ts: i64 = serde_json::from_value(date_val.unwrap())?;
assert_eq!(
stored_ts, new_ts,
"ActivationDate should equal new_ts after modify"
);
trace!("ActivationDate modified successfully on Pre-Active key");
Ok(())
}
#[tokio::test]
async fn test_modify_attribute() -> KmsCliResult<()> {
let ctx = start_default_test_kms_server().await;
let uid = CreateKeyAction::default()
.run(ctx.get_owner_client())
.await?;
check_modify_attributes(uid.as_str().unwrap(), ctx).await?;
check_modify_activation_date(ctx).await?;
Ok(())
}