// Copyright 2017-2019 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. //! //! Note: KMS support must be enabled at compile time, see the Roughenough's [documentation //! on optional features](https://github.com/int08h/roughenough/blob/master/doc/OPTIONAL-FEATURES.md#key-management-system-kms-support) //! for instructions. //! //! ## 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 ring; use std; use crate::config::ServerConfig; use crate::error; use crate::key::KmsProtection; 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; // Trivial domain separation to guard against KMS key reuse const AD: &str = "roughenough"; /// 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 crate::kms::awskms::inner::AwsKms; /// Load the seed value for the long-term key. /// /// Loading behavior depends on the value of `config.kms_protection()`: /// /// * If `config.kms_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.kms_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 crate::kms::envelope::EnvelopeEncryption; match config.kms_protection() { KmsProtection::Plaintext => Ok(config.seed()), KmsProtection::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 crate::kms::gcpkms::inner::GcpKms; /// Load the seed value for the long-term key. /// /// Loading behavior depends on the value of `config.kms_protection()`: /// /// * If `config.kms_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.kms_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 crate::kms::envelope::EnvelopeEncryption; match config.kms_protection() { KmsProtection::Plaintext => Ok(config.seed()), KmsProtection::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. /// /// ## This build has KMS disabled /// /// *The KMS feature is disabled in this build of Roughenough*. /// /// The only supported `kms_protection` value in this build is `plaintext`. Any /// other value will cause a runtime error. /// /// ## Background /// /// Loading behavior depends on the value of `config.kms_protection()`: /// /// * If `config.kms_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.kms_protection()` /// is parsed as a KMS key id and `EnvelopeEncryption::decrypt_seed` is called to obtain /// the plaintext seed value. /// #[cfg(not(any(feature = "awskms", feature = "gcpkms")))] pub fn load_seed(config: &Box) -> Result, error::Error> { match config.kms_protection() { KmsProtection::Plaintext => Ok(config.seed()), v => Err(error::Error::InvalidConfiguration(format!( "kms_protection '{}' requires KMS, but server was not compiled with KMS support", v ))), } }