diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bin/kms.rs | 64 | ||||
-rw-r--r-- | src/bin/roughenough-server.rs | 15 | ||||
-rw-r--r-- | src/config/environment.rs | 9 | ||||
-rw-r--r-- | src/config/file.rs | 7 | ||||
-rw-r--r-- | src/config/mod.rs | 17 | ||||
-rw-r--r-- | src/key/awskms.rs | 101 | ||||
-rw-r--r-- | src/key/longterm.rs | 68 | ||||
-rw-r--r-- | src/key/mod.rs | 41 | ||||
-rw-r--r-- | src/key/online.rs (renamed from src/keys.rs) | 45 | ||||
-rw-r--r-- | src/lib.rs | 3 | ||||
-rw-r--r-- | src/sign.rs | 4 | ||||
-rw-r--r-- | src/tag.rs | 2 |
12 files changed, 323 insertions, 53 deletions
diff --git a/src/bin/kms.rs b/src/bin/kms.rs new file mode 100644 index 0000000..311fbb5 --- /dev/null +++ b/src/bin/kms.rs @@ -0,0 +1,64 @@ +// 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. + +//! +//! Work with Roughenough long-term key +//! + +#[macro_use] +extern crate clap; +#[macro_use] +extern crate log; +extern crate ring; +extern crate roughenough; +extern crate simple_logger; +extern crate untrusted; + +#[cfg(feature = "kms")] +use roughenough::key::awskms::AwsKms; + +use std::default::Default; + +use clap::{App, Arg}; +use roughenough::VERSION; + +pub fn main() { + use log::Level; + + simple_logger::init_with_level(Level::Info).unwrap(); + + let matches = App::new("Roughenough key management") + .version(VERSION) + .arg( + Arg::with_name("operation") + .required(true) + .help("The operation to perform") + .takes_value(true), + ).get_matches(); + + if cfg!(feature = "kms") { + info!("KMS feature enabled"); + let client = AwsKms::from_uri( + // your key here + ).unwrap(); + + let ciphertext = client.encrypt("This is a test".as_ref()).unwrap(); + info!("Ciphertext: {:?}", ciphertext); + + let plaintext = String::from_utf8(client.decrypt(ciphertext.as_ref()).unwrap()).unwrap(); + info!("Plaintext : {:?}", plaintext); + } + + info!("Done"); +} diff --git a/src/bin/roughenough-server.rs b/src/bin/roughenough-server.rs index 13a7026..34e8b90 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::keys::{LongTermKey, OnlineKey}; +use roughenough::key::{LongTermKey, OnlineKey}; use roughenough::merkle::MerkleTree; use roughenough::{Error, RtMessage, Tag}; use roughenough::{MIN_REQUEST_LENGTH, VERSION}; @@ -256,7 +256,7 @@ pub fn main() { Err(e) => { error!("{:?}", e); process::exit(1) - }, + } Ok(ref cfg) if !config::is_valid_config(&cfg) => process::exit(1), Ok(cfg) => cfg, }; @@ -268,8 +268,15 @@ pub fn main() { info!("Long-term public key : {}", long_term_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 5053517..8f91f0c 100644 --- a/src/config/environment.rs +++ b/src/config/environment.rs @@ -21,6 +21,7 @@ use std::time::Duration; use config::ServerConfig; use config::{DEFAULT_BATCH_SIZE, DEFAULT_STATUS_INTERVAL}; use Error; +use KeyProtection; /// /// Obtain a Roughenough server configuration ([ServerConfig](trait.ServerConfig.html)) @@ -33,6 +34,7 @@ use Error; /// seed | `ROUGHENOUGH_SEED` /// batch_size | `ROUGHENOUGH_BATCH_SIZE` /// status_interval | `ROUGHENOUGH_STATUS_INTERVAL` +/// key_protection | `ROUGHENOUGH_KEY_PROTECTION` /// pub struct EnvironmentConfig { port: u16, @@ -40,6 +42,7 @@ pub struct EnvironmentConfig { seed: Vec<u8>, batch_size: u8, status_interval: Duration, + key_protection: KeyProtection, } const ROUGHENOUGH_PORT: &str = "ROUGHENOUGH_PORT"; @@ -47,6 +50,7 @@ const ROUGHENOUGH_INTERFACE: &str = "ROUGHENOUGH_INTERFACE"; const ROUGHENOUGH_SEED: &str = "ROUGHENOUGH_SEED"; const ROUGHENOUGH_BATCH_SIZE: &str = "ROUGHENOUGH_BATCH_SIZE"; const ROUGHENOUGH_STATUS_INTERVAL: &str = "ROUGHENOUGH_STATUS_INTERVAL"; +const ROUGHENOUGH_KEY_PROTECTION: &str = "ROUGHENOUGH_KEY_PROTECTION"; impl EnvironmentConfig { pub fn new() -> Result<Self, Error> { @@ -56,6 +60,7 @@ impl EnvironmentConfig { seed: Vec::new(), batch_size: DEFAULT_BATCH_SIZE, status_interval: DEFAULT_STATUS_INTERVAL, + key_protection: KeyProtection::Plaintext, }; if let Ok(port) = env::var(ROUGHENOUGH_PORT) { @@ -123,4 +128,8 @@ impl ServerConfig for EnvironmentConfig { Err(_) => Err(Error::InvalidConfiguration(addr)), } } + + fn key_protection(&self) -> KeyProtection { + self.key_protection + } } diff --git a/src/config/file.rs b/src/config/file.rs index e93ee99..a3b8b92 100644 --- a/src/config/file.rs +++ b/src/config/file.rs @@ -23,6 +23,7 @@ use yaml_rust::YamlLoader; use config::ServerConfig; use config::{DEFAULT_BATCH_SIZE, DEFAULT_STATUS_INTERVAL}; use Error; +use KeyProtection; /// /// Read a Roughenough server configuration ([ServerConfig](trait.ServerConfig.html)) @@ -42,6 +43,7 @@ pub struct FileConfig { seed: Vec<u8>, batch_size: u8, status_interval: Duration, + key_protection: KeyProtection, } impl FileConfig { @@ -67,6 +69,7 @@ impl FileConfig { seed: Vec::new(), batch_size: DEFAULT_BATCH_SIZE, status_interval: DEFAULT_STATUS_INTERVAL, + key_protection: KeyProtection::Plaintext, }; for (key, value) in cfg[0].as_hash().unwrap() { @@ -124,4 +127,8 @@ impl ServerConfig for FileConfig { Err(_) => Err(Error::InvalidConfiguration(addr)), } } + + fn key_protection(&self) -> KeyProtection { + KeyProtection::Plaintext + } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 983338c..ac903b3 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -35,7 +35,9 @@ mod environment; pub use self::environment::EnvironmentConfig; +use key; use Error; +use KeyProtection; /// Maximum number of requests to process in one batch and include the the Merkle tree. pub const DEFAULT_BATCH_SIZE: u8 = 64; @@ -85,6 +87,9 @@ pub trait ServerConfig { /// 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; } /// @@ -127,14 +132,22 @@ pub fn is_valid_config(cfg: &Box<ServerConfig>) -> bool { 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/key/awskms.rs b/src/key/awskms.rs new file mode 100644 index 0000000..87ba4bd --- /dev/null +++ b/src/key/awskms.rs @@ -0,0 +1,101 @@ +// 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. + +#[cfg(feature = "kms")] +extern crate rusoto_core; +#[cfg(feature = "kms")] +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 std::default::Default; +use std::error::Error; +use std::fmt; +use std::fmt::Formatter; +use std::str::FromStr; + +#[cfg(feature = "kms")] +pub struct AwsKms { + kms_client: KmsClient, + key_id: String, +} + +#[cfg(feature = "kms")] +impl AwsKms { + pub fn from_uri(uri: &str) -> Result<Self, DecryptError> { + let parts: Vec<&str> = uri.split(':').collect(); + + if parts.len() != 6 { + return Err(DecryptError::Validation( + "invalid KMS arn: too few parts".to_string(), + )); + } + + 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())), + }; + + Ok(AwsKms { + kms_client: KmsClient::new(region), + key_id: uri.to_string(), + }) + } + + pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, EncryptError> { + let mut encrypt_req: EncryptRequest = Default::default(); + + encrypt_req.key_id = self.key_id.clone(); + encrypt_req.plaintext = Vec::from(plaintext); + + match self.kms_client.encrypt(encrypt_req).sync() { + Ok(result) => { + let ciphertext = result + .ciphertext_blob + .expect("no ciphertext despite successful response"); + Ok(ciphertext) + } + Err(e) => Err(e), + } + } + + pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, DecryptError> { + let mut decrypt_req: DecryptRequest = Default::default(); + + decrypt_req.ciphertext_blob = Vec::from(ciphertext); + + match self.kms_client.decrypt(decrypt_req).sync() { + Ok(result) => { + let plaintext = result + .plaintext + .expect("no plaintext despite successful response"); + Ok(plaintext) + } + Err(e) => Err(e), + } + } +} + +#[cfg(feature = "kms")] +impl fmt::Display for AwsKms { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.key_id) + } +} diff --git a/src/key/longterm.rs b/src/key/longterm.rs new file mode 100644 index 0000000..f06a318 --- /dev/null +++ b/src/key/longterm.rs @@ -0,0 +1,68 @@ +// 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. + +//! +//! Represents the server's long-term identity. +//! + +use time::Timespec; + +use byteorder::{LittleEndian, WriteBytesExt}; + +use std::fmt; +use std::fmt::Formatter; + +use key::OnlineKey; +use message::RtMessage; +use sign::Signer; +use tag::Tag; +use CERTIFICATE_CONTEXT; + +/// +/// Represents the server's long-term identity. +/// +pub struct LongTermKey { + signer: Signer, +} + +impl LongTermKey { + pub fn new(seed: &[u8]) -> Self { + LongTermKey { + signer: Signer::from_seed(seed), + } + } + + /// Create a CERT message with a DELE containing the provided online key + /// and a SIG of the DELE value signed by the long-term key + pub fn make_cert(&mut self, online_key: &OnlineKey) -> RtMessage { + let dele_bytes = online_key.make_dele().encode().unwrap(); + + self.signer.update(CERTIFICATE_CONTEXT.as_bytes()); + self.signer.update(&dele_bytes); + + let dele_signature = self.signer.sign(); + + let mut cert_msg = RtMessage::new(2); + cert_msg.add_field(Tag::SIG, &dele_signature).unwrap(); + cert_msg.add_field(Tag::DELE, &dele_bytes).unwrap(); + + cert_msg + } +} + +impl fmt::Display for LongTermKey { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.signer) + } +} diff --git a/src/key/mod.rs b/src/key/mod.rs new file mode 100644 index 0000000..a4af975 --- /dev/null +++ b/src/key/mod.rs @@ -0,0 +1,41 @@ +// 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. + +//! +//! Representations and management of Roughtime's online and long-term Ed25519 keys +//! + +extern crate hex; +extern crate log; + +mod longterm; +mod online; + +pub use self::longterm::LongTermKey; +pub use self::online::OnlineKey; + +#[cfg(feature = "kms")] +pub mod awskms; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Clone, Copy)] +pub enum KeyProtection { + /// No protection, seed is in plaintext + Plaintext, + + /// Envelope encryption of seed by AWS Key Management Service + AwsKmsEnvelope, + + /// Envelope encryption of seed by Google Cloud Key Management Service + GoogleKmsEnvelope, +} diff --git a/src/keys.rs b/src/key/online.rs index 2fadb00..18c8b8f 100644 --- a/src/keys.rs +++ b/src/key/online.rs @@ -12,10 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! -//! Representations of Roughtime's online and long-term Ed25519 keys -//! - use message::RtMessage; use sign::Signer; use tag::Tag; @@ -23,10 +19,11 @@ use time::Timespec; use byteorder::{LittleEndian, WriteBytesExt}; -use super::{CERTIFICATE_CONTEXT, SIGNED_RESPONSE_CONTEXT}; use std::fmt; use std::fmt::Formatter; +use SIGNED_RESPONSE_CONTEXT; + /// /// Represents the delegated Roughtime ephemeral online key. /// @@ -107,41 +104,3 @@ impl fmt::Display for OnlineKey { write!(f, "{}", self.signer) } } - -/// -/// Represents the server's long-term identity. -/// -pub struct LongTermKey { - signer: Signer, -} - -impl LongTermKey { - pub fn new(seed: &[u8]) -> Self { - LongTermKey { - signer: Signer::from_seed(seed), - } - } - - /// Create a CERT message with a DELE containing the provided online key - /// and a SIG of the DELE value signed by the long-term key - pub fn make_cert(&mut self, online_key: &OnlineKey) -> RtMessage { - let dele_bytes = online_key.make_dele().encode().unwrap(); - - self.signer.update(CERTIFICATE_CONTEXT.as_bytes()); - self.signer.update(&dele_bytes); - - let dele_signature = self.signer.sign(); - - let mut cert_msg = RtMessage::new(2); - cert_msg.add_field(Tag::SIG, &dele_signature).unwrap(); - cert_msg.add_field(Tag::DELE, &dele_bytes).unwrap(); - - cert_msg - } -} - -impl fmt::Display for LongTermKey { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}", self.signer) - } -} @@ -61,11 +61,12 @@ mod message; mod tag; pub mod config; -pub mod keys; +pub mod key; pub mod merkle; pub mod sign; pub use error::Error; +pub use key::KeyProtection; pub use message::RtMessage; pub use tag::Tag; diff --git a/src/sign.rs b/src/sign.rs index bd141b0..ead5a4c 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -20,10 +20,10 @@ extern crate hex; extern crate ring; extern crate untrusted; -use self::ring::signature; -use self::ring::signature::Ed25519KeyPair; use self::ring::rand; use self::ring::rand::SecureRandom; +use self::ring::signature; +use self::ring::signature::Ed25519KeyPair; use self::untrusted::Input; @@ -15,7 +15,7 @@ use error::Error; /// An unsigned 32-bit value (key) that maps to a byte-string (value). -#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Clone)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Clone, Copy)] pub enum Tag { // Enforcement of the "tags in strictly increasing order" rule is done using the // little-endian encoding of the ASCII tag value; e.g. 'SIG\x00' is 0x00474953 and |