summaryrefslogtreecommitdiff
path: root/src/kms/envelope.rs
diff options
context:
space:
mode:
authorStuart Stock <stuart@int08h.com>2018-10-12 22:39:37 -0500
committerStuart Stock <stuart@int08h.com>2018-10-12 22:39:37 -0500
commitfec19a7d65c9dca293056f40b4a1983b82a0e68d (patch)
tree0e02ecc6174804a5be46f4fba19b7f98be6ab3d2 /src/kms/envelope.rs
parented89d98692ac273ec7dfc39c19008334077779a3 (diff)
downloadroughenough-fec19a7d65c9dca293056f40b4a1983b82a0e68d.zip
Refactor to kms module; add documentation
Diffstat (limited to 'src/kms/envelope.rs')
-rw-r--r--src/kms/envelope.rs171
1 files changed, 171 insertions, 0 deletions
diff --git a/src/kms/envelope.rs b/src/kms/envelope.rs
new file mode 100644
index 0000000..b6b536d
--- /dev/null
+++ b/src/kms/envelope.rs
@@ -0,0 +1,171 @@
+// Copyright 2017-2018 int08h LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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;
+use ring::rand::SecureRandom;
+
+use super::super::MIN_SEED_LENGTH;
+use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
+use kms::{KmsError, KmsProvider, DEK_SIZE_BYTES, NONCE_SIZE_BYTES, TAG_SIZE_BYTES};
+use std::string::ToString;
+
+const DEK_LEN_FIELD: usize = 2;
+const NONCE_LEN_FIELD: usize = 2;
+
+// 2 bytes - encrypted DEK length
+// 2 bytes - nonce length
+// n bytes - encrypted DEK
+// n bytes - nonce
+// n bytes - opaque (AEAD encrypted seed + tag)
+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;
+
+// No input prefix to skip, consume entire buffer
+const IN_PREFIX_LEN: usize = 0;
+
+// Trivial domain separation to guard against KMS key reuse
+static AD: &[u8; 11] = b"roughenough";
+
+// Convenience function to create zero-filled Vec of given size
+fn vec_zero_filled(len: usize) -> Vec<u8> {
+ let mut v = Vec::with_capacity(len);
+ for _ in 0..len {
+ v.push(0);
+ }
+ return v;
+}
+
+///
+/// Envelope encryption of the long-term key seed value.
+///
+/// The seed is encrypted using AES-GCM-256 with:
+///
+/// * 32 byte (256 bit) random key
+/// * 12 byte (96 bit) random nonce
+/// * 16 byte (128 bit) authentication tag
+///
+/// Randomness obtained from
+/// [`ring::rand::SecureRandom`](https://briansmith.org/rustdoc/ring/rand/trait.SecureRandom.html).
+///
+/// The key used to encrypt the seed is wrapped (encrypted) using a
+/// [`KmsProvider`](trait.KmsProvider.html) implementation.
+///
+pub struct EnvelopeEncryption;
+
+impl EnvelopeEncryption {
+
+ /// Decrypt a seed previously encrypted with `encrypt_seed()`
+ 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);
+
+ // Read the lengths of the wrapped DEK and of the nonce
+ let dek_len = tmp.read_u16::<LittleEndian>()?;
+ let nonce_len = tmp.read_u16::<LittleEndian>()?;
+
+ // Consume the wrapped DEK
+ let mut encrypted_dek = vec_zero_filled(dek_len as usize);
+ tmp.read_exact(&mut encrypted_dek)?;
+
+ // 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)?;
+
+ // Invoke KMS to decrypt the DEK
+ let dek = kms.decrypt_dek(&encrypted_dek)?;
+
+ // 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(_) => Err(KmsError::OperationFailed(
+ "failed to decrypt plaintext seed".to_string(),
+ )),
+ }
+ }
+
+ ///
+ /// Encrypt the seed value and protect the seed's encryption key using a
+ /// [KmsProvider](trait.KmsProvider.html).
+ ///
+ /// The returned encrypted byte blob is safe to store on unsecured media.
+ ///
+ 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; 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 using the DEK
+ 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(_) => {
+ return Err(KmsError::OperationFailed(
+ "failed to encrypt plaintext seed".to_string(),
+ ))
+ }
+ };
+
+ // Use the KMS to 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)?;
+
+ Ok(output)
+ }
+}