use std::path::PathBuf;
use base64::{Engine as _, engine::general_purpose};
use clap::Parser;
use cosmian_kms_client::{
ExportObjectParams, KmsClient,
cosmian_kmip::kmip_2_1::{
kmip_data_structures::KeyWrappingSpecification, kmip_types::CryptographicAlgorithm,
},
export_object,
kmip_2_1::{kmip_attributes::Attributes, requests::create_symmetric_key_kmip_object},
read_object_from_json_ttlv_file, write_kmip_object_to_file,
};
use cosmian_kms_crypto::crypto::{
password_derivation::derive_key_from_password, wrap::wrap_object_with_key,
};
use crate::{
actions::kms::{console, shared::SYMMETRIC_WRAPPING_KEY_SIZE},
cli_bail,
error::result::{KmsCliResult, KmsCliResultHelper},
};
#[derive(Parser, Default, Debug)]
#[clap(verbatim_doc_comment)]
pub struct WrapSecretDataOrKeyAction {
#[clap(required = true)]
pub(crate) key_file_in: PathBuf,
#[clap(required = false)]
pub(crate) key_file_out: Option<PathBuf>,
#[clap(long = "wrap-password", short = 'p', required = false, group = "wrap")]
pub(crate) wrap_password: Option<String>,
#[clap(long = "wrap-key-b64", short = 'k', required = false, group = "wrap")]
pub(crate) wrap_key_b64: Option<String>,
#[clap(long = "wrap-key-id", short = 'i', required = false, group = "wrap")]
pub(crate) wrap_key_id: Option<String>,
#[clap(long = "wrap-key-file", short = 'f', required = false, group = "wrap")]
pub(crate) wrap_key_file: Option<PathBuf>,
}
impl WrapSecretDataOrKeyAction {
#[expect(clippy::print_stdout)]
pub async fn run(&self, kms_rest_client: KmsClient) -> KmsCliResult<String> {
let mut object = read_object_from_json_ttlv_file(&self.key_file_in)?;
if object.key_wrapping_data().is_some() {
cli_bail!("cannot wrap an already wrapped key");
}
let object_type = object.object_type();
let vendor_id = kms_rest_client.config.vendor_id.as_str();
let wrapping_key = if let Some(b64) = &self.wrap_key_b64 {
let key_bytes = general_purpose::STANDARD
.decode(b64)
.with_context(|| "failed decoding the wrap key")?;
create_symmetric_key_kmip_object(
vendor_id,
&key_bytes,
&Attributes {
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
..Default::default()
},
)?
} else if let Some(password) = &self.wrap_password {
let key_bytes = derive_key_from_password::<SYMMETRIC_WRAPPING_KEY_SIZE>(
&[0_u8; 16],
password.as_bytes(),
)?;
let symmetric_key_object = create_symmetric_key_kmip_object(
vendor_id,
key_bytes.as_ref(),
&Attributes {
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
..Default::default()
},
)?;
println!(
"Wrapping key: {}. This is the only time that this wrapping key will be printed.",
general_purpose::STANDARD.encode(&*key_bytes)
);
symmetric_key_object
} else if let Some(key_id) = &self.wrap_key_id {
export_object(&kms_rest_client, key_id, ExportObjectParams::default())
.await?
.1
} else if let Some(key_file) = &self.wrap_key_file {
read_object_from_json_ttlv_file(key_file)?
} else {
cli_bail!("one of the wrapping options must be specified");
};
wrap_object_with_key(
&mut object,
&wrapping_key,
&KeyWrappingSpecification::default(),
)?;
let output_file = self
.key_file_out
.as_ref()
.unwrap_or(&self.key_file_in)
.clone();
write_kmip_object_to_file(&object, &output_file)?;
let stdout = format!(
"The key of type {:?} in file {} was wrapped in file: {}",
object_type,
self.key_file_in.display(),
output_file.display()
);
console::Stdout::new(&stdout).write()?;
let (wrapping_key_bytes, _) = wrapping_key.key_block()?.key_bytes_and_attributes()?;
Ok(general_purpose::STANDARD.encode(wrapping_key_bytes))
}
}