use std::path::PathBuf;
use clap::{Parser, Subcommand};
use cosmian_kms_client::{
ExportObjectParams, KmsClient,
cosmian_kmip::{
KmipResultHelper,
ttlv::{TTLV, from_ttlv},
},
export_object,
kmip_2_1::kmip_objects::Object,
read_from_json_file,
};
use cosmian_kms_crypto::{
CryptoError,
crypto::cover_crypt::{
attributes::RekeyEditAction, kmip_requests::build_rekey_keypair_request,
},
reexport::{
cosmian_cover_crypt::{
AccessStructure, EncryptionHint, MasterPublicKey, QualifiedAttribute,
},
cosmian_crypto_core::bytes_ser_de::Serializable,
},
};
use crate::{
actions::kms::{console, labels::KEY_ID, shared::get_key_uid},
cli_bail,
error::result::KmsCliResult,
};
#[derive(Subcommand)]
pub enum AccessStructureCommands {
View(ViewAction),
AddAttribute(AddQualifiedAttributeAction),
RemoveAttribute(RemoveAttributeAction),
DisableAttribute(DisableAttributeAction),
RenameAttribute(RenameAttributeAction),
}
impl AccessStructureCommands {
pub async fn process(&self, kms_rest_client: KmsClient) -> KmsCliResult<()> {
match self {
Self::View(action) => {
action.run(kms_rest_client).await?;
}
Self::AddAttribute(action) => {
action.run(kms_rest_client).await?;
}
Self::RemoveAttribute(action) => {
action.run(kms_rest_client).await?;
}
Self::DisableAttribute(action) => {
action.run(kms_rest_client).await?;
}
Self::RenameAttribute(action) => {
action.run(kms_rest_client).await?;
}
}
Ok(())
}
}
#[derive(Parser)]
#[clap(verbatim_doc_comment)]
pub struct ViewAction {
#[clap(long = KEY_ID, short = 'i', required_unless_present = "key_file")]
pub(crate) key_id: Option<String>,
#[clap(long = "key-file", short = 'f')]
pub(crate) key_file: Option<PathBuf>,
}
impl ViewAction {
pub async fn run(&self, kms_rest_client: KmsClient) -> KmsCliResult<AccessStructure> {
let object: Object = if let Some(id) = &self.key_id {
export_object(
&kms_rest_client,
id,
ExportObjectParams {
unwrap: true,
..ExportObjectParams::default()
},
)
.await?
.1
} else if let Some(key_file) = &self.key_file {
let ttlv: TTLV = read_from_json_file(key_file)?;
from_ttlv(ttlv).map_err(|e| {
CryptoError::Kmip(format!("Failed deserializing the CoverCrypt MPK: {e}"))
})?
} else {
cli_bail!("either a key ID or a key TTLV file must be supplied");
};
let mpk = MasterPublicKey::deserialize(&object.key_block()?.covercrypt_key_bytes()?)
.map_err(|e| {
CryptoError::Kmip(format!("Failed deserializing the CoverCrypt MPK: {e}"))
})?;
let stdout: String = format!("{:?}", mpk.access_structure);
console::Stdout::new(&stdout).write()?;
Ok(mpk.access_structure)
}
}
#[derive(Parser)]
#[clap(verbatim_doc_comment)]
pub struct AddQualifiedAttributeAction {
#[clap(required = true)]
pub(crate) attribute: String,
#[clap(required = false, long, default_value = "false")]
pub(crate) hybridized: bool,
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
pub(crate) secret_key_id: Option<String>,
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
pub(crate) tags: Option<Vec<String>>,
}
impl AddQualifiedAttributeAction {
pub async fn run(&self, kms_rest_client: KmsClient) -> KmsCliResult<()> {
let id = get_key_uid(self.secret_key_id.as_ref(), self.tags.as_ref(), KEY_ID)?;
let rekey_query = build_rekey_keypair_request(
kms_rest_client.config.vendor_id.as_str(),
&id,
&RekeyEditAction::AddAttribute(vec![(
QualifiedAttribute::try_from(self.attribute.as_str())?,
if self.hybridized {
EncryptionHint::Hybridized
} else {
EncryptionHint::Classic
},
None,
)]),
)?;
let rekey_response = kms_rest_client
.rekey_keypair(rekey_query)
.await
.with_context(|| "failed adding an attribute to the master keys")?;
let stdout = format!(
"New attribute {} was successfully added to the master secret key {} and master \
public key {}.",
&self.attribute,
&rekey_response.private_key_unique_identifier,
&rekey_response.public_key_unique_identifier,
);
console::Stdout::new(&stdout).write()
}
}
#[derive(Parser)]
#[clap(verbatim_doc_comment)]
pub struct RenameAttributeAction {
#[clap(required = true)]
pub(crate) attribute: String,
#[clap(required = true)]
pub(crate) new_name: String,
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
pub(crate) master_secret_key_id: Option<String>,
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
pub(crate) tags: Option<Vec<String>>,
}
impl RenameAttributeAction {
pub async fn run(&self, kms_rest_client: KmsClient) -> KmsCliResult<()> {
let id = get_key_uid(
self.master_secret_key_id.as_ref(),
self.tags.as_ref(),
KEY_ID,
)?;
let rekey_query = build_rekey_keypair_request(
kms_rest_client.config.vendor_id.as_str(),
&id,
&RekeyEditAction::RenameAttribute(vec![(
QualifiedAttribute::try_from(self.attribute.as_str())?,
self.new_name.clone(),
)]),
)?;
kms_rest_client
.rekey_keypair(rekey_query)
.await
.with_context(|| "failed renaming an attribute in the master keys' access structure")?;
let stdout = format!(
"Attribute {} was successfully renamed to {}.",
&self.attribute, &self.new_name
);
console::Stdout::new(&stdout).write()?;
Ok(())
}
}
#[derive(Parser)]
#[clap(verbatim_doc_comment)]
pub struct DisableAttributeAction {
#[clap(required = true)]
pub(crate) attribute: String,
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
pub(crate) master_secret_key_id: Option<String>,
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
pub(crate) tags: Option<Vec<String>>,
}
impl DisableAttributeAction {
pub async fn run(&self, kms_rest_client: KmsClient) -> KmsCliResult<()> {
let id = get_key_uid(
self.master_secret_key_id.as_ref(),
self.tags.as_ref(),
KEY_ID,
)?;
let rekey_query = build_rekey_keypair_request(
kms_rest_client.config.vendor_id.as_str(),
&id,
&RekeyEditAction::DisableAttribute(vec![QualifiedAttribute::try_from(
self.attribute.as_str(),
)?]),
)?;
let rekey_response = kms_rest_client
.rekey_keypair(rekey_query)
.await
.with_context(|| "failed disabling an attribute from the master keys")?;
let stdout = format!(
"Attribute {} was successfully disabled from the master public key {}.",
&self.attribute, &rekey_response.public_key_unique_identifier,
);
console::Stdout::new(&stdout).write()?;
Ok(())
}
}
#[derive(Parser)]
#[clap(verbatim_doc_comment)]
pub struct RemoveAttributeAction {
#[clap(required = true)]
pub(crate) attribute: String,
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
pub(crate) master_secret_key_id: Option<String>,
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
pub(crate) tags: Option<Vec<String>>,
}
impl RemoveAttributeAction {
pub async fn run(&self, kms_rest_client: KmsClient) -> KmsCliResult<()> {
let id = get_key_uid(
self.master_secret_key_id.as_ref(),
self.tags.as_ref(),
KEY_ID,
)?;
let rekey_query = build_rekey_keypair_request(
kms_rest_client.config.vendor_id.as_str(),
&id,
&RekeyEditAction::DeleteAttribute(vec![QualifiedAttribute::try_from(
self.attribute.as_str(),
)?]),
)?;
let rekey_response = kms_rest_client
.rekey_keypair(rekey_query)
.await
.with_context(|| "failed removing an attribute from the master keys")?;
let stdout = format!(
"Attribute {} was successfully removed from the master secret key {} and master \
public key {}.",
&self.attribute,
&rekey_response.private_key_unique_identifier,
&rekey_response.public_key_unique_identifier,
);
console::Stdout::new(&stdout).write()?;
Ok(())
}
}