summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bin/roughenough-kms.rs (renamed from src/bin/kms.rs)41
-rw-r--r--src/key/awskms.rs29
-rw-r--r--src/key/envelope.rs115
-rw-r--r--src/key/mod.rs29
-rw-r--r--src/lib.rs1
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>;
diff --git a/src/lib.rs b/src/lib.rs
index 4bfbd81..0903f95 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -49,7 +49,6 @@
//!
extern crate byteorder;
-extern crate clear_on_drop;
extern crate core;
extern crate time;
extern crate yaml_rust;