extern crate hex;
use std::io::{Cursor, Read, Write};
use ring::aead::{open_in_place, seal_in_place, OpeningKey, SealingKey, AES_256_GCM};
use ring::rand::{SecureRandom, SystemRandom};
use super::super::MIN_SEED_LENGTH;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use kms::{KmsError, KmsProvider, AD, DEK_SIZE_BYTES, NONCE_SIZE_BYTES, TAG_SIZE_BYTES};
const DEK_LEN_FIELD: usize = 2;
const NONCE_LEN_FIELD: usize = 2;
const MIN_PAYLOAD_SIZE: usize = DEK_LEN_FIELD
+ NONCE_LEN_FIELD
+ DEK_SIZE_BYTES
+ NONCE_SIZE_BYTES
+ MIN_SEED_LENGTH as usize
+ TAG_SIZE_BYTES;
const IN_PREFIX_LEN: usize = 0;
fn vec_zero_filled(len: usize) -> Vec<u8> {
(0..len).into_iter().map(|_| 0).collect()
}
pub struct EnvelopeEncryption;
impl EnvelopeEncryption {
pub fn decrypt_seed(kms: &KmsProvider, ciphertext_blob: &[u8]) -> Result<Vec<u8>, KmsError> {
if ciphertext_blob.len() < MIN_PAYLOAD_SIZE {
return Err(KmsError::InvalidData(format!(
"ciphertext too short: min {}, found {}",
MIN_PAYLOAD_SIZE,
ciphertext_blob.len()
)));
}
let mut tmp = Cursor::new(ciphertext_blob);
let dek_len = tmp.read_u16::<LittleEndian>()? as usize;
let nonce_len = tmp.read_u16::<LittleEndian>()? as usize;
if nonce_len != NONCE_SIZE_BYTES || dek_len > ciphertext_blob.len() {
return Err(KmsError::InvalidData(format!(
"invalid DEK ({}) or nonce ({}) length",
dek_len, nonce_len
)));
}
let mut encrypted_dek = vec_zero_filled(dek_len);
tmp.read_exact(&mut encrypted_dek)?;
let mut nonce = vec_zero_filled(nonce_len);
tmp.read_exact(&mut nonce)?;
let mut encrypted_seed = Vec::new();
tmp.read_to_end(&mut encrypted_seed)?;
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.as_bytes(),
IN_PREFIX_LEN,
&mut encrypted_seed,
) {
Ok(plaintext_seed) => Ok(plaintext_seed.to_vec()),
Err(_) => Err(KmsError::OperationFailed(
"failed to decrypt plaintext seed".to_string(),
)),
}
}
pub fn encrypt_seed(kms: &KmsProvider, plaintext_seed: &[u8]) -> Result<Vec<u8>, KmsError> {
let rng = SystemRandom::new();
let mut dek = [0u8; DEK_SIZE_BYTES];
let mut nonce = [0u8; NONCE_SIZE_BYTES];
rng.fill(&mut dek)?;
rng.fill(&mut nonce)?;
let mut plaintext_buf = plaintext_seed.to_vec();
plaintext_buf.reserve(TAG_SIZE_BYTES);
for _ in 0..TAG_SIZE_BYTES {
plaintext_buf.push(0);
}
let dek_seal_key = SealingKey::new(&AES_256_GCM, &dek)?;
let encrypted_seed = match seal_in_place(
&dek_seal_key,
&nonce,
AD.as_bytes(),
&mut plaintext_buf,
TAG_SIZE_BYTES,
) {
Ok(enc_len) => plaintext_buf[..enc_len].to_vec(),
Err(_) => {
return Err(KmsError::OperationFailed(
"failed to encrypt plaintext seed".to_string(),
))
}
};
let wrapped_dek = kms.encrypt_dek(&dek.to_vec())?;
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)?;
Ok(output)
}
}
#[cfg(test)]
mod test {
use kms::envelope::{DEK_LEN_FIELD, MIN_PAYLOAD_SIZE, NONCE_LEN_FIELD};
use kms::EnvelopeEncryption;
use kms::{KmsError, KmsProvider};
struct MockKmsProvider {}
impl KmsProvider for MockKmsProvider {
fn encrypt_dek(&self, plaintext_dek: &Vec<u8>) -> Result<Vec<u8>, KmsError> {
Ok(plaintext_dek.to_vec())
}
fn decrypt_dek(&self, encrypted_dek: &Vec<u8>) -> Result<Vec<u8>, KmsError> {
Ok(encrypted_dek.to_vec())
}
}
#[test]
fn decryption_reject_input_too_short() {
let ciphertext_blob = "1234567890";
assert!(ciphertext_blob.len() < MIN_PAYLOAD_SIZE);
let kms = MockKmsProvider {};
let result = EnvelopeEncryption::decrypt_seed(&kms, ciphertext_blob.as_bytes());
match result.expect_err("expected KmsError") {
KmsError::InvalidData(msg) => assert!(msg.contains("ciphertext too short")),
e => panic!("Unexpected error {:?}", e),
}
}
#[test]
fn encrypt_decrypt_round_trip() {
let kms = MockKmsProvider {};
let plaintext = Vec::from("This is the plaintext used for this test 1");
let enc_result = EnvelopeEncryption::encrypt_seed(&kms, &plaintext);
assert_eq!(enc_result.is_ok(), true);
let ciphertext = enc_result.unwrap();
assert_ne!(plaintext, ciphertext);
let dec_result = EnvelopeEncryption::decrypt_seed(&kms, &ciphertext);
assert_eq!(dec_result.is_ok(), true);
let new_plaintext = dec_result.unwrap();
assert_eq!(plaintext, new_plaintext);
}
#[test]
fn invalid_dek_length_detected() {
let kms = MockKmsProvider {};
let plaintext = Vec::from("This is the plaintext used for this test 2");
let enc_result = EnvelopeEncryption::encrypt_seed(&kms, &plaintext);
assert_eq!(enc_result.is_ok(), true);
let ciphertext = enc_result.unwrap();
let mut ciphertext_copy = ciphertext.clone();
ciphertext_copy[1] = 99;
let dec_result = EnvelopeEncryption::decrypt_seed(&kms, &ciphertext_copy);
match dec_result.expect_err("expected an error") {
KmsError::InvalidData(msg) => assert!(msg.contains("invalid DEK")),
e => panic!("unexpected error {:?}", e),
}
}
#[test]
fn invalid_nonce_length_detected() {
let kms = MockKmsProvider {};
let plaintext = Vec::from("This is the plaintext used for this test 3");
let enc_result = EnvelopeEncryption::encrypt_seed(&kms, &plaintext);
assert_eq!(enc_result.is_ok(), true);
let ciphertext = enc_result.unwrap();
let mut ciphertext_copy = ciphertext.clone();
ciphertext_copy[2] = 1;
let dec_result = EnvelopeEncryption::decrypt_seed(&kms, &ciphertext_copy);
match dec_result.expect_err("expected an error") {
KmsError::InvalidData(msg) => assert!(msg.contains("nonce (1)")),
e => panic!("unexpected error {:?}", e),
}
}
#[test]
fn modified_ciphertext_is_detected() {
let kms = MockKmsProvider {};
let plaintext = Vec::from("This is the plaintext used for this test 4");
let enc_result = EnvelopeEncryption::encrypt_seed(&kms, &plaintext);
assert_eq!(enc_result.is_ok(), true);
let ciphertext = enc_result.unwrap();
assert_ne!(plaintext, ciphertext);
for i in (DEK_LEN_FIELD + NONCE_LEN_FIELD)..ciphertext.len() {
let mut ciphertext_copy = ciphertext.clone();
ciphertext_copy[i] = ciphertext[i].wrapping_add(1);
let dec_result = EnvelopeEncryption::decrypt_seed(&kms, &ciphertext_copy);
match dec_result.expect_err("Expected a KmsError error here") {
KmsError::OperationFailed(msg) => assert!(msg.contains("failed to decrypt")),
e => panic!("unexpected result {:?}", e),
}
}
}
}