summaryrefslogtreecommitdiff
path: root/src
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
parented89d98692ac273ec7dfc39c19008334077779a3 (diff)
downloadroughenough-fec19a7d65c9dca293056f40b4a1983b82a0e68d.zip
Refactor to kms module; add documentation
Diffstat (limited to 'src')
-rw-r--r--src/bin/roughenough-kms.rs14
-rw-r--r--src/bin/roughenough-server.rs4
-rw-r--r--src/config/environment.rs2
-rw-r--r--src/config/file.rs2
-rw-r--r--src/config/mod.rs20
-rw-r--r--src/error.rs2
-rw-r--r--src/key/mod.rs88
-rw-r--r--src/kms/awskms.rs (renamed from src/key/awskms.rs)5
-rw-r--r--src/kms/envelope.rs (renamed from src/key/envelope.rs)26
-rw-r--r--src/kms/mod.rs145
-rw-r--r--src/lib.rs19
11 files changed, 217 insertions, 110 deletions
diff --git a/src/bin/roughenough-kms.rs b/src/bin/roughenough-kms.rs
index 072f451..cb9a904 100644
--- a/src/bin/roughenough-kms.rs
+++ b/src/bin/roughenough-kms.rs
@@ -28,14 +28,11 @@ extern crate untrusted;
use clap::{App, Arg};
use roughenough::VERSION;
-#[allow(unused_imports)]
-use roughenough::key::EnvelopeEncryption;
-
-#[cfg(feature = "kms")]
-use roughenough::key::awskms::AwsKms;
-
#[cfg(feature = "kms")]
fn aws_kms(kms_key: &str, plaintext_seed: &[u8]) {
+ use roughenough::kms::EnvelopeEncryption;
+ use roughenough::kms::AwsKms;
+
let client = AwsKms::from_arn(kms_key).unwrap();
match EnvelopeEncryption::encrypt_seed(&client, &plaintext_seed) {
@@ -55,8 +52,9 @@ pub fn main() {
simple_logger::init_with_level(Level::Info).unwrap();
- let matches = App::new("Roughenough key management")
+ let matches = App::new("roughenough-kms")
.version(VERSION)
+ .long_about("Encrypt a Roughenough server's long-term seed using a KMS")
.arg(
Arg::with_name("KEY_ID")
.short("k")
@@ -70,7 +68,7 @@ pub fn main() {
.long("seed")
.takes_value(true)
.required(true)
- .help("Seed for the server's long-term identity"),
+ .help("32 byte hex seed for the server's long-term identity"),
).get_matches();
let kms_key = matches.value_of("KEY_ID").unwrap();
diff --git a/src/bin/roughenough-server.rs b/src/bin/roughenough-server.rs
index 5be8620..52ae904 100644
--- a/src/bin/roughenough-server.rs
+++ b/src/bin/roughenough-server.rs
@@ -55,7 +55,7 @@ use byteorder::{LittleEndian, WriteBytesExt};
use roughenough::config;
use roughenough::config::ServerConfig;
-use roughenough::key;
+use roughenough::kms;
use roughenough::key::{LongTermKey, OnlineKey};
use roughenough::merkle::MerkleTree;
use roughenough::{Error, RtMessage, Tag};
@@ -266,7 +266,7 @@ pub fn main() {
let public_key: String;
let cert_bytes = {
- let seed = key::load_seed(&config).unwrap();
+ let seed = kms::load_seed(&config).unwrap();
let mut long_term_key = LongTermKey::new(&seed);
public_key = hex::encode(long_term_key.public_key());
diff --git a/src/config/environment.rs b/src/config/environment.rs
index b7fe0da..2385b28 100644
--- a/src/config/environment.rs
+++ b/src/config/environment.rs
@@ -21,7 +21,7 @@ use std::time::Duration;
use config::ServerConfig;
use config::{DEFAULT_BATCH_SIZE, DEFAULT_STATUS_INTERVAL};
use Error;
-use KeyProtection;
+use key::KeyProtection;
///
/// Obtain a Roughenough server configuration ([ServerConfig](trait.ServerConfig.html))
diff --git a/src/config/file.rs b/src/config/file.rs
index 602baa1..440c78c 100644
--- a/src/config/file.rs
+++ b/src/config/file.rs
@@ -23,7 +23,7 @@ use yaml_rust::YamlLoader;
use config::ServerConfig;
use config::{DEFAULT_BATCH_SIZE, DEFAULT_STATUS_INTERVAL};
use Error;
-use KeyProtection;
+use key::KeyProtection;
///
/// Read a Roughenough server configuration ([ServerConfig](trait.ServerConfig.html))
diff --git a/src/config/mod.rs b/src/config/mod.rs
index f0e2a1a..f05578b 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -18,7 +18,8 @@
//! The [ServerConfig](trait.ServerConfig.html) trait specifies the required and optional
//! parameters available for configuring a Roughenoguh server instance.
//!
-//! Implementations of `ServerConfig` obtain configurations from different back-end sources.
+//! Implementations of `ServerConfig` obtain configurations from different back-end sources
+//! such as files or environment variables.
//!
extern crate hex;
@@ -34,7 +35,7 @@ mod environment;
pub use self::environment::EnvironmentConfig;
use Error;
-use KeyProtection;
+use key::KeyProtection;
/// Maximum number of requests to process in one batch and include the the Merkle tree.
pub const DEFAULT_BATCH_SIZE: u8 = 64;
@@ -55,6 +56,7 @@ pub const DEFAULT_STATUS_INTERVAL: Duration = Duration::from_secs(600);
/// `seed` | `ROUGHENOUGH_SEED` | Required | A 32-byte hexadecimal value used to generate the server's long-term key pair. **This is a secret value and must be un-guessable**, treat it with care.
/// `batch_size` | `ROUGHENOUGH_BATCH_SIZE` | Optional | The maximum number of requests to process in one batch. All nonces in a batch are used to build a Merkle tree, the root of which is signed. Defaults to [DEFAULT_BATCH_SIZE](constant.DEFAULT_BATCH_SIZE.html) requests per batch.
/// `status_interval` | `ROUGHENOUGH_STATUS_INTERVAL` | Optional | Number of _seconds_ between each logged status update. Default value is [DEFAULT_STATUS_INTERVAL](constant.DEFAULT_STATUS_INTERVAL.html).
+/// `key_protection` | `ROUGHENOUGH_KEY_PROTECTION` | Optional | Encryption method (if any) applied to the `seed`. Defaults to "`plaintext`" (no encryption, `seed` is in the clear).
///
/// Implementations of this trait obtain a valid configuration from different back-end
/// sources. See:
@@ -82,15 +84,19 @@ pub trait ServerConfig {
/// Defaults to [DEFAULT_STATUS_INTERVAL](constant.DEFAULT_STATUS_INTERVAL.html)
fn status_interval(&self) -> Duration;
+ /// [Optional] Method used to protect the seed for the server's long-term key pair.
+ /// Defaults to "`plaintext`" (no encryption, seed is in the clear).
+ fn key_protection(&self) -> &KeyProtection;
+
/// Convenience function to create a `SocketAddr` from the provided `interface` and `port`
fn socket_addr(&self) -> Result<SocketAddr, Error>;
-
- /// Method used to protect the long-term key pair.
- fn key_protection(&self) -> &KeyProtection;
}
+/// Factory function to create a `ServerConfig` _trait object_ based on the value
+/// of the provided `arg`.
///
-/// Factory function to create a `ServerConfig` trait object based on the provided `arg`
+/// * `ENV` will return an [`EnvironmentConfig`](struct.EnvironmentConfig.html)
+/// * any other value returns a [`FileConfig`](struct.FileConfig.html)
///
pub fn make_config(arg: &str) -> Result<Box<ServerConfig>, Error> {
if arg == "ENV" {
@@ -107,7 +113,7 @@ pub fn make_config(arg: &str) -> Result<Box<ServerConfig>, Error> {
}
///
-/// Validate configuration settings
+/// Validate configuration settings. Returns `true` if the config is valid, `false` otherwise.
///
pub fn is_valid_config(cfg: &Box<ServerConfig>) -> bool {
let mut is_valid = true;
diff --git a/src/error.rs b/src/error.rs
index 7ffe1eb..e91a340 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -14,7 +14,7 @@
use std;
-use key::KmsError;
+use kms::KmsError;
use tag::Tag;
/// Error types generated by this implementation
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
- ))),
- }
-}
diff --git a/src/key/awskms.rs b/src/kms/awskms.rs
index 878eff9..396490f 100644
--- a/src/key/awskms.rs
+++ b/src/kms/awskms.rs
@@ -31,8 +31,9 @@ use std::fmt;
use std::fmt::Formatter;
use std::str::FromStr;
-use key::{EncryptedDEK, KmsError, KmsProvider, PlaintextDEK, DEK_SIZE_BYTES};
+use kms::{EncryptedDEK, KmsError, KmsProvider, PlaintextDEK, DEK_SIZE_BYTES};
+/// Amazon Key Management Service
#[cfg(feature = "kms")]
pub struct AwsKms {
kms_client: KmsClient,
@@ -41,6 +42,8 @@ pub struct AwsKms {
#[cfg(feature = "kms")]
impl AwsKms {
+
+ /// Create a new instance from the ARN of a AWS KMS key.
pub fn from_arn(arn: &str) -> Result<Self, KmsError> {
let parts: Vec<&str> = arn.split(':').collect();
diff --git a/src/key/envelope.rs b/src/kms/envelope.rs
index ed774ae..b6b536d 100644
--- a/src/key/envelope.rs
+++ b/src/kms/envelope.rs
@@ -22,7 +22,8 @@ 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};
+use kms::{KmsError, KmsProvider, DEK_SIZE_BYTES, NONCE_SIZE_BYTES, TAG_SIZE_BYTES};
+use std::string::ToString;
const DEK_LEN_FIELD: usize = 2;
const NONCE_LEN_FIELD: usize = 2;
@@ -54,9 +55,26 @@ fn vec_zero_filled(len: usize) -> Vec<u8> {
return v;
}
+///
+/// Envelope encryption of the long-term key seed value.
+///
+/// The seed is encrypted using AES-GCM-256 with:
+///
+/// * 32 byte (256 bit) random key
+/// * 12 byte (96 bit) random nonce
+/// * 16 byte (128 bit) authentication tag
+///
+/// Randomness obtained from
+/// [`ring::rand::SecureRandom`](https://briansmith.org/rustdoc/ring/rand/trait.SecureRandom.html).
+///
+/// The key used to encrypt the seed is wrapped (encrypted) using a
+/// [`KmsProvider`](trait.KmsProvider.html) implementation.
+///
pub struct EnvelopeEncryption;
impl EnvelopeEncryption {
+
+ /// Decrypt a seed previously encrypted with `encrypt_seed()`
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!(
@@ -97,6 +115,12 @@ impl EnvelopeEncryption {
}
}
+ ///
+ /// Encrypt the seed value and protect the seed's encryption key using a
+ /// [KmsProvider](trait.KmsProvider.html).
+ ///
+ /// The returned encrypted byte blob is safe to store on unsecured media.
+ ///
pub fn encrypt_seed(kms: &KmsProvider, plaintext_seed: &[u8]) -> Result<Vec<u8>, KmsError> {
// Generate random DEK and nonce
let rng = rand::SystemRandom::new();
diff --git a/src/kms/mod.rs b/src/kms/mod.rs
new file mode 100644
index 0000000..b47202a
--- /dev/null
+++ b/src/kms/mod.rs
@@ -0,0 +1,145 @@
+// 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
+//!
+//! Using envelope encryption the seed is protected by encrypting it with a locally generated
+//! Data Encryption Key (DEK) and then encrypting the DEK using a cloud key management system
+//! (KMS). See [`EnvelopeEncryption`](struct.EnvelopeEncryption.html) for the implementation.
+//!
+//! The resulting opaque encrypted "blob" (encrypted seed + encrypted DEK) is safely stored
+//! in the Roughenough configuration. At server start-up the KMS is used to decrypt the DEK,
+//! and the DEK is used to (temporarily in memory) decrypt the seed. The seed is used to
+//! generate the [delegated on-line key](../key/struct.OnlineKey.html) after which the seed
+//! is erased.
+//!
+//! For details see explanations from
+//! [Google](https://cloud.google.com/kms/docs/envelope-encryption) or
+//! [Amazon](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#enveloping).
+//!
+
+mod envelope;
+
+pub use self::envelope::EnvelopeEncryption;
+
+use std;
+use std::error::Error;
+use ring;
+
+use config::ServerConfig;
+use error;
+use key::KeyProtection;
+
+/// 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<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 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.
+pub const DEK_SIZE_BYTES: usize = 32;
+
+/// An unencrypted (plaintext) 256-bit Data Encryption Key (DEK).
+pub type PlaintextDEK = Vec<u8>;
+
+/// A Data Encryption Key (DEK) that has been encrypted (wrapped) by a Key Management System (KMS).
+///
+/// This is an opaque value and the size of the encrypted DEK is implementation specific.
+/// Things like AEAD tag size, nonce size, provider metadata, and so on will vary between
+/// [`KmsProvider`](trait.KmsProvider.html) implementations.
+pub type EncryptedDEK = Vec<u8>;
+
+///
+/// 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<EncryptedDEK, KmsError>;
+
+ /// Make a blocking request to decrypt (unwrap) a previously encrypted data encryption key.
+ fn decrypt_dek(&self, encrypted_dek: &EncryptedDEK) -> Result<PlaintextDEK, KmsError>;
+}
+
+#[cfg(feature = "kms")]
+mod awskms;
+
+#[cfg(feature = "kms")]
+pub use kms::awskms::AwsKms;
+
+#[cfg(feature = "kms")]
+pub fn load_seed(config: &Box<ServerConfig>) -> Result<Vec<u8>, 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(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
+ ))),
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 0903f95..0d003b2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -25,6 +25,16 @@
//! Roughtime messages are represented by [`RtMessage`](struct.RtMessage.html) which
//! implements the mapping of Roughtime `u32` [`tags`](enum.Tag.html) to byte-strings.
//!
+//! # Keys and Signing
+//!
+//! Roughtime uses an [Ed25519](https://ed25519.cr.yp.to/) key pair as the server's
+//! long-term identity and a second key pair (signed by the long-term key) as a
+//! delegated on-line (ephemeral) key.
+//!
+//! [`LongTermKey`](key/struct.LongTermKey.html) and [`OnlineKey`](key/struct.OnlineKey.html)
+//! implement these elements of the protocol. The [`sign`](sign/index.html) module provides
+//! signing and verification operations.
+//!
//! # Client
//!
//! A Roughtime client can be found in `src/bin/client.rs`. To run the client:
@@ -37,11 +47,10 @@
//!
//! # Server
//!
-//! The Roughtime server implementation is in `src/bin/server.rs`. The server is
-//! configured via a YAML config file. See [FileConfig](config/struct.FileConfig.html)
-//! for details of the configuration parameters.
+//! The Roughtime server implementation is in `src/bin/server.rs`. The server has multiple
+//! ways it can be configured, see [ServerConfig](config/trait.ServerConfig.html) for details.
//!
-//! To run the server:
+//! To run the server with a config file:
//!
//! ```bash
//! $ cargo run --release --bin server /path/to/config.file
@@ -63,11 +72,11 @@ mod tag;
pub mod config;
pub mod key;
+pub mod kms;
pub mod merkle;
pub mod sign;
pub use error::Error;
-pub use key::KeyProtection;
pub use message::RtMessage;
pub use tag::Tag;