diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | src/bin/roughenough-kms.rs | 11 | ||||
-rw-r--r-- | src/bin/roughenough-server.rs | 25 | ||||
-rw-r--r-- | src/config/environment.rs | 8 | ||||
-rw-r--r-- | src/config/file.rs | 13 | ||||
-rw-r--r-- | src/config/mod.rs | 23 | ||||
-rw-r--r-- | src/error.rs | 12 | ||||
-rw-r--r-- | src/key/awskms.rs | 5 | ||||
-rw-r--r-- | src/key/envelope.rs | 6 | ||||
-rw-r--r-- | src/key/longterm.rs | 9 | ||||
-rw-r--r-- | src/key/mod.rs | 49 |
11 files changed, 107 insertions, 55 deletions
@@ -1,3 +1,4 @@ Cargo.lock target/ *.rs.bk +example-kms.cfg diff --git a/src/bin/roughenough-kms.rs b/src/bin/roughenough-kms.rs index b660488..c8d3c74 100644 --- a/src/bin/roughenough-kms.rs +++ b/src/bin/roughenough-kms.rs @@ -27,6 +27,8 @@ extern crate untrusted; use clap::{App, Arg}; use roughenough::VERSION; + +#[allow(unused_imports)] use roughenough::key::EnvelopeEncryption; #[cfg(feature = "kms")] @@ -47,6 +49,7 @@ fn aws_kms(kms_key: &str, plaintext_seed: &[u8]) { } } +#[allow(unused_variables)] pub fn main() { use log::Level; @@ -54,13 +57,13 @@ pub fn main() { let matches = App::new("Roughenough key management") .version(VERSION) - .arg(Arg::with_name("kms-key") + .arg(Arg::with_name("KEY_ID") .short("k") .long("kms-key") .takes_value(true) .required(true) .help("Identity of the KMS key to be used")) - .arg(Arg::with_name("seed") + .arg(Arg::with_name("SEED") .short("s") .long("seed") .takes_value(true) @@ -68,8 +71,8 @@ pub fn main() { .help("Seed for the server's long-term identity")) .get_matches(); - let kms_key = matches.value_of("kms-key").unwrap(); - let plaintext_seed = matches.value_of("seed") + let kms_key = matches.value_of("KEY_ID").unwrap(); + let plaintext_seed = matches.value_of("SEED") .map(|seed| hex::decode(seed).expect("Error parsing seed value")) .unwrap(); diff --git a/src/bin/roughenough-server.rs b/src/bin/roughenough-server.rs index 34e8b90..5be8620 100644 --- a/src/bin/roughenough-server.rs +++ b/src/bin/roughenough-server.rs @@ -55,6 +55,7 @@ use byteorder::{LittleEndian, WriteBytesExt}; use roughenough::config; use roughenough::config::ServerConfig; +use roughenough::key; use roughenough::key::{LongTermKey, OnlineKey}; use roughenough::merkle::MerkleTree; use roughenough::{Error, RtMessage, Tag}; @@ -262,21 +263,21 @@ pub fn main() { }; let mut online_key = OnlineKey::new(); - let mut long_term_key = LongTermKey::new(config.seed()); - let cert_bytes = long_term_key.make_cert(&online_key).encode().unwrap(); + let public_key: String; - info!("Long-term public key : {}", long_term_key); + let cert_bytes = { + let seed = key::load_seed(&config).unwrap(); + let mut long_term_key = LongTermKey::new(&seed); + public_key = hex::encode(long_term_key.public_key()); + + long_term_key.make_cert(&online_key).encode().unwrap() + }; + + info!("Long-term public key : {}", public_key); info!("Online public key : {}", online_key); info!("Max response batch size : {}", config.batch_size()); - info!( - "Status updates every : {} seconds", - config.status_interval().as_secs() - ); - info!( - "Server listening on : {}:{}", - config.interface(), - config.port() - ); + info!("Status updates every : {} seconds", config.status_interval().as_secs()); + info!("Server listening on : {}:{}", config.interface(), config.port()); polling_loop(&config, &mut online_key, &cert_bytes); diff --git a/src/config/environment.rs b/src/config/environment.rs index a4cb528..14559d1 100644 --- a/src/config/environment.rs +++ b/src/config/environment.rs @@ -111,8 +111,8 @@ impl ServerConfig for EnvironmentConfig { self.port } - fn seed(&self) -> &[u8] { - &self.seed + fn seed(&self) -> Vec<u8> { + self.seed.clone() } fn batch_size(&self) -> u8 { @@ -131,7 +131,7 @@ impl ServerConfig for EnvironmentConfig { } } - fn key_protection(&self) -> KeyProtection { - self.key_protection + fn key_protection(&self) -> &KeyProtection { + &self.key_protection } } diff --git a/src/config/file.rs b/src/config/file.rs index a3b8b92..fd84404 100644 --- a/src/config/file.rs +++ b/src/config/file.rs @@ -86,6 +86,11 @@ impl FileConfig { let val = value.as_i64().expect("status_interval value invalid"); config.status_interval = Duration::from_secs(val as u64) } + "key_protection" => { + let val = value.as_str().unwrap().parse() + .expect(format!("invalid key_protection value: {:?}", value).as_ref()); + config.key_protection = val + } unknown => { return Err(Error::InvalidConfiguration(format!( "unknown config key: {}", @@ -108,8 +113,8 @@ impl ServerConfig for FileConfig { self.port } - fn seed(&self) -> &[u8] { - &self.seed + fn seed(&self) -> Vec<u8> { + self.seed.clone() } fn batch_size(&self) -> u8 { @@ -128,7 +133,7 @@ impl ServerConfig for FileConfig { } } - fn key_protection(&self) -> KeyProtection { - KeyProtection::Plaintext + fn key_protection(&self) -> &KeyProtection { + &self.key_protection } } diff --git a/src/config/mod.rs b/src/config/mod.rs index ac903b3..f0e2a1a 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -28,14 +28,11 @@ use std::net::SocketAddr; use std::time::Duration; mod file; - pub use self::file::FileConfig; mod environment; - pub use self::environment::EnvironmentConfig; -use key; use Error; use KeyProtection; @@ -74,7 +71,7 @@ pub trait ServerConfig { /// [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. - fn seed(&self) -> &[u8]; + fn seed(&self) -> Vec<u8>; /// [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. @@ -89,7 +86,7 @@ pub trait ServerConfig { fn socket_addr(&self) -> Result<SocketAddr, Error>; /// Method used to protect the long-term key pair. - fn key_protection(&self) -> KeyProtection; + fn key_protection(&self) -> &KeyProtection; } /// @@ -127,27 +124,19 @@ pub fn is_valid_config(cfg: &Box<ServerConfig>) -> bool { error!("seed value is missing"); is_valid = false; } - if !cfg.seed().is_empty() && cfg.seed().len() != 32 { - error!("seed value must be 32 characters long"); + if *cfg.key_protection() == KeyProtection::Plaintext && cfg.seed().len() != 32 { + error!("plaintext seed value must be 32 characters long"); is_valid = false; } if cfg.batch_size() < 1 || cfg.batch_size() > 64 { - error!( - "batch_size {} is invalid; valid range 1-64", - cfg.batch_size() - ); + error!("batch_size {} is invalid; valid range 1-64", cfg.batch_size()); is_valid = false; } if is_valid { match cfg.socket_addr() { Err(e) => { - error!( - "failed to create socket {}:{} {:?}", - cfg.interface(), - cfg.port(), - e - ); + error!("failed to create socket {}:{} {:?}", cfg.interface(), cfg.port(), e); is_valid = false; } _ => (), diff --git a/src/error.rs b/src/error.rs index b681f33..971bccd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -15,6 +15,7 @@ use std; use tag::Tag; +use key::KmsError; /// Error types generated by this implementation #[derive(Debug)] @@ -58,3 +59,14 @@ impl From<std::io::Error> for Error { Error::EncodingFailure(err) } } + +impl From<KmsError> for Error { + fn from(err: KmsError) -> Self { + match err { + KmsError::OperationFailed(m) => Error::InvalidConfiguration(m), + KmsError::InvalidConfiguration(m) => Error::InvalidConfiguration(m), + KmsError::InvalidData(m) => Error::InvalidConfiguration(m), + KmsError::InvalidKey(m) => Error::InvalidConfiguration(m), + } + } +} diff --git a/src/key/awskms.rs b/src/key/awskms.rs index 234864b..878eff9 100644 --- a/src/key/awskms.rs +++ b/src/key/awskms.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +extern crate hex; extern crate log; #[cfg(feature = "kms")] @@ -22,9 +23,7 @@ extern crate rusoto_kms; #[cfg(feature = "kms")] use self::rusoto_core::Region; #[cfg(feature = "kms")] -use self::rusoto_kms::{ - DecryptError, DecryptRequest, EncryptError, EncryptRequest, Kms, KmsClient, -}; +use self::rusoto_kms::{DecryptRequest, EncryptRequest, Kms, KmsClient}; use std::default::Default; use std::error::Error; diff --git a/src/key/envelope.rs b/src/key/envelope.rs index 5128b76..ed774ae 100644 --- a/src/key/envelope.rs +++ b/src/key/envelope.rs @@ -42,7 +42,7 @@ const MIN_PAYLOAD_SIZE: usize = DEK_LEN_FIELD // No input prefix to skip, consume entire buffer const IN_PREFIX_LEN: usize = 0; -// Trivial domain separation to guard KMS key reuse +// 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 @@ -91,7 +91,7 @@ impl EnvelopeEncryption { 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(e) => Err(KmsError::OperationFailed( + Err(_) => Err(KmsError::OperationFailed( "failed to decrypt plaintext seed".to_string(), )), } @@ -124,7 +124,7 @@ impl EnvelopeEncryption { TAG_SIZE_BYTES, ) { Ok(enc_len) => plaintext_buf[..enc_len].to_vec(), - Err(e) => { + Err(_) => { return Err(KmsError::OperationFailed( "failed to encrypt plaintext seed".to_string(), )) diff --git a/src/key/longterm.rs b/src/key/longterm.rs index f06a318..ddac6ea 100644 --- a/src/key/longterm.rs +++ b/src/key/longterm.rs @@ -16,10 +16,6 @@ //! Represents the server's long-term identity. //! -use time::Timespec; - -use byteorder::{LittleEndian, WriteBytesExt}; - use std::fmt; use std::fmt::Formatter; @@ -59,6 +55,11 @@ impl LongTermKey { cert_msg } + + /// Return the public key for the provided seed + pub fn public_key(&self) -> &[u8] { + self.signer.public_key_bytes() + } } impl fmt::Display for LongTermKey { diff --git a/src/key/mod.rs b/src/key/mod.rs index 7ae2198..3fe365f 100644 --- a/src/key/mod.rs +++ b/src/key/mod.rs @@ -32,10 +32,10 @@ pub use self::envelope::EnvelopeEncryption; pub use self::longterm::LongTermKey; pub use self::online::OnlineKey; -#[cfg(feature = "kms")] -pub mod awskms; +use super::error; +use super::config::ServerConfig; -#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Clone)] pub enum KeyProtection { /// No protection, seed is in plaintext Plaintext, @@ -47,6 +47,16 @@ pub enum KeyProtection { GoogleKmsEnvelope(String), } +impl Display for KeyProtection { + fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { + match self { + KeyProtection::Plaintext => write!(f, "Plaintext"), + KeyProtection::AwsKmsEnvelope(key_id) => write!(f, "AwsKms({})", key_id), + KeyProtection::GoogleKmsEnvelope(key_id) => write!(f, "GoogleKms({})", key_id), + } + } +} + impl FromStr for KeyProtection { type Err = (); @@ -75,7 +85,7 @@ impl From<std::io::Error> for KmsError { } impl From<ring::error::Unspecified> for KmsError { - fn from(error: ring::error::Unspecified) -> Self { + fn from(_: ring::error::Unspecified) -> Self { KmsError::OperationFailed("unspecified ring cryptographic failure".to_string()) } } @@ -101,3 +111,34 @@ 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))) + } +} |