// 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. //! //! Protect the server's long-term key with envelope encryption and a key management system. //! //! ## Motivation //! //! The seed for the server's [long-term key](../key/struct.LongTermKey.html) is subject to //! contradictory requirements: //! //! 1. The seed must be kept secret, but //! 2. The seed must be available at server start-up to create the //! [delegated on-line key](../key/struct.OnlineKey.html) //! //! ## Plaintext seed //! //! The default option is to store the seed in plaintext as part of the server's configuration. //! This usually means the seed is present in the clear: on disk, in a repository, or otherwise //! durably persisted where it can be compromised (accidentally or maliciously). //! //! ## Encrypting the seed //! //! Envelope encryption protects the seed by encrypting it with a locally generated 256-bit //! Data Encryption Key (DEK). The DEK itself is then encrypted using a cloud key management //! system (KMS). The resulting opaque encrypted "blob" (encrypted seed + encrypted DEK) is //! stored in the Roughenough configuration. //! //! At server start-up the KMS is used to decrypt the DEK, which is then used to (in memory) //! decrypt the seed. The seed is used to generate the //! [delegated on-line key](../key/struct.OnlineKey.html) after which the seed and DEK are erased //! from memory. //! //! See //! * [`EnvelopeEncryption`](struct.EnvelopeEncryption.html) for Roughenough's implementation. //! * [Google](https://cloud.google.com/kms/docs/envelope-encryption) or //! [Amazon](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#enveloping) //! for more in-depth explanations of envelope encryption. //! mod envelope; use base64; use std; use std::error::Error; use ring; use config::ServerConfig; use error; use key::KeyProtection; pub use self::envelope::EnvelopeEncryption; /// Errors generated by KMS operations #[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Clone)] pub enum KmsError { OperationFailed(String), InvalidConfiguration(String), InvalidData(String), InvalidKey(String), } impl From for KmsError { fn from(error: std::io::Error) -> Self { KmsError::OperationFailed(format!("{:?}", error)) } } impl From for KmsError { fn from(_: ring::error::Unspecified) -> Self { KmsError::OperationFailed("unspecified ring cryptographic failure".to_string()) } } impl From for KmsError { fn from(error: base64::DecodeError) -> Self { KmsError::OperationFailed(format!("base64: {}", error)) } } // Size of the AEAD nonce in bytes. const NONCE_SIZE_BYTES: usize = 12; // Size of the AEAD authentication tag in bytes. const TAG_SIZE_BYTES: usize = 16; // Size of the 256-bit Data Encryption Key (DEK) in bytes. const DEK_SIZE_BYTES: usize = 32; /// An unencrypted (plaintext) 256-bit Data Encryption Key (DEK). pub type PlaintextDEK = Vec; /// A Data Encryption Key (DEK) that has been encrypted (wrapped) by a Key Management System (KMS). /// /// This is an opaque, implementation-specific value. AEAD tag size, nonce size, /// provider metadata, and so on will vary between [`KmsProvider`](trait.KmsProvider.html) /// implementations. pub type EncryptedDEK = Vec; /// /// A key management system that wraps/unwraps a data encryption key (DEK). /// pub trait KmsProvider { /// Make a blocking request to encrypt (wrap) the provided plaintext data encryption key. fn encrypt_dek(&self, plaintext_dek: &PlaintextDEK) -> Result; /// Make a blocking request to decrypt (unwrap) a previously encrypted data encryption key. fn decrypt_dek(&self, encrypted_dek: &EncryptedDEK) -> Result; } #[cfg(feature = "awskms")] mod awskms; #[cfg(feature = "awskms")] pub use kms::awskms::inner::AwsKms; /// Load the seed value for the long-term key. /// /// Loading behavior depends on the value of `config.key_protection()`: /// /// * If `config.key_protection() == Plaintext` then the value returned from `config.seed()` /// is used as-is and assumed to be a 32-byte hexadecimal value. /// /// * Otherwise `config.seed()` is assumed to be an encrypted opaque blob generated from /// a prior `EnvelopeEncryption::encrypt_seed` call. The value of `config.key_protection()` /// is parsed as a KMS key id and `EnvelopeEncryption::decrypt_seed` is called to obtain /// the plaintext seed value. /// #[cfg(feature = "awskms")] pub fn load_seed(config: &Box) -> Result, error::Error> { use kms::envelope::EnvelopeEncryption; 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(feature = "gcpkms")] mod gcpkms; #[cfg(feature = "gcpkms")] pub use kms::gcpkms::inner::GcpKms; /// Load the seed value for the long-term key. /// /// Loading behavior depends on the value of `config.key_protection()`: /// /// * If `config.key_protection() == Plaintext` then the value returned from `config.seed()` /// is used as-is and assumed to be a 32-byte hexadecimal value. /// /// * Otherwise `config.seed()` is assumed to be an encrypted opaque blob generated from /// a prior `EnvelopeEncryption::encrypt_seed` call. The value of `config.key_protection()` /// is parsed as a KMS key id and `EnvelopeEncryption::decrypt_seed` is called to obtain /// the plaintext seed value. /// #[cfg(feature = "gcpkms")] pub fn load_seed(config: &Box) -> Result, error::Error> { use kms::envelope::EnvelopeEncryption; match config.key_protection() { KeyProtection::Plaintext => Ok(config.seed()), KeyProtection::GoogleKmsEnvelope(resource_id) => { info!("Unwrapping seed via Google KMS key '{}'", resource_id); let kms = GcpKms::from_resource_id(resource_id)?; let seed = EnvelopeEncryption::decrypt_seed(&kms, &config.seed())?; Ok(seed) } _ => Err(error::Error::InvalidConfiguration( "AWS KMS not supported".to_string(), )), } } /// /// Load the seed value for the long-term key. /// /// The KMS feature was disabled in this build of Roughenough. The only supported `key_protection` /// value is `plaintext`. Any other value is an error. /// #[cfg(all(not(feature = "awskms"), not(feature = "gcpkms")))] pub fn load_seed(config: &Box) -> Result, error::Error> { match config.key_protection() { KeyProtection::Plaintext => Ok(config.seed()), v => Err(error::Error::InvalidConfiguration(format!( "key_protection '{}' requires KMS, but server was not compiled with KMS support", v ))), } }