use std::collections::BTreeMap;
use chrono::Utc;
use fakecloud_aws::arn::Arn;
use uuid::Uuid;
use super::asym;
use super::asym_ecdsa;
use super::helpers::{
default_key_policy, encryption_algorithms_for_key, mac_algorithms_for_key_spec, rand_bytes,
signing_algorithms_for_key_spec,
};
use crate::state::{KmsAlias, KmsKey, SharedKmsState};
#[derive(Debug, Clone)]
pub struct KeyCreationInput {
pub description: String,
pub key_usage: String,
pub key_spec: String,
pub origin: String,
pub enabled: bool,
pub multi_region: bool,
pub key_rotation_enabled: bool,
pub policy: Option<String>,
pub tags: BTreeMap<String, String>,
}
impl Default for KeyCreationInput {
fn default() -> Self {
Self {
description: String::new(),
key_usage: "ENCRYPT_DECRYPT".to_string(),
key_spec: "SYMMETRIC_DEFAULT".to_string(),
origin: "AWS_KMS".to_string(),
enabled: true,
multi_region: false,
key_rotation_enabled: false,
policy: None,
tags: BTreeMap::new(),
}
}
}
pub const VALID_KEY_SPECS: &[&str] = &[
"SYMMETRIC_DEFAULT",
"RSA_2048",
"RSA_3072",
"RSA_4096",
"ECC_NIST_P256",
"ECC_NIST_P384",
"ECC_NIST_P521",
"ECC_SECG_P256K1",
"HMAC_224",
"HMAC_256",
"HMAC_384",
"HMAC_512",
"SM2",
];
pub const VALID_KEY_USAGES: &[&str] = &[
"ENCRYPT_DECRYPT",
"SIGN_VERIFY",
"GENERATE_VERIFY_MAC",
"KEY_AGREEMENT",
];
pub fn validate_key_usage_for_spec(key_usage: &str, key_spec: &str) -> Result<(), String> {
if !VALID_KEY_USAGES.contains(&key_usage) {
return Err(format!("Unsupported KeyUsage: {key_usage}"));
}
if !VALID_KEY_SPECS.contains(&key_spec) {
return Err(format!("Unsupported KeySpec: {key_spec}"));
}
let is_symmetric = key_spec == "SYMMETRIC_DEFAULT";
let is_hmac = key_spec.starts_with("HMAC_");
let is_rsa = key_spec.starts_with("RSA_");
let is_ecc = key_spec.starts_with("ECC_");
let is_sm2 = key_spec == "SM2";
match key_usage {
"ENCRYPT_DECRYPT" => {
if !(is_symmetric || is_rsa || is_sm2) {
return Err(format!(
"KeySpec {key_spec} does not support KeyUsage ENCRYPT_DECRYPT"
));
}
}
"SIGN_VERIFY" => {
if !(is_rsa || is_ecc || is_sm2) {
return Err(format!(
"KeySpec {key_spec} does not support KeyUsage SIGN_VERIFY"
));
}
}
"GENERATE_VERIFY_MAC" => {
if !is_hmac {
return Err(format!(
"KeySpec {key_spec} does not support KeyUsage GENERATE_VERIFY_MAC"
));
}
}
"KEY_AGREEMENT" => {
if !is_ecc {
return Err(format!(
"KeySpec {key_spec} does not support KeyUsage KEY_AGREEMENT"
));
}
}
_ => unreachable!(),
}
Ok(())
}
pub fn build_kms_key(
region: &str,
account_id: &str,
input: &KeyCreationInput,
) -> Result<KmsKey, String> {
validate_key_usage_for_spec(&input.key_usage, &input.key_spec)?;
let key_id = if input.multi_region {
format!("mrk-{}", Uuid::new_v4().as_simple())
} else {
Uuid::new_v4().to_string()
};
let arn = Arn::new("kms", region, account_id, &format!("key/{key_id}")).to_string();
let now = Utc::now().timestamp() as f64;
let signing_algs = if input.key_usage == "SIGN_VERIFY" {
signing_algorithms_for_key_spec(&input.key_spec)
} else {
None
};
let encryption_algs = encryption_algorithms_for_key(&input.key_usage, &input.key_spec);
let mac_algs = if input.key_usage == "GENERATE_VERIFY_MAC" {
mac_algorithms_for_key_spec(&input.key_spec)
} else {
None
};
let mut asym_priv: Option<Vec<u8>> = None;
let mut asym_pub: Option<Vec<u8>> = None;
if let Some((p, k)) =
asym::generate_keypair(&input.key_spec).map_err(|e| format!("rsa keygen failed: {e}"))?
{
asym_priv = Some(p);
asym_pub = Some(k);
} else if let Some((p, k)) = asym_ecdsa::generate_keypair(&input.key_spec)
.map_err(|e| format!("ecdsa keygen failed: {e}"))?
{
asym_priv = Some(p);
asym_pub = Some(k);
}
let policy = input
.policy
.clone()
.unwrap_or_else(|| default_key_policy(account_id));
Ok(KmsKey {
key_id,
arn,
creation_date: now,
description: input.description.clone(),
enabled: input.enabled,
key_usage: input.key_usage.clone(),
key_spec: input.key_spec.clone(),
key_manager: "CUSTOMER".to_string(),
key_state: if input.enabled { "Enabled" } else { "Disabled" }.to_string(),
deletion_date: None,
tags: input.tags.clone(),
policy,
key_rotation_enabled: input.key_rotation_enabled,
origin: input.origin.clone(),
multi_region: input.multi_region,
rotations: Vec::new(),
signing_algorithms: signing_algs,
encryption_algorithms: encryption_algs,
mac_algorithms: mac_algs,
custom_key_store_id: None,
imported_key_material: false,
imported_material_bytes: None,
private_key_seed: rand_bytes(32),
primary_region: None,
asymmetric_private_key_der: asym_priv,
asymmetric_public_key_der: asym_pub,
})
}
pub fn provision_key(
state: &SharedKmsState,
account_id: &str,
input: &KeyCreationInput,
) -> Result<(String, String), String> {
let mut accounts = state.write();
let s = accounts.get_or_create(account_id);
let region = s.region.clone();
let key = build_kms_key(®ion, account_id, input)?;
let key_id = key.key_id.clone();
let arn = key.arn.clone();
s.keys.insert(key_id.clone(), key);
Ok((key_id, arn))
}
pub fn provision_replica_key(
state: &SharedKmsState,
account_id: &str,
primary_arn: &str,
description: Option<String>,
enabled: bool,
policy: Option<String>,
tags: BTreeMap<String, String>,
) -> Result<(String, String), String> {
let parts: Vec<&str> = primary_arn.split(':').collect();
if parts.len() < 6 {
return Err(format!("Invalid PrimaryKeyArn: {primary_arn}"));
}
let primary_region = parts[3].to_string();
let key_id = parts[5]
.strip_prefix("key/")
.ok_or_else(|| format!("PrimaryKeyArn missing key/ segment: {primary_arn}"))?
.to_string();
let mut accounts = state.write();
let s = accounts.get_or_create(account_id);
let region = s.region.clone();
let source_storage_keys = [key_id.clone(), format!("{primary_region}:{key_id}")];
let source = source_storage_keys
.iter()
.find_map(|k| s.keys.get(k).cloned())
.ok_or_else(|| format!("Primary key {primary_arn} does not exist"))?;
if !source.multi_region {
return Err(format!(
"Primary key {primary_arn} is not a multi-region key"
));
}
let replica_key_id = format!("mrk-replica-{}", Uuid::new_v4().as_simple());
let replica_arn =
Arn::new("kms", ®ion, account_id, &format!("key/{replica_key_id}")).to_string();
let mut replica = source;
replica.key_id = replica_key_id.clone();
replica.arn = replica_arn.clone();
if let Some(d) = description {
if !d.is_empty() {
replica.description = d;
}
}
replica.enabled = enabled;
replica.key_state = if enabled { "Enabled" } else { "Disabled" }.to_string();
if let Some(p) = policy {
if !p.is_empty() {
replica.policy = p;
}
}
if !tags.is_empty() {
replica.tags.extend(tags);
}
replica.deletion_date = None;
replica.key_rotation_enabled = false;
replica.multi_region = true;
replica.rotations = Vec::new();
replica.custom_key_store_id = None;
replica.imported_key_material = false;
replica.imported_material_bytes = None;
replica.primary_region = Some(primary_region);
s.keys.insert(replica_key_id.clone(), replica);
Ok((replica_key_id, replica_arn))
}
pub fn provision_alias(
state: &SharedKmsState,
account_id: &str,
alias_name: &str,
target_input: &str,
) -> Result<String, String> {
if !alias_name.starts_with("alias/") {
return Err(format!(
"AliasName must start with 'alias/'; got '{alias_name}'"
));
}
let mut accounts = state.write();
let s = accounts.get_or_create(account_id);
let target_key_id = if s.keys.contains_key(target_input) {
target_input.to_string()
} else if let Some(id) = target_input
.strip_prefix("arn:aws:kms:")
.and_then(|rest| rest.split(":key/").nth(1))
{
if s.keys.contains_key(id) {
id.to_string()
} else {
return Err(format!("KMS key '{target_input}' does not exist"));
}
} else {
return Err(format!("KMS key '{target_input}' does not exist"));
};
let alias_arn = Arn::new("kms", &s.region, &s.account_id, alias_name).to_string();
let alias = KmsAlias {
alias_name: alias_name.to_string(),
alias_arn,
target_key_id,
creation_date: Utc::now().timestamp() as f64,
};
s.aliases.insert(alias_name.to_string(), alias);
Ok(alias_name.to_string())
}
#[derive(Debug, Default, Clone)]
pub struct KeyUpdate {
pub description: Option<String>,
pub enabled: Option<bool>,
pub key_rotation_enabled: Option<bool>,
pub policy: Option<String>,
pub tags: Option<BTreeMap<String, String>>,
}
pub fn update_key_properties(
state: &SharedKmsState,
account_id: &str,
key_id: &str,
update: KeyUpdate,
) -> Result<(), String> {
let mut accounts = state.write();
let s = accounts.get_or_create(account_id);
let key = s
.keys
.get_mut(key_id)
.ok_or_else(|| format!("Key '{key_id}' does not exist"))?;
if let Some(d) = update.description {
key.description = d;
}
if let Some(e) = update.enabled {
key.enabled = e;
key.key_state = if e { "Enabled" } else { "Disabled" }.to_string();
}
if let Some(r) = update.key_rotation_enabled {
key.key_rotation_enabled = r;
}
if let Some(p) = update.policy {
if !p.is_empty() {
key.policy = p;
}
}
if let Some(t) = update.tags {
key.tags = t;
}
Ok(())
}
pub fn update_alias_target(
state: &SharedKmsState,
account_id: &str,
alias_name: &str,
target_input: &str,
) -> Result<(), String> {
let mut accounts = state.write();
let s = accounts.get_or_create(account_id);
let target_key_id = if s.keys.contains_key(target_input) {
target_input.to_string()
} else if let Some(id) = target_input
.strip_prefix("arn:aws:kms:")
.and_then(|rest| rest.split(":key/").nth(1))
{
if s.keys.contains_key(id) {
id.to_string()
} else {
return Err(format!("KMS key '{target_input}' does not exist"));
}
} else {
return Err(format!("KMS key '{target_input}' does not exist"));
};
let alias = s
.aliases
.get_mut(alias_name)
.ok_or_else(|| format!("Alias '{alias_name}' does not exist"))?;
alias.target_key_id = target_key_id;
Ok(())
}