diff options
author | Stuart Stock <stuart@int08h.com> | 2018-10-12 22:39:37 -0500 |
---|---|---|
committer | Stuart Stock <stuart@int08h.com> | 2018-10-12 22:39:37 -0500 |
commit | fec19a7d65c9dca293056f40b4a1983b82a0e68d (patch) | |
tree | 0e02ecc6174804a5be46f4fba19b7f98be6ab3d2 /src/key | |
parent | ed89d98692ac273ec7dfc39c19008334077779a3 (diff) | |
download | roughenough-fec19a7d65c9dca293056f40b4a1983b82a0e68d.zip |
Refactor to kms module; add documentation
Diffstat (limited to 'src/key')
-rw-r--r-- | src/key/awskms.rs | 125 | ||||
-rw-r--r-- | src/key/envelope.rs | 147 | ||||
-rw-r--r-- | src/key/mod.rs | 88 |
3 files changed, 5 insertions, 355 deletions
diff --git a/src/key/awskms.rs b/src/key/awskms.rs deleted file mode 100644 index 878eff9..0000000 --- a/src/key/awskms.rs +++ /dev/null @@ -1,125 +0,0 @@ -// 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; -extern crate log; - -#[cfg(feature = "kms")] -extern crate rusoto_core; -#[cfg(feature = "kms")] -extern crate rusoto_kms; - -#[cfg(feature = "kms")] -use self::rusoto_core::Region; -#[cfg(feature = "kms")] -use self::rusoto_kms::{DecryptRequest, EncryptRequest, Kms, KmsClient}; - -use std::default::Default; -use std::error::Error; -use std::fmt; -use std::fmt::Formatter; -use std::str::FromStr; - -use key::{EncryptedDEK, KmsError, KmsProvider, PlaintextDEK, DEK_SIZE_BYTES}; - -#[cfg(feature = "kms")] -pub struct AwsKms { - kms_client: KmsClient, - key_id: String, -} - -#[cfg(feature = "kms")] -impl AwsKms { - pub fn from_arn(arn: &str) -> Result<Self, KmsError> { - let parts: Vec<&str> = arn.split(':').collect(); - - if parts.len() != 6 { - return Err(KmsError::InvalidConfiguration(format!( - "invalid KMS arn: too few parts {}", - parts.len() - ))); - } - - let region_part = parts.get(3).expect("region is missing"); - let region = match Region::from_str(region_part) { - Ok(r) => r, - Err(e) => return Err(KmsError::InvalidConfiguration(e.description().to_string())), - }; - - Ok(AwsKms { - kms_client: KmsClient::new(region), - key_id: arn.to_string(), - }) - } -} - -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() - ))); - } - - let mut encrypt_req: EncryptRequest = Default::default(); - encrypt_req.key_id = self.key_id.clone(); - encrypt_req.plaintext = plaintext_dek.clone(); - - match self.kms_client.encrypt(encrypt_req).sync() { - Ok(result) => { - if let Some(ciphertext) = result.ciphertext_blob { - Ok(ciphertext) - } else { - Err(KmsError::OperationFailed( - "no ciphertext despite successful response".to_string(), - )) - } - } - Err(e) => Err(KmsError::OperationFailed(e.description().to_string())), - } - } - - fn decrypt_dek(&self, encrypted_dek: &EncryptedDEK) -> Result<PlaintextDEK, KmsError> { - let mut decrypt_req: DecryptRequest = Default::default(); - decrypt_req.ciphertext_blob = encrypted_dek.clone(); - - match self.kms_client.decrypt(decrypt_req).sync() { - Ok(result) => { - if let Some(plaintext_dek) = result.plaintext { - if plaintext_dek.len() == DEK_SIZE_BYTES { - Ok(plaintext_dek) - } else { - Err(KmsError::InvalidKey(format!( - "decrypted DEK wrong length: {}", - plaintext_dek.len() - ))) - } - } else { - Err(KmsError::OperationFailed( - "decrypted payload is empty".to_string(), - )) - } - } - Err(e) => Err(KmsError::OperationFailed(e.description().to_string())), - } - } -} - -#[cfg(feature = "kms")] -impl fmt::Display for AwsKms { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}", self.key_id) - } -} diff --git a/src/key/envelope.rs b/src/key/envelope.rs deleted file mode 100644 index ed774ae..0000000 --- a/src/key/envelope.rs +++ /dev/null @@ -1,147 +0,0 @@ -// 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 key::{KmsError, KmsProvider, DEK_SIZE_BYTES, NONCE_SIZE_BYTES, TAG_SIZE_BYTES}; - -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; -} - -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); - - // 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(), - )), - } - } - - 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) - } -} diff --git a/src/key/mod.rs b/src/key/mod.rs index e59bfda..bd06931 100644 --- a/src/key/mod.rs +++ b/src/key/mod.rs @@ -21,29 +21,26 @@ extern crate log; extern crate ring; extern crate std; -mod envelope; mod longterm; mod online; -use std::error::Error; +use std::fmt::Display; +use std::fmt::Formatter; use std::str::FromStr; -pub use self::envelope::EnvelopeEncryption; pub use self::longterm::LongTermKey; pub use self::online::OnlineKey; -use super::config::ServerConfig; -use super::error; - +/// Methods for protecting the server's long-term identity #[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Clone)] pub enum KeyProtection { /// No protection, seed is in plaintext Plaintext, - /// Envelope encryption using AWS Key Management Service + /// Envelope encryption of the seed using AWS Key Management Service AwsKmsEnvelope(String), - /// Envelope encryption using Google Cloud Key Management Service + /// Envelope encryption of the seed using Google Cloud Key Management Service GoogleKmsEnvelope(String), } @@ -70,78 +67,3 @@ impl FromStr for KeyProtection { } } -#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Clone)] -pub enum KmsError { - 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(_: 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>; - -/// A Data Encryption Key (DEK) that has been encrypted (wrapped) by a Key Encryption Key (KEK). -/// Size of the encrypted DEK is implementation specific (things like AEAD tag size, nonce size, -/// provider metadata, and so on will cause it to vary). -type EncryptedDEK = Vec<u8>; - -pub trait KmsProvider { - fn encrypt_dek(&self, plaintext_dek: &PlaintextDEK) -> Result<EncryptedDEK, KmsError>; - fn decrypt_dek(&self, encrypted_dek: &EncryptedDEK) -> Result<PlaintextDEK, KmsError>; -} - -#[cfg(feature = "kms")] -pub mod awskms; - -#[cfg(feature = "kms")] -use key::awskms::AwsKms; -use std::fmt::Display; -use std::fmt::Formatter; - -#[cfg(feature = "kms")] -pub fn load_seed(config: &Box<ServerConfig>) -> Result<Vec<u8>, error::Error> { - match config.key_protection() { - KeyProtection::Plaintext => Ok(config.seed()), - KeyProtection::AwsKmsEnvelope(key_id) => { - info!("Unwrapping seed via AWS KMS key '{}'", key_id); - let kms = AwsKms::from_arn(key_id)?; - let seed = EnvelopeEncryption::decrypt_seed(&kms, &config.seed())?; - Ok(seed) - } - _ => Err(error::Error::InvalidConfiguration( - "Google KMS not supported".to_string(), - )), - } -} - -#[cfg(not(feature = "kms"))] -pub fn load_seed(config: &Box<ServerConfig>) -> Result<Vec<u8>, error::Error> { - match config.key_protection() { - KeyProtection::Plaintext => Ok(config.seed()), - v => Err(error::Error::InvalidConfiguration(format!( - "key_protection '{}' implies KMS but server was not compiled with KMS support", v - ))), - } -} |