use std::{collections::HashMap, str::FromStr};
use clap::ValueEnum;
use cosmian_kmip::kmip_2_1::{
extra::tagging::VENDOR_ATTR_TAG,
kmip_attributes::{Attribute, Attributes},
kmip_types::{
CryptographicAlgorithm, Link, LinkType, LinkedObjectIdentifier, Name, NameType, Tag,
},
};
use serde_json::Value;
use strum::{EnumIter, EnumString, IntoEnumIterator};
use time::{OffsetDateTime, format_description};
use crate::{
error::UtilsError,
import_utils::{KeyUsage, build_usage_mask_from_key_usage},
};
#[derive(ValueEnum, Debug, Clone, PartialEq, Eq, EnumIter, EnumString)]
#[strum(serialize_all = "kebab-case")]
pub enum CLinkType {
Certificate,
PublicKey,
PrivateKey,
DerivationBaseObject,
DerivedKey,
ReplacementObject,
ReplacedObject,
Parent,
Child,
Previous,
Next,
PKCS12Certificate,
PKCS12Password,
WrappingKey,
}
impl From<CLinkType> for LinkType {
fn from(value: CLinkType) -> Self {
match value {
CLinkType::Certificate => Self::CertificateLink,
CLinkType::PublicKey => Self::PublicKeyLink,
CLinkType::PrivateKey => Self::PrivateKeyLink,
CLinkType::DerivationBaseObject => Self::DerivationBaseObjectLink,
CLinkType::DerivedKey => Self::DerivedKeyLink,
CLinkType::ReplacementObject => Self::ReplacementObjectLink,
CLinkType::ReplacedObject => Self::ReplacedObjectLink,
CLinkType::Parent => Self::ParentLink,
CLinkType::Child => Self::ChildLink,
CLinkType::Previous => Self::PreviousLink,
CLinkType::Next => Self::NextLink,
CLinkType::PKCS12Certificate => Self::PKCS12CertificateLink,
CLinkType::PKCS12Password => Self::PKCS12PasswordLink,
CLinkType::WrappingKey => Self::WrappingKeyLink,
}
}
}
fn add_if_not_empty(tag: Tag, new_value: &str, results: &mut HashMap<String, Value>) {
if !new_value.is_empty() {
results.insert(
tag.to_string(),
serde_json::to_value(new_value).unwrap_or_default(),
);
}
}
pub fn parse_selected_attributes(
vendor_id: &str,
attributes: &Attributes,
attribute_tags: &[Tag],
attribute_link_types: &[CLinkType],
) -> Result<HashMap<String, Value>, UtilsError> {
let tags = if attribute_tags.is_empty() {
Tag::iter().collect()
} else {
attribute_tags.to_vec()
};
let mut results: HashMap<String, Value> = HashMap::new();
for tag in &tags {
match tag {
Tag::ActivationDate => {
if let Some(v) = attributes.activation_date.as_ref() {
results.insert(
tag.to_string(),
serde_json::to_value(v.unix_timestamp() * 1000_i64).unwrap_or_default(),
);
}
}
Tag::CertificateLength => {
if let Some(v) = attributes.certificate_length.as_ref() {
results.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
}
}
Tag::CertificateType => {
if let Some(v) = attributes.certificate_type.as_ref() {
results.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
}
}
Tag::CertificateSubjectC => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_subject_c, &mut results);
}
}
Tag::CertificateSubjectCN => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_subject_cn, &mut results);
}
}
Tag::CertificateSubjectDC => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_subject_dc, &mut results);
}
}
Tag::CertificateSubjectEmail => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_subject_email, &mut results);
}
}
Tag::CertificateSubjectL => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_subject_l, &mut results);
}
}
Tag::CertificateSubjectO => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_subject_o, &mut results);
}
}
Tag::CertificateSubjectOU => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_subject_ou, &mut results);
}
}
Tag::CertificateSubjectST => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_subject_st, &mut results);
}
}
Tag::CertificateSubjectDNQualifier => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_subject_dn_qualifier, &mut results);
}
}
Tag::CertificateSubjectSerialNumber => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_subject_serial_number, &mut results);
}
}
Tag::CertificateSubjectTitle => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_subject_title, &mut results);
}
}
Tag::CertificateSubjectUID => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_subject_uid, &mut results);
}
}
Tag::CertificateIssuerC => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_issuer_c, &mut results);
}
}
Tag::CertificateIssuerCN => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_issuer_cn, &mut results);
}
}
Tag::CertificateIssuerDC => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_issuer_dc, &mut results);
}
}
Tag::CertificateIssuerEmail => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_issuer_email, &mut results);
}
}
Tag::CertificateIssuerL => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_issuer_l, &mut results);
}
}
Tag::CertificateIssuerO => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_issuer_o, &mut results);
}
}
Tag::CertificateIssuerOU => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_issuer_ou, &mut results);
}
}
Tag::CertificateIssuerST => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_issuer_st, &mut results);
}
}
Tag::CertificateIssuerDNQualifier => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_issuer_dn_qualifier, &mut results);
}
}
Tag::CertificateIssuerUID => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_issuer_uid, &mut results);
}
}
Tag::CertificateIssuerSerialNumber => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_issuer_serial_number, &mut results);
}
}
Tag::CertificateIssuerTitle => {
if let Some(v) = attributes.certificate_attributes.as_ref() {
add_if_not_empty(*tag, &v.certificate_issuer_title, &mut results);
}
}
Tag::CryptographicAlgorithm => {
if let Some(v) = attributes.cryptographic_algorithm.as_ref() {
results.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
}
}
Tag::CryptographicLength => {
if let Some(v) = attributes.cryptographic_length.as_ref() {
results.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
}
}
Tag::CryptographicParameters => {
if let Some(v) = attributes.cryptographic_parameters.as_ref() {
results.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
}
}
Tag::CryptographicDomainParameters => {
if let Some(v) = attributes.cryptographic_domain_parameters.as_ref() {
results.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
}
}
Tag::CryptographicUsageMask => {
if let Some(v) = attributes.cryptographic_usage_mask.as_ref() {
results.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
}
}
Tag::KeyFormatType => {
if let Some(v) = attributes.key_format_type.as_ref() {
results.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
}
}
Tag::ObjectType => {
if let Some(v) = attributes.object_type.as_ref() {
results.insert(tag.to_string(), serde_json::to_value(v).unwrap_or_default());
}
}
Tag::Tag => {
let tags = attributes.get_tags(vendor_id);
results.insert(
tag.to_string(),
serde_json::to_value(tags).unwrap_or_default(),
);
}
Tag::Name => {
if let Some(names) = attributes.name.as_ref() {
if !names.is_empty() {
results.insert(
tag.to_string(),
serde_json::to_value(names).unwrap_or_default(),
);
}
}
}
Tag::VendorExtension => {
if let Some(vendor_attributes) = attributes.vendor_attributes.as_ref() {
let filtered_vendor_attributes: Vec<_> = vendor_attributes
.iter()
.filter(|va| {
!(va.vendor_identification == vendor_id
&& va.attribute_name == VENDOR_ATTR_TAG)
})
.collect();
if !filtered_vendor_attributes.is_empty() {
results.insert(
tag.to_string(),
serde_json::to_value(filtered_vendor_attributes).unwrap_or_default(),
);
}
}
}
_x => {}
}
}
let link_types: Vec<LinkType> = if attribute_link_types.is_empty() {
LinkType::iter().collect()
} else {
attribute_link_types
.iter()
.map(|link| LinkType::from(link.clone()))
.collect()
};
for link_type in &link_types {
if let Some(v) = attributes.get_link(*link_type).as_ref() {
results.insert(
link_type.to_string(),
serde_json::to_value(v).unwrap_or_default(),
);
}
}
Ok(results)
}
pub const LOCATE_ENRICH_ATTRIBUTE_KEYS: &[&str] = &[
"object_type",
"state",
"tags",
"user_tags",
"cryptographic_algorithm",
"cryptographic_length",
"key_format_type",
"public_key_id",
"private_key_id",
"certificate_id",
"initial_date",
"activation_date",
"original_creation_date",
"rotate_date",
"rotate_name",
"rotate_interval",
"rotate_offset",
"rotate_generation",
];
pub fn parse_selected_attributes_flatten(
vendor_id: &str,
attributes: &Attributes,
selected_attributes: &[&str],
) -> Result<HashMap<String, Value>, UtilsError> {
macro_rules! insert_if_some {
($results:expr, $key:expr, $opt:expr) => {
if let Some(v) = $opt {
$results.insert($key.to_owned(), serde_json::to_value(v).unwrap_or_default());
}
};
}
let mut results: HashMap<String, Value> = HashMap::new();
if selected_attributes.is_empty() {
let values = serde_json::to_value(attributes)?;
if let Value::Object(map) = values {
results = map
.into_iter()
.map(|(key, val)| (key, serde_json::to_value(val).unwrap_or(Value::Null)))
.collect();
}
return Ok(results);
}
for &selected_attribute_name in selected_attributes {
match selected_attribute_name {
"activation_date" => {
if let Some(v) = attributes.activation_date.as_ref() {
results.insert(
selected_attribute_name.to_owned(),
serde_json::to_value(v.unix_timestamp() * 1000_i64).unwrap_or_default(),
);
}
}
"initial_date" => {
if let Some(v) = attributes.initial_date.as_ref() {
results.insert(
selected_attribute_name.to_owned(),
serde_json::to_value(v.unix_timestamp() * 1000_i64).unwrap_or_default(),
);
}
}
"original_creation_date" => {
if let Some(v) = attributes.original_creation_date.as_ref() {
results.insert(
selected_attribute_name.to_owned(),
serde_json::to_value(v.unix_timestamp() * 1000_i64).unwrap_or_default(),
);
}
}
"rotate_automatic" => insert_if_some!(
results,
selected_attribute_name,
attributes.rotate_automatic.as_ref()
),
"rotate_date" => {
if let Some(v) = attributes.rotate_date.as_ref() {
results.insert(
selected_attribute_name.to_owned(),
serde_json::to_value(v.unix_timestamp() * 1000_i64).unwrap_or_default(),
);
}
}
"rotate_generation" => insert_if_some!(
results,
selected_attribute_name,
attributes.rotate_generation.as_ref()
),
"rotate_interval" => insert_if_some!(
results,
selected_attribute_name,
attributes.rotate_interval.as_ref()
),
"rotate_latest" => insert_if_some!(
results,
selected_attribute_name,
attributes.rotate_latest.as_ref()
),
"rotate_name" => insert_if_some!(
results,
selected_attribute_name,
attributes.rotate_name.as_ref()
),
"rotate_offset" => insert_if_some!(
results,
selected_attribute_name,
attributes.rotate_offset.as_ref()
),
"cryptographic_algorithm" => insert_if_some!(
results,
selected_attribute_name,
attributes.cryptographic_algorithm.as_ref()
),
"cryptographic_length" => insert_if_some!(
results,
selected_attribute_name,
attributes.cryptographic_length.as_ref()
),
"key_usage" => insert_if_some!(
results,
selected_attribute_name,
attributes.cryptographic_usage_mask.as_ref()
),
"key_format_type" => insert_if_some!(
results,
selected_attribute_name,
attributes.key_format_type.as_ref()
),
"object_type" => insert_if_some!(
results,
selected_attribute_name,
attributes.object_type.as_ref()
),
"state" => insert_if_some!(results, selected_attribute_name, attributes.state.as_ref()),
"tags" | "user_tags" => {
let tags = attributes.get_tags(vendor_id);
if !tags.is_empty() {
results.insert(
selected_attribute_name.to_owned(),
serde_json::to_value(tags).unwrap_or_default(),
);
}
}
"vendor_attributes" => insert_if_some!(
results,
selected_attribute_name,
attributes.vendor_attributes.as_ref()
),
"public_key_id" => insert_if_some!(
results,
selected_attribute_name,
attributes.get_link(LinkType::PublicKeyLink).as_ref()
),
"private_key_id" => insert_if_some!(
results,
selected_attribute_name,
attributes.get_link(LinkType::PrivateKeyLink).as_ref()
),
"certificate_id" => insert_if_some!(
results,
selected_attribute_name,
attributes.get_link(LinkType::CertificateLink).as_ref()
),
"pkcs12_certificate_id" => insert_if_some!(
results,
selected_attribute_name,
attributes
.get_link(LinkType::PKCS12CertificateLink)
.as_ref()
),
"pkcs12_password_certificate" => insert_if_some!(
results,
selected_attribute_name,
attributes.get_link(LinkType::PKCS12PasswordLink).as_ref()
),
"parent_id" => insert_if_some!(
results,
selected_attribute_name,
attributes.get_link(LinkType::ParentLink).as_ref()
),
"child_id" => insert_if_some!(
results,
selected_attribute_name,
attributes.get_link(LinkType::ChildLink).as_ref()
),
"deactivation_date" => {
if let Some(v) = attributes.deactivation_date.as_ref() {
results.insert(
selected_attribute_name.to_owned(),
serde_json::to_value(v.unix_timestamp() * 1000_i64).unwrap_or_default(),
);
}
}
"description" => insert_if_some!(
results,
selected_attribute_name,
attributes.description.as_ref()
),
"comment" => insert_if_some!(
results,
selected_attribute_name,
attributes.comment.as_ref()
),
"contact_information" => insert_if_some!(
results,
selected_attribute_name,
attributes.contact_information.as_ref()
),
"object_group" => insert_if_some!(
results,
selected_attribute_name,
attributes.object_group.as_ref()
),
"sensitive" => insert_if_some!(
results,
selected_attribute_name,
attributes.sensitive.as_ref()
),
"extractable" => insert_if_some!(
results,
selected_attribute_name,
attributes.extractable.as_ref()
),
_x => {}
}
}
Ok(results)
}
pub fn build_selected_attribute(
attribute_name: &str,
attribute_value: String,
) -> Result<Attribute, UtilsError> {
let attribute = match attribute_name {
"activation_date" => {
let format = format_description::parse_borrowed::<2>(
"[year]-[month]-[day]T[hour]:[minute]:[second]Z",
)
.map_err(|e| UtilsError::Default(e.to_string()))?;
let activation_date = OffsetDateTime::parse(&attribute_value, &format)
.map_err(|e| UtilsError::Default(e.to_string()))?;
Attribute::ActivationDate(activation_date)
}
"cryptographic_algorithm" => {
let cryptographic_algorithm =
CryptographicAlgorithm::from_str(attribute_value.as_str())
.map_err(|e| UtilsError::Default(e.to_string()))?;
Attribute::CryptographicAlgorithm(cryptographic_algorithm)
}
"cryptographic_length" => {
let cryptographic_length = attribute_value
.parse::<i32>()
.map_err(|e| UtilsError::Default(e.to_string()))?;
Attribute::CryptographicLength(cryptographic_length)
}
"key_usage" => {
let key_usages = attribute_value
.split(',')
.map(|s| {
s.trim()
.parse::<KeyUsage>()
.map_err(|e| UtilsError::Default(e.to_string()))
})
.collect::<Result<Vec<_>, _>>()?;
let Some(cryptographic_usage_mask) = build_usage_mask_from_key_usage(&key_usages)
else {
return Err(UtilsError::Default(
"Error building cryptographic usage mask".to_owned(),
));
};
Attribute::CryptographicUsageMask(cryptographic_usage_mask)
}
"public_key_id" => Attribute::Link(Link {
link_type: LinkType::PublicKeyLink,
linked_object_identifier: LinkedObjectIdentifier::TextString(attribute_value),
}),
"private_key_id" => Attribute::Link(Link {
link_type: LinkType::PrivateKeyLink,
linked_object_identifier: LinkedObjectIdentifier::TextString(attribute_value),
}),
"certificate_id" => Attribute::Link(Link {
link_type: LinkType::CertificateLink,
linked_object_identifier: LinkedObjectIdentifier::TextString(attribute_value),
}),
"pkcs12_certificate_id" => Attribute::Link(Link {
link_type: LinkType::PKCS12CertificateLink,
linked_object_identifier: LinkedObjectIdentifier::TextString(attribute_value),
}),
"pkcs12_password_certificate" => Attribute::Link(Link {
link_type: LinkType::PKCS12PasswordLink,
linked_object_identifier: LinkedObjectIdentifier::TextString(attribute_value),
}),
"parent_id" => Attribute::Link(Link {
link_type: LinkType::ParentLink,
linked_object_identifier: LinkedObjectIdentifier::TextString(attribute_value),
}),
"child_id" => Attribute::Link(Link {
link_type: LinkType::ChildLink,
linked_object_identifier: LinkedObjectIdentifier::TextString(attribute_value),
}),
"name" => Attribute::Name(Name {
name_value: attribute_value,
name_type: NameType::UninterpretedTextString,
}),
"rotate_interval" => {
let v = attribute_value
.parse::<i64>()
.map_err(|e| UtilsError::Default(e.to_string()))?;
Attribute::RotateInterval(v)
}
"rotate_name" => Attribute::RotateName(attribute_value),
"rotate_offset" => {
let v = attribute_value
.parse::<i64>()
.map_err(|e| UtilsError::Default(e.to_string()))?;
Attribute::RotateOffset(v)
}
"rotate_automatic" => {
let v = attribute_value
.parse::<bool>()
.map_err(|e| UtilsError::Default(e.to_string()))?;
Attribute::RotateAutomatic(v)
}
"deactivation_date" => {
let format = format_description::parse_borrowed::<2>(
"[year]-[month]-[day]T[hour]:[minute]:[second]Z",
)
.map_err(|e| UtilsError::Default(e.to_string()))?;
let deactivation_date = OffsetDateTime::parse(&attribute_value, &format)
.map_err(|e| UtilsError::Default(e.to_string()))?;
Attribute::DeactivationDate(deactivation_date)
}
"description" => Attribute::Description(attribute_value),
"comment" => Attribute::Comment(attribute_value),
"contact_information" => Attribute::ContactInformation(attribute_value),
"object_group" => Attribute::ObjectGroup(attribute_value),
"sensitive" => {
let v = attribute_value
.parse::<bool>()
.map_err(|e| UtilsError::Default(e.to_string()))?;
Attribute::Sensitive(v)
}
"extractable" => {
let v = attribute_value
.parse::<bool>()
.map_err(|e| UtilsError::Default(e.to_string()))?;
Attribute::Extractable(v)
}
_ => {
return Err(UtilsError::Default(format!(
"Unknown attribute name: {attribute_name}"
)));
}
};
Ok(attribute)
}
#[cfg(test)]
#[expect(clippy::unwrap_used, clippy::indexing_slicing, clippy::panic)]
mod tests {
use super::*;
use crate::reexport::cosmian_kmip::kmip_2_1::{
extra::tagging::{VENDOR_ATTR_TAG, VENDOR_ID_COSMIAN},
kmip_attributes::Attributes,
kmip_types::{CryptographicAlgorithm, VendorAttribute, VendorAttributeValue},
};
#[test]
fn test_vendor_extension_tag_filtering() {
let mut attributes = Attributes {
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
..Default::default()
};
let tag_vendor_attr = VendorAttribute {
vendor_identification: VENDOR_ID_COSMIAN.to_owned(),
attribute_name: VENDOR_ATTR_TAG.to_owned(),
attribute_value: VendorAttributeValue::TextString(
"[\"_cert\",\"test_cert\"]".to_owned(),
),
};
let other_vendor_attr = VendorAttribute {
vendor_identification: VENDOR_ID_COSMIAN.to_owned(),
attribute_name: "some_other_attr".to_owned(),
attribute_value: VendorAttributeValue::TextString("some_value".to_owned()),
};
attributes.vendor_attributes = Some(vec![tag_vendor_attr, other_vendor_attr]);
let result = parse_selected_attributes(VENDOR_ID_COSMIAN, &attributes, &[], &[]).unwrap();
if let Some(vendor_extension) = result.get("VendorExtension") {
if let Ok(vendor_attrs) =
serde_json::from_value::<Vec<VendorAttribute>>(vendor_extension.clone())
{
let has_tag_attr = vendor_attrs.iter().any(|va| {
va.vendor_identification == VENDOR_ID_COSMIAN
&& va.attribute_name == VENDOR_ATTR_TAG
});
assert!(
!has_tag_attr,
"Tag vendor attribute should be filtered out from VendorExtension"
);
assert_eq!(
vendor_attrs.len(),
1,
"Should have exactly one non-tag vendor attribute"
);
assert_eq!(vendor_attrs[0].attribute_name, "some_other_attr");
} else {
panic!("Failed to parse vendor extension as array of VendorAttribute");
}
} else {
panic!("VendorExtension should be present when there are non-tag vendor attributes");
}
assert!(
result.contains_key("CryptographicAlgorithm"),
"CryptographicAlgorithm should be present"
);
assert!(result.contains_key("Tag"), "Tag field should be present");
}
#[test]
fn test_vendor_extension_only_tags_filtered_out() {
let mut attributes = Attributes::default();
let tag_vendor_attr = VendorAttribute {
vendor_identification: VENDOR_ID_COSMIAN.to_owned(),
attribute_name: VENDOR_ATTR_TAG.to_owned(),
attribute_value: VendorAttributeValue::TextString("[\"_cert\"]".to_owned()),
};
attributes.vendor_attributes = Some(vec![tag_vendor_attr]);
let result = parse_selected_attributes(VENDOR_ID_COSMIAN, &attributes, &[], &[]).unwrap();
assert!(
!result.contains_key("VendorExtension"),
"VendorExtension should not be present when it would only contain tag data"
);
assert!(
result.contains_key("Tag"),
"Tag field should still be present"
);
}
}