diff options
-rw-r--r-- | src/bin/roughenough-kms.rs (renamed from src/bin/kms.rs) | 41 | ||||
-rw-r--r-- | src/key/awskms.rs | 29 | ||||
-rw-r--r-- | src/key/envelope.rs | 115 | ||||
-rw-r--r-- | src/key/mod.rs | 29 | ||||
-rw-r--r-- | src/lib.rs | 1 |
5 files changed, 179 insertions, 36 deletions
diff --git a/src/bin/kms.rs b/src/bin/roughenough-kms.rs index 3661e48..671aa75 100644 --- a/src/bin/kms.rs +++ b/src/bin/roughenough-kms.rs @@ -20,19 +20,41 @@ extern crate clap; #[macro_use] extern crate log; +extern crate hex; extern crate ring; extern crate roughenough; extern crate simple_logger; extern crate untrusted; -#[cfg(feature = "kms")] -use roughenough::key::awskms::AwsKms; - use std::default::Default; use clap::{App, Arg}; +use roughenough::key::{EnvelopeEncryption, KmsProvider}; use roughenough::VERSION; +#[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(); + + let plaintext_seed = [b'a'; 32]; + match EnvelopeEncryption::encrypt_seed(&client, &plaintext_seed) { + Ok(bundle) => { + info!("Bundle len={}", bundle.len()); + info!("{}", hex::encode(&bundle)); + + EnvelopeEncryption::decrypt_seed(&client, &bundle); + } + Err(e) => { + error!("Error: {:?}", e); + } + } +} + pub fn main() { use log::Level; @@ -49,15 +71,10 @@ pub fn main() { if cfg!(feature = "kms") { info!("KMS feature enabled"); - let client = AwsKms::from_arn( - "arn:aws:kms:us-east-2:927891522318:key/1c96fb2c-d417-48f4-bf24-8e7173a587f5", - ).unwrap(); - - let ciphertext = client.encrypt("This is a test".as_ref()).unwrap(); - info!("Ciphertext: {:?}", ciphertext); - - let plaintext = String::from_utf8(client.decrypt(ciphertext.as_ref()).unwrap()).unwrap(); - info!("Plaintext : {:?}", plaintext); + #[cfg(feature = "kms")] + { + aws_kms(); + } } info!("Done"); diff --git a/src/key/awskms.rs b/src/key/awskms.rs index bb8d215..234864b 100644 --- a/src/key/awskms.rs +++ b/src/key/awskms.rs @@ -46,9 +46,10 @@ impl AwsKms { let parts: Vec<&str> = arn.split(':').collect(); if parts.len() != 6 { - return Err(KmsError::InvalidConfiguration( - format!("invalid KMS arn: too few parts {}", parts.len()) - )); + return Err(KmsError::InvalidConfiguration(format!( + "invalid KMS arn: too few parts {}", + parts.len() + ))); } let region_part = parts.get(3).expect("region is missing"); @@ -67,9 +68,10 @@ impl AwsKms { impl KmsProvider for AwsKms { fn encrypt_dek(&self, plaintext_dek: &PlaintextDEK) -> Result<EncryptedDEK, KmsError> { if plaintext_dek.len() != DEK_SIZE_BYTES { - return Err(KmsError::InvalidKey( - format!("provided DEK wrong length: {}", plaintext_dek.len()), - )); + return Err(KmsError::InvalidKey(format!( + "provided DEK wrong length: {}", + plaintext_dek.len() + ))); } let mut encrypt_req: EncryptRequest = Default::default(); @@ -81,12 +83,12 @@ impl KmsProvider for AwsKms { if let Some(ciphertext) = result.ciphertext_blob { Ok(ciphertext) } else { - Err(KmsError::EncryptionFailed( + Err(KmsError::OperationFailed( "no ciphertext despite successful response".to_string(), )) } } - Err(e) => Err(KmsError::EncryptionFailed(e.description().to_string())), + Err(e) => Err(KmsError::OperationFailed(e.description().to_string())), } } @@ -100,17 +102,18 @@ impl KmsProvider for AwsKms { if plaintext_dek.len() == DEK_SIZE_BYTES { Ok(plaintext_dek) } else { - Err(KmsError::InvalidKey( - format!("decrypted DEK wrong length: {}", plaintext_dek.len()) - )) + Err(KmsError::InvalidKey(format!( + "decrypted DEK wrong length: {}", + plaintext_dek.len() + ))) } } else { - Err(KmsError::DecryptionFailed( + Err(KmsError::OperationFailed( "decrypted payload is empty".to_string(), )) } } - Err(e) => Err(KmsError::DecryptionFailed(e.description().to_string())), + Err(e) => Err(KmsError::OperationFailed(e.description().to_string())), } } } diff --git a/src/key/envelope.rs b/src/key/envelope.rs index 5c869c5..1bcc077 100644 --- a/src/key/envelope.rs +++ b/src/key/envelope.rs @@ -14,22 +14,123 @@ extern crate hex; -use ring::aead::AES_256_GCM; +use std::io::{Cursor, Read, Write}; + +use ring::aead::{open_in_place, seal_in_place, OpeningKey, SealingKey, AES_256_GCM}; use ring::rand; use ring::rand::SecureRandom; -use key::KmsProvider; +use super::super::MIN_SEED_LENGTH; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use key::{KmsError, KmsProvider, DEK_SIZE_BYTES, NONCE_SIZE_BYTES, TAG_SIZE_BYTES}; -/// /// 2 bytes - encrypted DEK length +/// 2 bytes - nonce length /// n bytes - encrypted DEK -/// 2 bytes - encrypted +/// n bytes - nonce +/// n bytes - opaque (encrypted seed + tag) pub struct EnvelopeEncryption; +static AD: &[u8; 11] = b"roughenough"; + +const DEK_LEN_FIELD: usize = 2; +const NONCE_LEN_FIELD: usize = 2; + +fn zero_filled(len: usize) -> Vec<u8> { + let mut v = Vec::with_capacity(len); + for _ in 0..len { + v.push(0); + } + return v; +} + impl EnvelopeEncryption { - pub fn encrypt(kms: &KmsProvider, plaintext: &[u8]) -> Vec<u8> { + pub fn decrypt_seed(kms: &KmsProvider, ciphertext_blob: &[u8]) -> Result<Vec<u8>, KmsError> { + let min_size = DEK_LEN_FIELD + + NONCE_LEN_FIELD + + DEK_SIZE_BYTES + + NONCE_SIZE_BYTES + + TAG_SIZE_BYTES + + MIN_SEED_LENGTH as usize; + + if ciphertext_blob.len() < min_size { + return Err(KmsError::InvalidData( + format!("ciphertext too short: min {}, found {}", min_size, ciphertext_blob.len()) + )); + } + /// 2 bytes - encrypted DEK length + /// 2 bytes - nonce length + /// n bytes - encrypted DEK + /// n bytes - nonce + /// n bytes - encrypted seed + + let mut tmp = Cursor::new(ciphertext_blob.clone()); + let dek_len = tmp.read_u16::<LittleEndian>()?; + let nonce_len = tmp.read_u16::<LittleEndian>()?; + + let mut encrypted_dek = zero_filled(usize::from(dek_len)); + tmp.read_exact(&mut encrypted_dek)?; + + let mut nonce = zero_filled(usize::from(nonce_len)); + tmp.read_exact(&mut nonce)?; + + let mut encrypted_blob = zero_filled(ciphertext_blob.len() - tmp.position() as usize); + tmp.read_to_end(&mut encrypted_blob)?; + + 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_blob)); + + Ok(Vec::new()) + } + + pub fn encrypt_seed(kms: &KmsProvider, plaintext_seed: &[u8]) -> Result<Vec<u8>, KmsError> { + // Generate random DEK and nonce let rng = rand::SystemRandom::new(); - let mut dek = [0u8; 16]; - rng.fill(&mut dek).unwrap(); + let mut dek = [0u8; DEK_SIZE_BYTES]; + let mut nonce = [0u8; NONCE_SIZE_BYTES]; + rng.fill(&mut dek)?; + rng.fill(&mut nonce)?; + + // ring will overwrite plaintext with ciphertext in this buffer + let mut plaintext_buf = plaintext_seed.to_vec(); + + // reserve space for the authentication tag which will be appended after the ciphertext + plaintext_buf.reserve(TAG_SIZE_BYTES); + for _ in 0..TAG_SIZE_BYTES { + plaintext_buf.push(0); + } + + // Encrypt the plaintext seed + let dek_seal_key = SealingKey::new(&AES_256_GCM, &dek)?; + let encrypted_seed = + match seal_in_place(&dek_seal_key, &nonce, AD, &mut plaintext_buf, TAG_SIZE_BYTES) { + Ok(enc_len) => plaintext_buf[..enc_len].to_vec(), + Err(e) => { + return Err(KmsError::OperationFailed( + "failed to encrypt plaintext seed".to_string(), + )) + } + }; + + // Wrap the DEK + let wrapped_dek = kms.encrypt_dek(&dek.to_vec())?; + + // And coalesce everything together + let mut output = Vec::new(); + output.write_u16::<LittleEndian>(wrapped_dek.len() as u16)?; + output.write_u16::<LittleEndian>(nonce.len() as u16)?; + output.write_all(&wrapped_dek)?; + output.write_all(&nonce)?; + output.write_all(&encrypted_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 b269af2..62c37a9 100644 --- a/src/key/mod.rs +++ b/src/key/mod.rs @@ -18,11 +18,16 @@ extern crate hex; extern crate log; +extern crate ring; +extern crate std; mod envelope; mod longterm; mod online; +use std::error::Error; + +pub use self::envelope::EnvelopeEncryption; pub use self::longterm::LongTermKey; pub use self::online::OnlineKey; @@ -41,17 +46,35 @@ pub enum KeyProtection { GoogleKmsEnvelope, } -#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Clone)] pub enum KmsError { - DecryptionFailed(String), - EncryptionFailed(String), + OperationFailed(String), InvalidConfiguration(String), + InvalidData(String), InvalidKey(String), } +impl From<std::io::Error> for KmsError { + fn from(error: std::io::Error) -> Self { + KmsError::OperationFailed(error.description().to_string()) + } +} + +impl From<ring::error::Unspecified> for KmsError { + fn from(error: ring::error::Unspecified) -> Self { + KmsError::OperationFailed("unspecified ring cryptographic failure".to_string()) + } +} + /// Size of the Data Encryption Key (DEK) in bytes pub const DEK_SIZE_BYTES: usize = 32; +/// Size of the AEAD nonce in bytes +pub const NONCE_SIZE_BYTES: usize = 12; + +/// Size of the AEAD authentication tag in bytes +pub const TAG_SIZE_BYTES: usize = 16; + /// An unencrypted (plaintext) 256-bit Data Encryption Key (DEK). type PlaintextDEK = Vec<u8>; @@ -49,7 +49,6 @@ //! extern crate byteorder; -extern crate clear_on_drop; extern crate core; extern crate time; extern crate yaml_rust; |