use base64::{Engine as _, engine::general_purpose};
use cosmian_kms_client::{
read_object_from_json_ttlv_file,
reexport::cosmian_kms_client_utils::{
create_utils::SecretDataType,
export_utils::{ExportKeyFormat, WrappingAlgorithm},
},
};
use cosmian_kms_crypto::reexport::cosmian_crypto_core::{
CsRng,
reexport::rand_core::{RngCore, SeedableRng},
};
use cosmian_logger::info;
use tempfile::TempDir;
use test_kms_server::start_default_test_kms_server;
use crate::{
actions::kms::{
secret_data::create_secret::CreateSecretDataAction, shared::ExportSecretDataOrKeyAction,
symmetric::keys::create_key::CreateKeyAction,
},
error::result::KmsCliResult,
};
#[tokio::test]
pub(crate) async fn test_create_secret_data() -> KmsCliResult<()> {
let ctx = start_default_test_kms_server().await;
let mut rng = CsRng::from_entropy();
let mut key = vec![0_u8; 32];
{
CreateSecretDataAction::default()
.run(ctx.get_owner_client())
.await?;
let _uid = CreateSecretDataAction::default()
.run(ctx.get_owner_client())
.await?;
rng.fill_bytes(&mut key);
let _uid = CreateSecretDataAction {
secret_value: Some("password".to_owned()),
secret_type: SecretDataType::Password,
..Default::default()
}
.run(ctx.get_owner_client())
.await?;
}
Ok(())
}
#[tokio::test]
pub(crate) async fn test_secret_data_export_with_wrapping() -> KmsCliResult<()> {
let ctx = start_default_test_kms_server().await;
let tmp_dir = TempDir::new()?;
let tmp_path = tmp_dir.path();
let wrapping_key_id = CreateKeyAction::default()
.run(ctx.get_owner_client())
.await?;
let secret_data_id = CreateSecretDataAction {
secret_value: Some("test-secret-password".to_owned()),
secret_type: SecretDataType::Password,
tags: vec!["test-secret".to_string()],
..Default::default()
}
.run(ctx.get_owner_client())
.await?;
let wrapped_secret_file = tmp_path.join("wrapped_secret.json");
ExportSecretDataOrKeyAction {
key_file: wrapped_secret_file.clone(),
key_id: Some(secret_data_id.to_string()),
export_format: ExportKeyFormat::JsonTtlv,
wrap_key_id: Some(wrapping_key_id.to_string()),
..Default::default()
}
.run(ctx.get_owner_client())
.await?;
let wrapped_object = read_object_from_json_ttlv_file(&wrapped_secret_file)?;
assert!(wrapped_object.key_wrapping_data().is_some());
let raw_wrapped_secret_file = tmp_path.join("wrapped_secret.raw");
ExportSecretDataOrKeyAction {
key_file: raw_wrapped_secret_file.clone(),
key_id: Some(secret_data_id.to_string()),
export_format: ExportKeyFormat::Raw,
wrap_key_id: Some(wrapping_key_id.to_string()),
wrapping_algorithm: Some(WrappingAlgorithm::AesGCM),
..Default::default()
}
.run(ctx.get_owner_client())
.await?;
assert!(raw_wrapped_secret_file.exists());
let raw_wrapped_data = std::fs::read(&raw_wrapped_secret_file)?;
assert!(!raw_wrapped_data.is_empty());
let unwrapped_secret_file = tmp_path.join("unwrapped_secret.json");
ExportSecretDataOrKeyAction {
key_file: unwrapped_secret_file.clone(),
key_id: Some(secret_data_id.to_string()),
export_format: ExportKeyFormat::JsonTtlv,
..Default::default()
}
.run(ctx.get_owner_client())
.await?;
let unwrapped_object = read_object_from_json_ttlv_file(&unwrapped_secret_file)?;
assert!(unwrapped_object.key_wrapping_data().is_none());
Ok(())
}
#[tokio::test]
pub(crate) async fn test_secret_data_export_with_different_wrapping_algorithms() -> KmsCliResult<()>
{
let ctx = start_default_test_kms_server().await;
let tmp_dir = TempDir::new()?;
let tmp_path = tmp_dir.path();
let wrapping_key_id = CreateKeyAction::default()
.run(ctx.get_owner_client())
.await?;
let secret_data_id = CreateSecretDataAction {
secret_value: Some("test-secret-for-wrapping".to_owned()),
secret_type: SecretDataType::Password,
..Default::default()
}
.run(ctx.get_owner_client())
.await?;
let wrapping_algorithms = [
WrappingAlgorithm::AESKeyWrapPadding, WrappingAlgorithm::NistKeyWrap, WrappingAlgorithm::AesGCM,
];
for (i, algorithm) in wrapping_algorithms.iter().enumerate() {
let wrapped_file = tmp_path.join(format!("wrapped_secret_{i}.json"));
ExportSecretDataOrKeyAction {
key_file: wrapped_file.clone(),
key_id: Some(secret_data_id.to_string()),
export_format: ExportKeyFormat::JsonTtlv,
wrap_key_id: Some(wrapping_key_id.to_string()),
wrapping_algorithm: Some(algorithm.clone()),
..Default::default()
}
.run(ctx.get_owner_client())
.await?;
let wrapped_object = read_object_from_json_ttlv_file(&wrapped_file)?;
assert!(wrapped_object.key_wrapping_data().is_some());
}
Ok(())
}
#[tokio::test]
pub(crate) async fn test_create_secret_data_with_wrapping_key() -> KmsCliResult<()> {
let ctx = start_default_test_kms_server().await;
let tmp_dir = TempDir::new()?;
let tmp_path = tmp_dir.path();
let wrapping_key_id = CreateKeyAction::default()
.run(ctx.get_owner_client())
.await?;
let secret_data_id = CreateSecretDataAction {
secret_value: Some("wrapped-at-creation".to_owned()),
secret_type: SecretDataType::Password,
wrapping_key_id: Some(wrapping_key_id.to_string()),
..Default::default()
}
.run(ctx.get_owner_client())
.await?;
let export_file = tmp_path.join("secret_wrapped_at_creation.json");
ExportSecretDataOrKeyAction {
key_file: export_file.clone(),
key_id: Some(secret_data_id.to_string()),
export_format: ExportKeyFormat::JsonTtlv,
..Default::default()
}
.run(ctx.get_owner_client())
.await?;
let object = read_object_from_json_ttlv_file(&export_file)?;
assert!(object.key_wrapping_data().is_some());
Ok(())
}
#[tokio::test]
pub(crate) async fn test_issue_549_exact_scenario() -> KmsCliResult<()> {
let ctx = start_default_test_kms_server().await;
let tmp_dir = TempDir::new()?;
let tmp_path = tmp_dir.path();
let wrap_key_id = CreateKeyAction::default()
.run(ctx.get_owner_client())
.await?;
let secret_id = CreateSecretDataAction {
secret_value: Some("sensitive-data-for-tas-plugin".to_owned()),
secret_type: SecretDataType::Password,
tags: vec!["tas-plugin".to_string()],
..Default::default()
}
.run(ctx.get_owner_client())
.await?;
let keyfile_path = tmp_path.join("keyfile.bin");
let result = ExportSecretDataOrKeyAction {
key_file: keyfile_path.clone(),
key_id: Some(secret_id.to_string()),
export_format: ExportKeyFormat::Raw,
wrap_key_id: Some(wrap_key_id.to_string()),
wrapping_algorithm: Some(WrappingAlgorithm::AesGCM),
..Default::default()
}
.run(ctx.get_owner_client())
.await;
match result {
Ok(_) => {
assert!(keyfile_path.exists());
let wrapped_data = std::fs::read(&keyfile_path)?;
assert!(!wrapped_data.is_empty());
info!("SUCCESS: SecretData export with wrapping is working!");
}
Err(e) => {
info!("ISSUE #549 REPRODUCTION: {}", e);
}
}
Ok(())
}
#[tokio::test]
pub(crate) async fn test_secret_data_wrapping_edge_cases() -> KmsCliResult<()> {
let ctx = start_default_test_kms_server().await;
let tmp_dir = TempDir::new()?;
let tmp_path = tmp_dir.path();
let wrapping_key_id = CreateKeyAction::default()
.run(ctx.get_owner_client())
.await?;
let secret_id = CreateSecretDataAction {
secret_value: Some("test-secret".to_owned()),
secret_type: SecretDataType::Password,
..Default::default()
}
.run(ctx.get_owner_client())
.await?;
let invalid_wrap_file = tmp_path.join("invalid_wrap.json");
let result = ExportSecretDataOrKeyAction {
key_file: invalid_wrap_file.clone(),
key_id: Some(secret_id.to_string()),
export_format: ExportKeyFormat::JsonTtlv,
wrap_key_id: Some("non-existent-key-id".to_string()),
..Default::default()
}
.run(ctx.get_owner_client())
.await;
assert!(
result.is_err(),
"Export with non-existent wrapping key should fail"
);
let wrapped_secret_id = CreateSecretDataAction {
secret_value: Some("already-wrapped-secret".to_owned()),
secret_type: SecretDataType::Password,
wrapping_key_id: Some(wrapping_key_id.to_string()),
..Default::default()
}
.run(ctx.get_owner_client())
.await?;
let double_wrap_file = tmp_path.join("double_wrap.json");
let result = ExportSecretDataOrKeyAction {
key_file: double_wrap_file.clone(),
key_id: Some(wrapped_secret_id.to_string()),
export_format: ExportKeyFormat::JsonTtlv,
wrap_key_id: Some(wrapping_key_id.to_string()),
..Default::default()
}
.run(ctx.get_owner_client())
.await;
if result.is_err() {
} else {
let object = read_object_from_json_ttlv_file(&double_wrap_file)?;
assert!(object.key_wrapping_data().is_some());
}
Ok(())
}
#[tokio::test]
pub(crate) async fn test_secret_data_export_with_unwrapping() -> KmsCliResult<()> {
let ctx = start_default_test_kms_server().await;
let tmp_dir = TempDir::new()?;
let tmp_path = tmp_dir.path();
let wrapping_key_id = CreateKeyAction::default()
.run(ctx.get_owner_client())
.await?;
let wrapped_secret_id = CreateSecretDataAction {
secret_value: Some("secret-to-unwrap".to_owned()),
secret_type: SecretDataType::Password,
wrapping_key_id: Some(wrapping_key_id.to_string()),
..Default::default()
}
.run(ctx.get_owner_client())
.await?;
let unwrapped_file = tmp_path.join("unwrapped_secret.json");
ExportSecretDataOrKeyAction {
key_file: unwrapped_file.clone(),
key_id: Some(wrapped_secret_id.to_string()),
export_format: ExportKeyFormat::JsonTtlv,
unwrap: true,
..Default::default()
}
.run(ctx.get_owner_client())
.await?;
let object = read_object_from_json_ttlv_file(&unwrapped_file)?;
assert!(object.key_wrapping_data().is_none());
let raw_unwrapped_file = tmp_path.join("raw_unwrapped_secret.bin");
ExportSecretDataOrKeyAction {
key_file: raw_unwrapped_file.clone(),
key_id: Some(wrapped_secret_id.to_string()),
export_format: ExportKeyFormat::Raw,
unwrap: true,
..Default::default()
}
.run(ctx.get_owner_client())
.await?;
assert!(raw_unwrapped_file.exists());
let raw_data = std::fs::read(&raw_unwrapped_file)?;
assert!(!raw_data.is_empty());
let secret_string = String::from_utf8(raw_data)?;
assert_eq!(secret_string, "secret-to-unwrap");
Ok(())
}
#[tokio::test]
pub(crate) async fn test_secret_data_base64_export() -> KmsCliResult<()> {
let ctx = start_default_test_kms_server().await;
let tmp_dir = TempDir::new()?;
let tmp_path = tmp_dir.path();
let secret_id = CreateSecretDataAction {
secret_value: Some("base64-test-secret".to_owned()),
secret_type: SecretDataType::Password,
..Default::default()
}
.run(ctx.get_owner_client())
.await?;
let base64_file = tmp_path.join("secret.b64");
ExportSecretDataOrKeyAction {
key_file: base64_file.clone(),
key_id: Some(secret_id.to_string()),
export_format: ExportKeyFormat::Base64,
..Default::default()
}
.run(ctx.get_owner_client())
.await?;
assert!(base64_file.exists());
let exported_data = std::fs::read(&base64_file)?;
assert!(!exported_data.is_empty());
let base64_encoded = general_purpose::STANDARD.encode(&exported_data);
assert!(!base64_encoded.is_empty());
let decoded_back = general_purpose::STANDARD.decode(&base64_encoded)?;
assert_eq!(exported_data, decoded_back);
Ok(())
}