diff options
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/bin/kms.rs | 4 | ||||
-rw-r--r-- | src/key/awskms.rs | 66 | ||||
-rw-r--r-- | src/key/envelope.rs | 12 | ||||
-rw-r--r-- | src/key/mod.rs | 30 | ||||
-rw-r--r-- | src/lib.rs | 3 |
6 files changed, 84 insertions, 33 deletions
@@ -1,6 +1,6 @@ [package] name = "roughenough" -version = "1.0.6" +version = "1.1.0" repository = "https://github.com/int08h/roughenough" authors = ["Stuart Stock <stuart@int08h.com>", "Aaron Hill <aa1ronham@gmail.com>"] license = "Apache-2.0" diff --git a/src/bin/kms.rs b/src/bin/kms.rs index 724c4ad..3661e48 100644 --- a/src/bin/kms.rs +++ b/src/bin/kms.rs @@ -49,8 +49,8 @@ pub fn main() { if cfg!(feature = "kms") { info!("KMS feature enabled"); - let client = AwsKms::from_uri( - "arn:aws:kms:us-east-2:927891522318:key/1c96fb2c-d417-48f4-bf24-8e7173a587f5" + 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(); diff --git a/src/key/awskms.rs b/src/key/awskms.rs index 87ba4bd..bb8d215 100644 --- a/src/key/awskms.rs +++ b/src/key/awskms.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +extern crate log; + #[cfg(feature = "kms")] extern crate rusoto_core; #[cfg(feature = "kms")] @@ -30,6 +32,8 @@ 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, @@ -38,57 +42,75 @@ pub struct AwsKms { #[cfg(feature = "kms")] impl AwsKms { - pub fn from_uri(uri: &str) -> Result<Self, DecryptError> { - let parts: Vec<&str> = uri.split(':').collect(); + pub fn from_arn(arn: &str) -> Result<Self, KmsError> { + let parts: Vec<&str> = arn.split(':').collect(); if parts.len() != 6 { - return Err(DecryptError::Validation( - "invalid KMS arn: too few parts".to_string(), + 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(DecryptError::Validation(e.description().to_string())), + Err(e) => return Err(KmsError::InvalidConfiguration(e.description().to_string())), }; Ok(AwsKms { kms_client: KmsClient::new(region), - key_id: uri.to_string(), + key_id: arn.to_string(), }) } +} - pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, EncryptError> { - let mut encrypt_req: EncryptRequest = Default::default(); +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 = Vec::from(plaintext); + encrypt_req.plaintext = plaintext_dek.clone(); match self.kms_client.encrypt(encrypt_req).sync() { Ok(result) => { - let ciphertext = result - .ciphertext_blob - .expect("no ciphertext despite successful response"); - Ok(ciphertext) + if let Some(ciphertext) = result.ciphertext_blob { + Ok(ciphertext) + } else { + Err(KmsError::EncryptionFailed( + "no ciphertext despite successful response".to_string(), + )) + } } - Err(e) => Err(e), + Err(e) => Err(KmsError::EncryptionFailed(e.description().to_string())), } } - pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, DecryptError> { + fn decrypt_dek(&self, encrypted_dek: &EncryptedDEK) -> Result<PlaintextDEK, KmsError> { let mut decrypt_req: DecryptRequest = Default::default(); - - decrypt_req.ciphertext_blob = Vec::from(ciphertext); + decrypt_req.ciphertext_blob = encrypted_dek.clone(); match self.kms_client.decrypt(decrypt_req).sync() { Ok(result) => { - let plaintext = result - .plaintext - .expect("no plaintext despite successful response"); - Ok(plaintext) + 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::DecryptionFailed( + "decrypted payload is empty".to_string(), + )) + } } - Err(e) => Err(e), + Err(e) => Err(KmsError::DecryptionFailed(e.description().to_string())), } } } diff --git a/src/key/envelope.rs b/src/key/envelope.rs index 3e54255..5c869c5 100644 --- a/src/key/envelope.rs +++ b/src/key/envelope.rs @@ -14,18 +14,22 @@ extern crate hex; +use ring::aead::AES_256_GCM; use ring::rand; use ring::rand::SecureRandom; -use ring::aead::AES_256_GCM; -use key::awskms::AwsKms; +use key::KmsProvider; + +/// +/// 2 bytes - encrypted DEK length +/// n bytes - encrypted DEK +/// 2 bytes - encrypted pub struct EnvelopeEncryption; impl EnvelopeEncryption { - pub fn encrypt(kms: &AwsKms, plaintext: &[u8]) -> Vec<u8> { + pub fn encrypt(kms: &KmsProvider, plaintext: &[u8]) -> Vec<u8> { let rng = rand::SystemRandom::new(); let mut dek = [0u8; 16]; rng.fill(&mut dek).unwrap(); - } } diff --git a/src/key/mod.rs b/src/key/mod.rs index da18303..b269af2 100644 --- a/src/key/mod.rs +++ b/src/key/mod.rs @@ -19,9 +19,9 @@ extern crate hex; extern crate log; +mod envelope; mod longterm; mod online; -mod envelope; pub use self::longterm::LongTermKey; pub use self::online::OnlineKey; @@ -34,9 +34,33 @@ pub enum KeyProtection { /// No protection, seed is in plaintext Plaintext, - /// Envelope encryption of seed by AWS Key Management Service + /// Envelope encryption with Key-Encrypting-Key (KEK) from AWS Key Management Service AwsKmsEnvelope, - /// Envelope encryption of seed by Google Cloud Key Management Service + /// Envelope encryption with Key-Encrypting-Key (KEK) from Google Cloud Key Management Service GoogleKmsEnvelope, } + +#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Clone, Copy)] +pub enum KmsError { + DecryptionFailed(String), + EncryptionFailed(String), + InvalidConfiguration(String), + InvalidKey(String), +} + +/// Size of the Data Encryption Key (DEK) in bytes +pub const DEK_SIZE_BYTES: usize = 32; + +/// 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>; +} @@ -49,6 +49,7 @@ //! extern crate byteorder; +extern crate clear_on_drop; extern crate core; extern crate time; extern crate yaml_rust; @@ -72,7 +73,7 @@ pub use message::RtMessage; pub use tag::Tag; /// Version of Roughenough -pub const VERSION: &str = "1.0.6"; +pub const VERSION: &str = "1.1.0"; // Constants and magic numbers of the Roughtime protocol |