diff --git a/src/bin/roughenough-kms.rs b/src/bin/roughenough-kms.rs index bc749bb..b660488 100644 --- a/src/bin/roughenough-kms.rs +++ b/src/bin/roughenough-kms.rs @@ -16,7 +16,6 @@ //! Work with Roughenough long-term key //! -#[macro_use] extern crate clap; #[macro_use] extern crate log; @@ -26,31 +25,21 @@ extern crate roughenough; extern crate simple_logger; extern crate untrusted; -use std::default::Default; - use clap::{App, Arg}; -use roughenough::key::{EnvelopeEncryption, KmsProvider}; use roughenough::VERSION; +use roughenough::key::EnvelopeEncryption; #[cfg(feature = "kms")] use roughenough::key::awskms::AwsKms; #[cfg(feature = "kms")] -fn aws_kms() { - let client = AwsKms::from_arn( - "arn:aws:kms:us-east-2:927891522318:key/1c96fb2c-d417-48f4-bf24-8e7173a587f5", - ).unwrap(); +fn aws_kms(kms_key: &str, plaintext_seed: &[u8]) { + let client = AwsKms::from_arn(kms_key).unwrap(); - let plaintext_seed = [b'a'; 32]; match EnvelopeEncryption::encrypt_seed(&client, &plaintext_seed) { - Ok(bundle) => { - info!("Bundle len={}", bundle.len()); - info!("{}", hex::encode(&bundle)); - - match EnvelopeEncryption::decrypt_seed(&client, &bundle) { - Ok(plaintext) => info!("Result is {}", hex::encode(plaintext)), - Err(e) => error!("Nope, {:?}", e), - }; + Ok(encrypted_blob) => { + println!("key_protection: \"{}\"", kms_key); + println!("seed: {}", hex::encode(&encrypted_blob)); } Err(e) => { error!("Error: {:?}", e); @@ -65,22 +54,34 @@ pub fn main() { let matches = App::new("Roughenough key management") .version(VERSION) - .arg( - Arg::with_name("operation") - .required(true) - .help("The operation to perform") - .takes_value(true), - ).get_matches(); + .arg(Arg::with_name("kms-key") + .short("k") + .long("kms-key") + .takes_value(true) + .required(true) + .help("Identity of the KMS key to be used")) + .arg(Arg::with_name("seed") + .short("s") + .long("seed") + .takes_value(true) + .required(true) + .help("Seed for the server's long-term identity")) + .get_matches(); + + let kms_key = matches.value_of("kms-key").unwrap(); + let plaintext_seed = matches.value_of("seed") + .map(|seed| hex::decode(seed).expect("Error parsing seed value")) + .unwrap(); + + if plaintext_seed.len() != 32 { + error!("Seed must be 32 bytes long; provided seed is {}", plaintext_seed.len()); + return; + } if cfg!(feature = "kms") { - info!("KMS feature enabled"); #[cfg(feature = "kms")] - { - aws_kms(); - } + aws_kms(kms_key, &plaintext_seed); } else { warn!("KMS not enabled, nothing to do"); } - - info!("Done"); } diff --git a/src/config/environment.rs b/src/config/environment.rs index 8f91f0c..a4cb528 100644 --- a/src/config/environment.rs +++ b/src/config/environment.rs @@ -74,12 +74,8 @@ impl EnvironmentConfig { }; if let Ok(seed) = env::var(ROUGHENOUGH_SEED) { - cfg.seed = hex::decode(&seed).expect( - format!( - "invalid seed value: {}\n'seed' should be 32 byte hex value", - seed - ).as_ref(), - ); + cfg.seed = hex::decode(&seed) + .expect("invalid seed value; 'seed' should be a hex value"); }; if let Ok(batch_size) = env::var(ROUGHENOUGH_BATCH_SIZE) { @@ -96,6 +92,12 @@ impl EnvironmentConfig { cfg.status_interval = Duration::from_secs(val as u64); }; + if let Ok(key_protection) = env::var(ROUGHENOUGH_KEY_PROTECTION) { + cfg.key_protection = key_protection + .parse() + .expect(format!("invalid key_protection value: {}", key_protection).as_ref()); + } + Ok(cfg) } } diff --git a/src/key/envelope.rs b/src/key/envelope.rs index bb3560f..5128b76 100644 --- a/src/key/envelope.rs +++ b/src/key/envelope.rs @@ -39,11 +39,14 @@ const MIN_PAYLOAD_SIZE: usize = DEK_LEN_FIELD + MIN_SEED_LENGTH as usize + TAG_SIZE_BYTES; -// Domain separation in case KMS key is reused +// No input prefix to skip, consume entire buffer +const IN_PREFIX_LEN: usize = 0; + +// Trivial domain separation to guard KMS key reuse static AD: &[u8; 11] = b"roughenough"; // Convenience function to create zero-filled Vec of given size -fn zero_filled(len: usize) -> Vec { +fn vec_zero_filled(len: usize) -> Vec { let mut v = Vec::with_capacity(len); for _ in 0..len { v.push(0); @@ -63,31 +66,30 @@ impl EnvelopeEncryption { ))); } - info!("--- decrypt ---"); - info!("blob {}", hex::encode(ciphertext_blob)); let mut tmp = Cursor::new(ciphertext_blob); + + // Read the lengths of the wrapped DEK and of the nonce let dek_len = tmp.read_u16::()?; let nonce_len = tmp.read_u16::()?; - let mut encrypted_dek = zero_filled(dek_len as usize); + // Consume the wrapped DEK + let mut encrypted_dek = vec_zero_filled(dek_len as usize); tmp.read_exact(&mut encrypted_dek)?; - let mut nonce = zero_filled(nonce_len as usize); + // Consume the nonce + let mut nonce = vec_zero_filled(nonce_len as usize); tmp.read_exact(&mut nonce)?; + // Consume the encrypted seed + tag let mut encrypted_seed = Vec::new(); tmp.read_to_end(&mut encrypted_seed)?; - info!("dek len {}", dek_len); - info!("nonce len {}", nonce_len); - info!("enc dec {}", hex::encode(&encrypted_dek)); - info!("nonce {}", hex::encode(&nonce)); - info!("blob {}", hex::encode(&encrypted_seed)); - + // Invoke KMS to decrypt the DEK let dek = kms.decrypt_dek(&encrypted_dek)?; - let dek_open_key = OpeningKey::new(&AES_256_GCM, &dek)?; - match open_in_place(&dek_open_key, &nonce, AD, 0, &mut encrypted_seed) { + // Decrypt the seed value using the DEK + let dek_open_key = OpeningKey::new(&AES_256_GCM, &dek)?; + match open_in_place(&dek_open_key, &nonce, AD, IN_PREFIX_LEN, &mut encrypted_seed) { Ok(plaintext_seed) => Ok(plaintext_seed.to_vec()), Err(e) => Err(KmsError::OperationFailed( "failed to decrypt plaintext seed".to_string(), @@ -112,7 +114,7 @@ impl EnvelopeEncryption { plaintext_buf.push(0); } - // Encrypt the plaintext seed + // Encrypt the plaintext seed using the DEK let dek_seal_key = SealingKey::new(&AES_256_GCM, &dek)?; let encrypted_seed = match seal_in_place( &dek_seal_key, @@ -129,7 +131,7 @@ impl EnvelopeEncryption { } }; - // Wrap the DEK + // Use the KMS to wrap the DEK let wrapped_dek = kms.encrypt_dek(&dek.to_vec())?; // And coalesce everything together @@ -140,13 +142,6 @@ impl EnvelopeEncryption { output.write_all(&nonce)?; output.write_all(&encrypted_seed)?; - info!("--- encrypt ---"); - info!("seed {}", hex::encode(plaintext_seed)); - info!("dek {}", hex::encode(&dek)); - info!("enc dek {}", hex::encode(&wrapped_dek)); - info!("nonce {}", hex::encode(&nonce)); - info!("blob {}", hex::encode(&encrypted_seed)); - Ok(output) } } diff --git a/src/key/mod.rs b/src/key/mod.rs index 62c37a9..7ae2198 100644 --- a/src/key/mod.rs +++ b/src/key/mod.rs @@ -26,6 +26,7 @@ mod longterm; mod online; use std::error::Error; +use std::str::FromStr; pub use self::envelope::EnvelopeEncryption; pub use self::longterm::LongTermKey; @@ -39,11 +40,24 @@ pub enum KeyProtection { /// No protection, seed is in plaintext Plaintext, - /// Envelope encryption with Key-Encrypting-Key (KEK) from AWS Key Management Service - AwsKmsEnvelope, + /// Envelope encryption using AWS Key Management Service + AwsKmsEnvelope(String), - /// Envelope encryption with Key-Encrypting-Key (KEK) from Google Cloud Key Management Service - GoogleKmsEnvelope, + /// Envelope encryption using Google Cloud Key Management Service + GoogleKmsEnvelope(String), +} + +impl FromStr for KeyProtection { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "plaintext" => Ok(KeyProtection::Plaintext), + s if s.starts_with("arn") => Ok(KeyProtection::AwsKmsEnvelope(s.to_string())), + s if s.starts_with("gcp") => Ok(KeyProtection::GoogleKmsEnvelope(s.to_string())), + _ => Err(()) + } + } } #[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Clone)]