Skip to content

Commit

Permalink
Land KMS support, yay!
Browse files Browse the repository at this point in the history
AWS KMS for now, work-in-progress
  • Loading branch information
int08h committed Oct 7, 2018
1 parent b43bcb2 commit 0b924cc
Show file tree
Hide file tree
Showing 13 changed files with 329 additions and 53 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ keywords = ["roughtime", "cryptography", "crypto"]
[badges]
travis-ci = { repository = "int08h/roughenough", branch = "master" }

[features]
default = []
kms = ["rusoto_core", "rusoto_kms"]

[dependencies]
mio = "0.6"
mio-extras = "2.0"
Expand All @@ -26,3 +30,5 @@ clap = "2"
chrono = "0.4"
hex = "0.3"

rusoto_core = { version = "0.34", optional = true }
rusoto_kms = { version = "0.34", optional = true }
64 changes: 64 additions & 0 deletions src/bin/kms.rs
Original file line number Diff line number Diff line change
@@ -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");
}
15 changes: 11 additions & 4 deletions src/bin/roughenough-server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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,
};
Expand All @@ -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);

Expand Down
9 changes: 9 additions & 0 deletions src/config/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -33,20 +34,23 @@ 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,
interface: String,
seed: Vec<u8>,
batch_size: u8,
status_interval: Duration,
key_protection: KeyProtection,
}

const ROUGHENOUGH_PORT: &str = "ROUGHENOUGH_PORT";
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> {
Expand All @@ -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) {
Expand Down Expand Up @@ -123,4 +128,8 @@ impl ServerConfig for EnvironmentConfig {
Err(_) => Err(Error::InvalidConfiguration(addr)),
}
}

fn key_protection(&self) -> KeyProtection {
self.key_protection
}
}
7 changes: 7 additions & 0 deletions src/config/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -42,6 +43,7 @@ pub struct FileConfig {
seed: Vec<u8>,
batch_size: u8,
status_interval: Duration,
key_protection: KeyProtection,
}

impl FileConfig {
Expand All @@ -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() {
Expand Down Expand Up @@ -124,4 +127,8 @@ impl ServerConfig for FileConfig {
Err(_) => Err(Error::InvalidConfiguration(addr)),
}
}

fn key_protection(&self) -> KeyProtection {
KeyProtection::Plaintext
}
}
17 changes: 15 additions & 2 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

///
Expand Down Expand Up @@ -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;
}
_ => (),
Expand Down
101 changes: 101 additions & 0 deletions src/key/awskms.rs
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading

0 comments on commit 0b924cc

Please sign in to comment.