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_objects::Object, kmip_types::CryptographicAlgorithm},
export_object,
kmip_2_1::{
kmip_attributes::Attributes,
kmip_operations::Destroy,
kmip_types::UniqueIdentifier,
requests::{create_symmetric_key_kmip_object, import_object_request},
},
read_object_from_json_ttlv_file, write_kmip_object_to_file,
};
use cosmian_kms_crypto::crypto::wrap::unwrap_key_block;
use cosmian_logger::trace;
use uuid::Uuid;
use crate::{
actions::kms::console,
cli_bail,
error::result::{KmsCliResult, KmsCliResultHelper},
};
#[derive(Parser, Default, Debug)]
#[clap(verbatim_doc_comment)]
pub struct UnwrapSecretDataOrKeyAction {
#[clap(required = true)]
pub(crate) key_file_in: PathBuf,
#[clap(required = false)]
pub(crate) key_file_out: Option<PathBuf>,
#[clap(
long = "unwrap-key-b64",
short = 'k',
required = false,
group = "unwrap"
)]
pub(crate) unwrap_key_b64: Option<String>,
#[clap(
long = "unwrap-key-id",
short = 'i',
required = false,
group = "unwrap"
)]
pub(crate) unwrap_key_id: Option<String>,
#[clap(
long = "unwrap-key-file",
short = 'f',
required = false,
group = "unwrap"
)]
pub(crate) unwrap_key_file: Option<PathBuf>,
}
impl UnwrapSecretDataOrKeyAction {
pub async fn run(&self, kms_rest_client: KmsClient) -> KmsCliResult<()> {
let mut object = read_object_from_json_ttlv_file(&self.key_file_in)?;
let object_type = object.object_type();
let vendor_id = kms_rest_client.config.vendor_id.as_str();
let unwrapping_key = if let Some(b64) = &self.unwrap_key_b64 {
trace!("unwrap using a base64 encoded key: {b64}");
let key_bytes = general_purpose::STANDARD
.decode(b64)
.with_context(|| "failed decoding the unwrap key")?;
create_symmetric_key_kmip_object(
vendor_id,
&key_bytes,
&Attributes {
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
..Default::default()
},
)?
} else if let Some(key_id) = &self.unwrap_key_id {
if key_id.contains("::") {
trace!("unwrap using server-side HSM crypto oracle for key: {key_id}");
return self
.unwrap_via_server(kms_rest_client, object, key_id)
.await;
}
trace!("unwrap using the KMS server with the unique identifier of the unwrapping key");
export_object(&kms_rest_client, key_id, ExportObjectParams::default())
.await?
.1
} else if let Some(key_file) = &self.unwrap_key_file {
trace!("unwrap using a key file path");
read_object_from_json_ttlv_file(key_file)?
} else {
cli_bail!("one of the unwrapping options must be specified");
};
unwrap_key_block(object.key_block_mut()?, &unwrapping_key)?;
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 unwrapped in file: {}",
object_type,
self.key_file_in.display(),
&output_file.display()
);
console::Stdout::new(&stdout).write()?;
Ok(())
}
async fn unwrap_via_server(
&self,
kms_rest_client: KmsClient,
object: Object,
_unwrap_key_id: &str,
) -> KmsCliResult<()> {
let vendor_id = kms_rest_client.config.vendor_id.as_str();
let tmp_id = Uuid::new_v4().to_string();
let import_request = import_object_request(
vendor_id,
Some(tmp_id.clone()),
object,
None,
true, true, std::iter::empty::<String>(),
)?;
kms_rest_client.import(import_request).await.with_context(
|| "server-side unwrap: failed to import the wrapped key to the KMS server",
)?;
let (_, unwrapped_object, _) = export_object(
&kms_rest_client,
&tmp_id,
ExportObjectParams {
unwrap: true,
..ExportObjectParams::default()
},
)
.await
.with_context(|| "server-side unwrap: failed to export the unwrapped key")?;
let destroy_request = Destroy {
unique_identifier: Some(UniqueIdentifier::TextString(tmp_id.clone())),
remove: true,
cascade: true,
expected_object_type: None,
};
kms_rest_client
.destroy(destroy_request)
.await
.with_context(|| "server-side unwrap: failed to destroy temporary KMS key")?;
let object_type = unwrapped_object.object_type();
let output_file = self
.key_file_out
.as_ref()
.unwrap_or(&self.key_file_in)
.clone();
write_kmip_object_to_file(&unwrapped_object, &output_file)?;
let stdout = format!(
"The key of type {:?} in file {} was unwrapped via the KMS server in file: {}",
object_type,
self.key_file_in.display(),
&output_file.display()
);
console::Stdout::new(&stdout).write()?;
Ok(())
}
}