summaryrefslogtreecommitdiff
path: root/src/key
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/key
parented89d98692ac273ec7dfc39c19008334077779a3 (diff)
downloadroughenough-fec19a7d65c9dca293056f40b4a1983b82a0e68d.zip
Refactor to kms module; add documentation
Diffstat (limited to 'src/key')
-rw-r--r--src/key/awskms.rs125
-rw-r--r--src/key/envelope.rs147
-rw-r--r--src/key/mod.rs88
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
- ))),
- }
-}