use std::{convert::TryFrom, fmt::Display};
use clap::{Parser, ValueEnum};
use cosmian_kms_client::{
KmsClient,
cosmian_kmip::kmip_2_1::kmip_types::UniqueIdentifier,
kmip_2_1::{
kmip_attributes::Attribute,
kmip_operations::{SetAttribute, SetAttributeResponse},
kmip_types::{
self, CryptographicAlgorithm, Link, LinkType, LinkedObjectIdentifier, Name, NameType,
VendorAttribute, VendorAttributeValue,
},
},
reexport::cosmian_kms_client_utils::import_utils::{KeyUsage, build_usage_mask_from_key_usage},
};
use cosmian_logger::{info, trace};
use serde::Deserialize;
use strum::EnumIter;
use time::OffsetDateTime;
use crate::{
actions::kms::{console, labels::ATTRIBUTE_ID, shared::get_key_uid},
cli_bail,
error::result::KmsCliResult,
};
#[derive(ValueEnum, Clone, Copy, Debug, EnumIter)]
pub enum CCryptographicAlgorithm {
AES,
RSA,
ECDSA,
ECDH,
EC,
Chacha20,
Chacha20Poly1305,
SHA3224,
SHA3256,
SHA3384,
SHA3512,
Ed25519,
Ed448,
Covercrypt,
CovercryptBulk,
}
impl Display for CCryptographicAlgorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value = match self {
Self::AES => "aes",
Self::RSA => "rsa",
Self::ECDSA => "ecdsa",
Self::ECDH => "ecdh",
Self::EC => "ec",
Self::Chacha20 => "chacha20",
Self::Chacha20Poly1305 => "chacha20-poly1305",
Self::SHA3224 => "sha3224",
Self::SHA3256 => "sha3256",
Self::SHA3384 => "sha3384",
Self::SHA3512 => "sha3512",
Self::Ed25519 => "ed25519",
Self::Ed448 => "ed448",
Self::Covercrypt => "covercrypt",
Self::CovercryptBulk => "covercrypt-bulk",
};
write!(f, "{value}")
}
}
impl From<CCryptographicAlgorithm> for CryptographicAlgorithm {
fn from(value: CCryptographicAlgorithm) -> Self {
match value {
CCryptographicAlgorithm::AES => Self::AES,
CCryptographicAlgorithm::RSA => Self::RSA,
CCryptographicAlgorithm::ECDSA => Self::ECDSA,
CCryptographicAlgorithm::ECDH => Self::ECDH,
CCryptographicAlgorithm::EC => Self::EC,
CCryptographicAlgorithm::Chacha20 => Self::ChaCha20,
CCryptographicAlgorithm::Chacha20Poly1305 => Self::ChaCha20Poly1305,
CCryptographicAlgorithm::SHA3224 => Self::SHA3224,
CCryptographicAlgorithm::SHA3256 => Self::SHA3256,
CCryptographicAlgorithm::SHA3384 => Self::SHA3384,
CCryptographicAlgorithm::SHA3512 => Self::SHA3512,
CCryptographicAlgorithm::Ed25519 => Self::Ed25519,
CCryptographicAlgorithm::Ed448 => Self::Ed448,
CCryptographicAlgorithm::Covercrypt => Self::CoverCrypt,
CCryptographicAlgorithm::CovercryptBulk => Self::CoverCryptBulk,
}
}
}
#[derive(Parser, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
pub struct VendorAttributeCli {
#[clap(long, short = 'v', requires = "attribute_name")]
pub vendor_identification: Option<String>,
#[clap(long, short = 'n', requires = "vendor_identification")]
pub attribute_name: Option<String>,
#[clap(long, requires = "vendor_identification")]
pub attribute_value: Option<String>,
}
impl TryFrom<&VendorAttributeCli> for Attribute {
type Error = crate::error::KmsCliError;
fn try_from(vendor_attribute: &VendorAttributeCli) -> Result<Self, Self::Error> {
let vendor_attribute = kmip_types::VendorAttribute {
vendor_identification: vendor_attribute
.vendor_identification
.clone()
.unwrap_or_default(),
attribute_name: vendor_attribute.attribute_name.clone().unwrap_or_default(),
attribute_value: VendorAttributeValue::ByteString(hex::decode(
vendor_attribute.attribute_value.clone().unwrap_or_default(),
)?),
};
Ok(Self::VendorAttribute(vendor_attribute))
}
}
impl TryFrom<&VendorAttributeCli> for VendorAttribute {
type Error = crate::error::KmsCliError;
fn try_from(vendor_attribute: &VendorAttributeCli) -> Result<Self, Self::Error> {
let vendor_attribute = Self {
vendor_identification: vendor_attribute
.vendor_identification
.clone()
.unwrap_or_default(),
attribute_name: vendor_attribute.attribute_name.clone().unwrap_or_default(),
attribute_value: VendorAttributeValue::ByteString(hex::decode(
vendor_attribute.attribute_value.clone().unwrap_or_default(),
)?),
};
Ok(vendor_attribute)
}
}
#[derive(Parser, Default, Debug, Clone)]
pub struct SetOrDeleteAttributes {
#[clap(long = ATTRIBUTE_ID, short = 'i', group = "id-tags")]
pub id: Option<String>,
#[clap(long = "tag", short = 't', value_name = "TAG", group = "id-tags")]
pub tags: Option<Vec<String>>,
#[clap(long, short = 'd')]
pub activation_date: Option<i64>,
#[clap(long, short = 'a')]
pub cryptographic_algorithm: Option<CCryptographicAlgorithm>,
#[clap(long)]
pub cryptographic_length: Option<i32>,
#[clap(long, short = 'u')]
pub key_usage: Option<Vec<KeyUsage>>,
#[clap(long)]
pub public_key_id: Option<String>,
#[clap(long)]
pub private_key_id: Option<String>,
#[clap(long)]
pub certificate_id: Option<String>,
#[clap(long = "p12-id")]
pub pkcs12_certificate_id: Option<String>,
#[clap(long = "p12-pwd")]
pub pkcs12_password_certificate: Option<String>,
#[clap(long)]
pub parent_id: Option<String>,
#[clap(long)]
pub child_id: Option<String>,
#[clap(long = "name")]
pub name: Option<String>,
#[clap(flatten)]
pub vendor_attributes: Option<VendorAttributeCli>,
}
impl SetOrDeleteAttributes {
pub(crate) fn get_attributes_from_args(&self) -> KmsCliResult<Vec<Attribute>> {
let mut result = Vec::new();
if let Some(timestamp) = self.activation_date {
match OffsetDateTime::from_unix_timestamp(timestamp) {
Ok(activation_date) => {
let attribute = Attribute::ActivationDate(activation_date);
result.push(attribute);
}
Err(e) => {
return Err(crate::error::KmsCliError::Conversion(format!(
"Could not convert {timestamp:?} to OffsetDateTime: {e:?}"
)));
}
}
}
if let Some(cryptographic_algorithm) = &self.cryptographic_algorithm {
let attribute = Attribute::CryptographicAlgorithm(CryptographicAlgorithm::from(
cryptographic_algorithm.to_owned(),
));
result.push(attribute);
}
if let Some(cryptographic_length) = &self.cryptographic_length {
let attribute = Attribute::CryptographicLength(*cryptographic_length);
result.push(attribute);
}
if let Some(key_usage) = &self.key_usage {
let cryptographic_usage_mask =
build_usage_mask_from_key_usage(key_usage).ok_or_else(|| {
crate::error::KmsCliError::Conversion(format!(
"Could not convert {key_usage:?} to cryptographic usage mask"
))
})?;
let attribute = Attribute::CryptographicUsageMask(cryptographic_usage_mask);
result.push(attribute);
}
if let Some(public_key_id) = &self.public_key_id {
let attribute = Attribute::Link(Link {
link_type: LinkType::PublicKeyLink,
linked_object_identifier: LinkedObjectIdentifier::TextString(public_key_id.clone()),
});
result.push(attribute);
}
if let Some(private_key_id) = &self.private_key_id {
let attribute = Attribute::Link(Link {
link_type: LinkType::PrivateKeyLink,
linked_object_identifier: LinkedObjectIdentifier::TextString(
private_key_id.clone(),
),
});
result.push(attribute);
}
if let Some(certificate_id) = &self.certificate_id {
let attribute = Attribute::Link(Link {
link_type: LinkType::CertificateLink,
linked_object_identifier: LinkedObjectIdentifier::TextString(
certificate_id.clone(),
),
});
result.push(attribute);
}
if let Some(pkcs12_certificate_id) = &self.pkcs12_certificate_id {
let attribute = Attribute::Link(Link {
link_type: LinkType::PKCS12CertificateLink,
linked_object_identifier: LinkedObjectIdentifier::TextString(
pkcs12_certificate_id.clone(),
),
});
result.push(attribute);
}
if let Some(pkcs12_password_certificate) = &self.pkcs12_password_certificate {
let attribute = Attribute::Link(Link {
link_type: LinkType::PKCS12PasswordLink,
linked_object_identifier: LinkedObjectIdentifier::TextString(
pkcs12_password_certificate.clone(),
),
});
result.push(attribute);
}
if let Some(parent_id) = &self.parent_id {
let attribute = Attribute::Link(Link {
link_type: LinkType::ParentLink,
linked_object_identifier: LinkedObjectIdentifier::TextString(parent_id.clone()),
});
result.push(attribute);
}
if let Some(child_id) = &self.child_id {
let attribute = Attribute::Link(Link {
link_type: LinkType::ChildLink,
linked_object_identifier: LinkedObjectIdentifier::TextString(child_id.clone()),
});
result.push(attribute);
}
if let Some(name_value) = &self.name {
let attribute = Attribute::Name(Name {
name_value: name_value.clone(),
name_type: NameType::UninterpretedTextString,
});
result.push(attribute);
}
if let Some(vendor_attributes) = &self.vendor_attributes {
let attribute = Attribute::try_from(vendor_attributes)?;
result.push(attribute);
}
Ok(result)
}
}
#[derive(Parser, Debug)]
#[clap(verbatim_doc_comment)]
pub struct SetAttributesAction {
#[clap(flatten)]
pub(crate) requested_attributes: SetOrDeleteAttributes,
}
impl SetAttributesAction {
pub(crate) async fn set_attribute(
&self,
kms_rest_client: &KmsClient,
id: &str,
attribute: Attribute,
) -> KmsCliResult<()> {
let id = UniqueIdentifier::TextString(id.to_owned());
let SetAttributeResponse { unique_identifier } = kms_rest_client
.set_attribute(SetAttribute {
unique_identifier: Some(id.clone()),
new_attribute: attribute.clone(),
})
.await?;
info!("SetAttributes response for {unique_identifier}: {attribute}");
let mut stdout = console::Stdout::new("Attribute set successfully");
stdout.set_tags(self.requested_attributes.tags.as_ref());
stdout.set_unique_identifier(&id);
stdout.set_attribute(attribute);
stdout.write()?;
Ok(())
}
pub async fn process(&self, kms_rest_client: KmsClient) -> KmsCliResult<()> {
trace!("{self:?}");
let id = get_key_uid(
self.requested_attributes.id.as_ref(),
self.requested_attributes.tags.as_ref(),
ATTRIBUTE_ID,
)?;
let attributes_to_set = self.requested_attributes.get_attributes_from_args()?;
if attributes_to_set.is_empty() {
cli_bail!("No attribute specified")
}
for attribute in attributes_to_set {
self.set_attribute(&kms_rest_client, &id, attribute).await?;
}
Ok(())
}
}