use std::process::Command;
use assert_cmd::prelude::*;
use clap::ValueEnum;
use cosmian_kms_cli_actions::{
actions::{
derive_key::DeriveKeyAction, mac::CHashingAlgorithm,
secret_data::create_secret::CreateSecretDataAction,
},
reexport::cosmian_kms_client::{
KmsClient,
kmip_0::kmip_types::CryptographicUsageMask,
kmip_2_1::{
extra::VENDOR_ID_COSMIAN,
kmip_attributes::Attributes,
kmip_objects::ObjectType,
kmip_operations::Create,
kmip_types::{CryptographicAlgorithm, KeyFormatType},
},
reexport::cosmian_kms_client_utils::create_utils::SymmetricAlgorithm,
},
};
use cosmian_logger::log_init;
use test_kms_server::start_default_test_kms_server;
use super::super::utils::extract_uids::extract_uid;
use crate::{
config::CKMS_CONF_ENV,
error::{CosmianError, result::CosmianResult},
tests::{
PROG_NAME, save_kms_cli_config, secret_data::create_secret::create_secret_data,
utils::recover_cmd_logs,
},
};
const SUB_COMMAND: &str = "derive-key";
pub(crate) fn derive_key(cli_conf_path: &str, action: DeriveKeyAction) -> CosmianResult<String> {
let mut cmd = Command::cargo_bin(PROG_NAME)?;
cmd.env(CKMS_CONF_ENV, cli_conf_path);
let mut args: Vec<String> = vec![
"--algorithm".to_owned(),
action
.algorithm
.to_possible_value()
.expect("possible value")
.get_name()
.to_string(),
"--length".to_owned(),
action.cryptographic_length.to_string(),
"--derivation-method".to_owned(),
action.derivation_method.clone(),
"--salt".to_owned(),
action.salt,
"--iteration-count".to_owned(),
action.iteration_count.to_string(),
"--digest-algorithm".to_owned(),
action.digest_algorithm.to_string(),
];
if let Some(k) = action.key_id {
args.extend(vec!["--key-id".to_owned(), k]);
}
if let Some(pw) = action.password {
args.extend(vec!["--password".to_owned(), pw]);
}
if let Some(iv) = action.initialization_vector {
args.extend(vec!["--initialization-vector".to_owned(), iv]);
}
if let Some(derived_id) = action.derived_key_id {
args.extend(vec!["--derived-key-id".to_owned(), derived_id]);
}
cmd.arg(SUB_COMMAND).args(args);
let output = recover_cmd_logs(&mut cmd);
if output.status.success() {
let stdout = std::str::from_utf8(&output.stdout)?;
if let Some(pos) = stdout.find("Derived key ID:") {
let sliced = &stdout[pos..];
if let Some(uid) = extract_uid(sliced, "Derived key ID") {
return Ok(uid.to_string());
}
}
return Err(CosmianError::Default(
"failed extracting the unique identifier".to_owned(),
));
}
Err(CosmianError::Default(
std::str::from_utf8(&output.stderr)?.to_owned(),
))
}
pub(crate) async fn create_derivable_symmetric_key_with_client(
kms_client: &KmsClient,
tags: Vec<String>,
_key_id: Option<String>,
) -> CosmianResult<String> {
let mut attributes = Attributes {
cryptographic_algorithm: Some(CryptographicAlgorithm::AES),
cryptographic_length: Some(256),
cryptographic_usage_mask: Some(
CryptographicUsageMask::Encrypt
| CryptographicUsageMask::Decrypt
| CryptographicUsageMask::DeriveKey,
),
key_format_type: Some(KeyFormatType::TransparentSymmetricKey),
object_type: Some(ObjectType::SymmetricKey),
..Attributes::default()
};
if !tags.is_empty() {
attributes
.set_tags(VENDOR_ID_COSMIAN, tags)
.map_err(|e| CosmianError::Default(format!("Failed to set tags: {e}")))?;
}
let request = Create {
object_type: ObjectType::SymmetricKey,
attributes,
protection_storage_masks: None,
};
let response = kms_client
.create(request)
.await
.map_err(|e| CosmianError::Default(format!("Failed to create symmetric key: {e}")))?;
Ok(response.unique_identifier.to_string())
}
#[tokio::test]
pub(crate) async fn test_derive_symmetric_key_pbkdf2() -> CosmianResult<()> {
log_init(None);
let ctx = start_default_test_kms_server().await;
let kms_client = ctx.get_owner_client();
let (owner_client_conf_path, _) = save_kms_cli_config(ctx);
let base_key_id = create_derivable_symmetric_key_with_client(
&kms_client,
vec!["test-derive-base".to_owned()],
Some("test-base-symmetric-key".to_owned()),
)
.await?;
let derived_key_id = derive_key(
&owner_client_conf_path,
DeriveKeyAction {
key_id: Some(base_key_id),
password: None,
derivation_method: "PBKDF2".to_owned(),
salt: "0123456789abcdef".to_owned(),
iteration_count: 4096,
initialization_vector: None,
digest_algorithm: CHashingAlgorithm::SHA256,
algorithm: SymmetricAlgorithm::default(),
cryptographic_length: 256,
derived_key_id: Some("test-derived-symmetric-pbkdf2".to_owned()),
},
)?;
assert!(!derived_key_id.is_empty());
assert!(derived_key_id.starts_with("derived-"));
Ok(())
}
#[tokio::test]
pub(crate) async fn test_derive_symmetric_key_hkdf() -> CosmianResult<()> {
log_init(None);
let ctx = start_default_test_kms_server().await;
let kms_client = ctx.get_owner_client();
let (owner_client_conf_path, _) = save_kms_cli_config(ctx);
let base_key_id = create_derivable_symmetric_key_with_client(
&kms_client,
vec!["test-derive-base".to_owned()],
Some("test-base-symmetric-key-hkdf".to_owned()),
)
.await?;
let derived_key_id = derive_key(
&owner_client_conf_path,
DeriveKeyAction {
key_id: Some(base_key_id),
password: None,
derivation_method: "HKDF".to_owned(),
salt: "fedcba9876543210".to_owned(),
iteration_count: 4096,
initialization_vector: Some("1122334455667788".to_owned()),
digest_algorithm: CHashingAlgorithm::SHA256,
algorithm: SymmetricAlgorithm::default(),
cryptographic_length: 512,
derived_key_id: Some("test-derived-symmetric-hkdf".to_owned()),
},
)?;
assert!(!derived_key_id.is_empty());
assert!(derived_key_id.starts_with("derived-"));
Ok(())
}
#[tokio::test]
pub(crate) async fn test_derive_symmetric_key_different_lengths() -> CosmianResult<()> {
log_init(None);
let ctx = start_default_test_kms_server().await;
let kms_client = ctx.get_owner_client();
let (owner_client_conf_path, _) = save_kms_cli_config(ctx);
let base_key_id = create_derivable_symmetric_key_with_client(
&kms_client,
vec!["test-derive-base".to_owned()],
Some("test-base-symmetric-key-lengths".to_owned()),
)
.await?;
let lengths = vec![128, 192, 256, 512];
for length in lengths {
let derived_key_id = derive_key(
&owner_client_conf_path,
DeriveKeyAction {
key_id: Some(base_key_id.clone()),
password: None,
derivation_method: "PBKDF2".to_owned(),
salt: "0123456789abcdef".to_owned(),
iteration_count: 4096,
initialization_vector: None,
digest_algorithm: CHashingAlgorithm::SHA256,
algorithm: SymmetricAlgorithm::default(),
cryptographic_length: length,
derived_key_id: Some(format!("test-derived-symmetric-{length}-bits")),
},
)?;
assert!(!derived_key_id.is_empty());
assert!(derived_key_id.starts_with("derived-"));
}
Ok(())
}
#[tokio::test]
pub(crate) async fn test_derive_from_secret_data() -> CosmianResult<()> {
log_init(None);
let ctx = start_default_test_kms_server().await;
let _kms_client = ctx.get_owner_client();
let (owner_client_conf_path, _) = save_kms_cli_config(ctx);
let secret_data_id = create_secret_data(
&owner_client_conf_path,
&CreateSecretDataAction {
tags: vec!["test-secret".to_owned()],
..Default::default()
},
)?;
let derived_key_id = derive_key(
&owner_client_conf_path,
DeriveKeyAction {
key_id: Some(secret_data_id),
password: None,
derivation_method: "PBKDF2".to_owned(),
salt: "0123456789abcdef".to_owned(),
iteration_count: 4096,
initialization_vector: None,
digest_algorithm: CHashingAlgorithm::SHA256,
algorithm: SymmetricAlgorithm::default(),
cryptographic_length: 256,
derived_key_id: Some("test-derived-from-secret".to_owned()),
},
)?;
assert!(!derived_key_id.is_empty());
assert!(derived_key_id.starts_with("derived-"));
Ok(())
}
#[tokio::test]
pub(crate) async fn test_derive_key_different_algorithms() -> CosmianResult<()> {
log_init(None);
let ctx = start_default_test_kms_server().await;
let kms_client = ctx.get_owner_client();
let (owner_client_conf_path, _) = save_kms_cli_config(ctx);
let base_key_id = create_derivable_symmetric_key_with_client(
&kms_client,
vec!["test-derive-base".to_owned()],
Some("test-base-symmetric-key-algorithms".to_owned()),
)
.await?;
let algorithms = vec![
("PBKDF2", CHashingAlgorithm::SHA256),
("HKDF", CHashingAlgorithm::SHA256),
("PBKDF2", CHashingAlgorithm::SHA512),
];
for (method, digest) in algorithms {
let derived_key_id = derive_key(
&owner_client_conf_path,
DeriveKeyAction {
key_id: Some(base_key_id.clone()),
password: None,
derivation_method: method.to_owned(),
salt: "0123456789abcdef".to_owned(),
iteration_count: 4096,
initialization_vector: None,
digest_algorithm: digest.clone(),
algorithm: SymmetricAlgorithm::default(),
cryptographic_length: 256,
derived_key_id: Some(format!("test-derived-{method}-{digest:?}")),
},
)?;
assert!(!derived_key_id.is_empty());
assert!(derived_key_id.starts_with("derived-"));
}
Ok(())
}
#[tokio::test]
pub(crate) async fn test_derive_key_from_password() -> CosmianResult<()> {
log_init(None);
let ctx = start_default_test_kms_server().await;
let _kms_client = ctx.get_owner_client();
let (owner_client_conf_path, _) = save_kms_cli_config(ctx);
let derived_key_id = derive_key(
&owner_client_conf_path,
DeriveKeyAction {
key_id: None,
password: Some("my-secure-password-123".to_owned()),
derivation_method: "PBKDF2".to_owned(),
salt: "0123456789abcdef".to_owned(),
iteration_count: 4096,
initialization_vector: None,
digest_algorithm: CHashingAlgorithm::SHA256,
algorithm: SymmetricAlgorithm::default(),
cryptographic_length: 256,
derived_key_id: Some("test-derived-from-password".to_owned()),
},
)?;
assert!(!derived_key_id.is_empty());
assert!(derived_key_id.starts_with("derived-"));
Ok(())
}
#[tokio::test]
pub(crate) async fn test_derive_key_from_unicode_password() -> CosmianResult<()> {
log_init(None);
let ctx = start_default_test_kms_server().await;
let _kms_client = ctx.get_owner_client();
let (owner_client_conf_path, _) = save_kms_cli_config(ctx);
let derived_key_id = derive_key(
&owner_client_conf_path,
DeriveKeyAction {
key_id: None,
password: Some("мой-пароль-🔐-密码-123".to_owned()), derivation_method: "HKDF".to_owned(),
salt: "fedcba9876543210".to_owned(),
iteration_count: 4096,
initialization_vector: Some("1122334455667788".to_owned()),
digest_algorithm: CHashingAlgorithm::SHA512,
algorithm: SymmetricAlgorithm::default(),
cryptographic_length: 384,
derived_key_id: Some("test-derived-from-unicode-password".to_owned()),
},
)?;
assert!(!derived_key_id.is_empty());
assert!(derived_key_id.starts_with("derived-"));
Ok(())
}