use std::path::PathBuf;
use base64::Engine;
use clap::Parser;
use cosmian_kms_client::{
ExportObjectParams, KmsClient, export_object,
kmip_2_1::{
kmip_data_structures::{KeyMaterial, KeyValue},
kmip_objects::Object,
kmip_types::UniqueIdentifier,
},
reexport::cosmian_kms_client_utils::export_utils::{
ExportKeyFormat, WrappingAlgorithm, der_to_pem, prepare_key_export_elements,
},
write_bytes_to_file, write_kmip_object_to_file,
};
use super::get_key_uid;
use crate::{
actions::kms::{console, labels::KEY_ID},
error::{
KmsCliError,
result::{KmsCliResult, KmsCliResultHelper},
},
};
#[derive(Parser, Default, Debug)]
#[clap(verbatim_doc_comment)]
pub struct ExportSecretDataOrKeyAction {
#[clap(required = true)]
pub key_file: PathBuf,
#[clap(long = KEY_ID, short = 'k', group = "key-tags")]
pub key_id: Option<String>,
#[clap(long = "tag", short = 't', value_name = "TAG", group = "key-tags")]
pub tags: Option<Vec<String>>,
#[clap(
long = "key-format",
short = 'f',
default_value = "json-ttlv",
verbatim_doc_comment
)]
pub export_format: ExportKeyFormat,
#[clap(
long = "unwrap",
short = 'u',
default_value = "false",
group = "wrapping"
)]
pub unwrap: bool,
#[clap(
long = "wrap-key-id",
short = 'w',
required = false,
group = "wrapping"
)]
pub wrap_key_id: Option<String>,
#[clap(
long = "allow-revoked",
short = 'i',
default_value = "false",
verbatim_doc_comment
)]
pub allow_revoked: bool,
#[clap(
long = "wrapping-algorithm",
short = 'm',
default_value = None,
verbatim_doc_comment
)]
pub wrapping_algorithm: Option<WrappingAlgorithm>,
#[clap(
long = "authenticated-additional-data",
short = 'd',
default_value = None,
)]
pub authenticated_additional_data: Option<String>,
}
impl ExportSecretDataOrKeyAction {
pub async fn run(&self, kms_rest_client: KmsClient) -> KmsCliResult<UniqueIdentifier> {
let id = get_key_uid(self.key_id.as_ref(), self.tags.as_ref(), KEY_ID)?;
let (key_format_type, encode_to_pem, encode_to_ttlv, wrapping_cryptographic_parameters) =
prepare_key_export_elements(&self.export_format, &self.wrapping_algorithm)?;
let export_params = ExportObjectParams {
unwrap: self.unwrap,
wrapping_key_id: self.wrap_key_id.as_deref(),
allow_revoked: self.allow_revoked,
key_format_type,
encode_to_ttlv,
wrapping_cryptographic_parameters,
authenticated_encryption_additional_data: self.authenticated_additional_data.clone(),
};
let (id, object, _) = export_object(&kms_rest_client, &id, export_params).await?;
if self.export_format == ExportKeyFormat::JsonTtlv {
write_kmip_object_to_file(&object, &self.key_file)?;
} else if self.export_format == ExportKeyFormat::Base64 {
let base64_key = base64::engine::general_purpose::STANDARD
.encode(get_object_bytes(&object)?)
.to_lowercase();
write_bytes_to_file(base64_key.as_bytes(), &self.key_file)?;
} else {
let bytes = {
let mut bytes = get_object_bytes(&object)?;
if encode_to_pem {
bytes = der_to_pem(
bytes.as_slice(),
key_format_type.context(
"Server Error: the Key Format Type should be known at this stage",
)?,
object.object_type(),
)?
.to_vec();
}
bytes
};
write_bytes_to_file(&bytes, &self.key_file)?;
}
let stdout = format!(
"The key {} of type {} was exported to {:?}",
&id,
object.object_type(),
self.key_file.display()
);
let mut stdout = console::Stdout::new(&stdout);
stdout.set_unique_identifier(&id);
stdout.write()?;
Ok(id)
}
}
fn get_object_bytes(object: &Object) -> KmsCliResult<Vec<u8>> {
if let Object::OpaqueObject(o) = object {
return Ok(o.opaque_data_value.clone());
}
let key_block = object.key_block()?;
match key_block
.key_value
.as_ref()
.ok_or_else(|| KmsCliError::Default("Key value is missing".to_owned()))?
{
KeyValue::ByteString(v) => Ok(v.to_vec()),
KeyValue::Structure { key_material, .. } => match key_material {
KeyMaterial::ByteString(v) => Ok(v.to_vec()),
KeyMaterial::TransparentSymmetricKey { key } => Ok(key.to_vec()),
KeyMaterial::TransparentECPrivateKey { .. }
| KeyMaterial::TransparentECPublicKey { .. } => key_block
.ec_raw_bytes()
.map(|v| v.to_vec())
.map_err(Into::into),
x => Err(KmsCliError::Default(format!(
"Unsupported key material type: {x:?}"
))),
},
}
}