use std::path::PathBuf;
use base64::{Engine, prelude::BASE64_STANDARD};
use clap::{ArgGroup, Parser};
use cosmian_kmip::kmip_2_1::kmip_types::UniqueIdentifier;
use cosmian_kms_client::{
KmsClient,
reexport::cosmian_kms_client_utils::import_utils::{ImportKeyFormat, KeyUsage},
};
use crate::{
actions::kms::{
aws::byok::wrapping_algorithms::AwsKmsWrappingAlgorithm,
shared::ImportSecretDataOrKeyAction,
},
error::{KmsCliError, result::KmsCliResult},
};
fn validate_and_normalize_kek_base64(s: &str) -> Result<String, String> {
let normalized = s.split_whitespace().collect();
let decoded = BASE64_STANDARD
.decode(&normalized)
.map_err(|e| format!("Invalid base64 encoding: {e}"))?;
if decoded.is_empty() {
return Err("KEK decoded data is empty".to_owned());
}
if decoded.len() > 4096 {
return Err(format!(
"KEK decoded data exceeds maximum length of 4096 bytes (got {})",
decoded.len()
));
}
Ok(normalized)
}
#[derive(Parser)]
#[clap(verbatim_doc_comment)]
#[clap(group(ArgGroup::new("kek_input").required(true).args(["kek_base64", "kek_file"])))] pub struct ImportKekAction {
#[clap(
short = 'b',
long,
value_parser = clap::builder::ValueParser::new(validate_and_normalize_kek_base64),
group = "kek_input"
)]
pub(crate) kek_base64: Option<String>,
#[clap(short = 'f', long, group = "kek_input")]
pub(crate) kek_file: Option<PathBuf>,
#[clap(short = 'w', long, required = true)]
pub(crate) wrapping_algorithm: AwsKmsWrappingAlgorithm,
#[clap(short = 'a', long, required = false)]
pub(crate) key_arn: Option<String>,
#[clap(short = 'i', long, required = false)]
pub(crate) key_id: Option<String>,
}
impl ImportKekAction {
pub async fn run(&self, kms_client: KmsClient) -> KmsCliResult<UniqueIdentifier> {
let mut tags = vec![
"aws".to_owned(),
format!("wrapping_algorithm:{}", self.wrapping_algorithm),
];
if let Some(arn) = &self.key_arn {
tags.push(format!("key_arn:{arn}"));
}
let import_action = ImportSecretDataOrKeyAction {
key_file: match (&self.kek_file, &self.kek_base64) {
(Some(file), _) => file.clone(),
(None, Some(base64_str)) => {
let temp_path = std::env::temp_dir().join(format!("{}", uuid::Uuid::new_v4()));
std::fs::write(&temp_path, BASE64_STANDARD.decode(base64_str)?)?;
temp_path
}
(None, None) => {
return Err(KmsCliError::Default(
"KEK file or base64 data must be provided".to_owned(),
));
}
},
key_id: self.key_id.clone(),
key_format: ImportKeyFormat::Pkcs8Pub,
tags,
key_usage: Some(vec![KeyUsage::WrapKey, KeyUsage::Encrypt]),
replace_existing: true,
..Default::default()
};
let res = import_action.run(kms_client).await;
if self.kek_file.is_none() && self.kek_base64.is_some() {
drop(std::fs::remove_file(&import_action.key_file));
}
res
}
}