summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorStuart Stock <stuart@int08h.com>2018-10-14 14:47:09 -0500
committerStuart Stock <stuart@int08h.com>2018-10-14 14:47:09 -0500
commitac71e92f11d91cca566dd38c78bcdef2db6c1811 (patch)
treed388a60b9bb743a50d185496756995dfe7605b52 /src
parentb1acebdbc53bf3a8fab45e6f059ae35acb235a01 (diff)
downloadroughenough-ac71e92f11d91cca566dd38c78bcdef2db6c1811.zip
one-way GCP KMS working
Diffstat (limited to 'src')
-rw-r--r--src/bin/roughenough-kms.rs27
-rw-r--r--src/bin/roughenough-server.rs12
-rw-r--r--src/kms/awskms.rs178
-rw-r--r--src/kms/envelope.rs2
-rw-r--r--src/kms/gcpkms.rs95
-rw-r--r--src/kms/mod.rs31
-rw-r--r--src/lib.rs3
7 files changed, 242 insertions, 106 deletions
diff --git a/src/bin/roughenough-kms.rs b/src/bin/roughenough-kms.rs
index cb9a904..6563224 100644
--- a/src/bin/roughenough-kms.rs
+++ b/src/bin/roughenough-kms.rs
@@ -28,7 +28,7 @@ extern crate untrusted;
use clap::{App, Arg};
use roughenough::VERSION;
-#[cfg(feature = "kms")]
+#[cfg(feature = "awskms")]
fn aws_kms(kms_key: &str, plaintext_seed: &[u8]) {
use roughenough::kms::EnvelopeEncryption;
use roughenough::kms::AwsKms;
@@ -46,6 +46,24 @@ fn aws_kms(kms_key: &str, plaintext_seed: &[u8]) {
}
}
+#[cfg(feature = "gcpkms")]
+fn gcp_kms(kms_key: &str, plaintext_seed: &[u8]) {
+ use roughenough::kms::EnvelopeEncryption;
+ use roughenough::kms::GcpKms;
+
+ let client = GcpKms::from_resource_id(kms_key).unwrap();
+
+ match EnvelopeEncryption::encrypt_seed(&client, &plaintext_seed) {
+ Ok(encrypted_blob) => {
+ println!("key_protection: \"{}\"", kms_key);
+ println!("seed: {}", hex::encode(&encrypted_blob));
+ }
+ Err(e) => {
+ error!("Error: {:?}", e);
+ }
+ }
+}
+
#[allow(unused_variables)]
pub fn main() {
use log::Level;
@@ -85,9 +103,12 @@ pub fn main() {
return;
}
- if cfg!(feature = "kms") {
- #[cfg(feature = "kms")]
+ if cfg!(feature = "awskms") {
+ #[cfg(feature = "awskms")]
aws_kms(kms_key, &plaintext_seed);
+ } else if cfg!(feature = "gcpkms") {
+ #[cfg(feature = "gcpkms")]
+ gcp_kms(kms_key, &plaintext_seed);
} else {
warn!("KMS not enabled, nothing to do");
}
diff --git a/src/bin/roughenough-server.rs b/src/bin/roughenough-server.rs
index 52ae904..6a9cb5c 100644
--- a/src/bin/roughenough-server.rs
+++ b/src/bin/roughenough-server.rs
@@ -239,12 +239,22 @@ fn polling_loop(config: &Box<ServerConfig>, online_key: &mut OnlineKey, cert_byt
}
}
+fn kms_support_str() -> &'static str {
+ if cfg!(feature = "awskms") {
+ " (+AWS KMS)"
+ } else if cfg!(feature = "gcpkms") {
+ " (+GCP KMS)"
+ } else {
+ ""
+ }
+}
+
pub fn main() {
use log::Level;
simple_logger::init_with_level(Level::Info).unwrap();
- info!("Roughenough server v{} starting", VERSION);
+ info!("Roughenough server v{}{} starting", VERSION, kms_support_str());
let mut args = env::args();
if args.len() != 2 {
diff --git a/src/kms/awskms.rs b/src/kms/awskms.rs
index 396490f..96d4a38 100644
--- a/src/kms/awskms.rs
+++ b/src/kms/awskms.rs
@@ -15,114 +15,110 @@
extern crate hex;
extern crate log;
-#[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::{DecryptRequest, EncryptRequest, Kms, KmsClient};
-
-use std::default::Default;
-use std::error::Error;
-use std::fmt;
-use std::fmt::Formatter;
-use std::str::FromStr;
-
-use kms::{EncryptedDEK, KmsError, KmsProvider, PlaintextDEK, DEK_SIZE_BYTES};
-
-/// Amazon Key Management Service
-#[cfg(feature = "kms")]
-pub struct AwsKms {
- kms_client: KmsClient,
- key_id: String,
-}
-
-#[cfg(feature = "kms")]
-impl AwsKms {
+#[cfg(feature = "awskms")]
+pub mod inner {
+ extern crate rusoto_core;
+ extern crate rusoto_kms;
+
+ use std::default::Default;
+ use std::error::Error;
+ use std::fmt;
+ use std::fmt::Formatter;
+ use std::str::FromStr;
+
+ use self::rusoto_core::Region;
+ use self::rusoto_kms::{DecryptRequest, EncryptRequest, Kms, KmsClient};
+ use kms::{EncryptedDEK, KmsError, KmsProvider, PlaintextDEK, DEK_SIZE_BYTES};
+
+ /// Amazon Key Management Service
+ pub struct AwsKms {
+ kms_client: KmsClient,
+ key_id: String,
+ }
- /// 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();
+ 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();
- if parts.len() != 6 {
- return Err(KmsError::InvalidConfiguration(format!(
- "invalid KMS arn: too few parts {}",
- parts.len()
- )));
- }
+ if parts.len() != 6 {
+ return Err(KmsError::InvalidConfiguration(format!(
+ "invalid KMS arn: too few parts {}",
+ parts.len()
+ )));
+ }
- 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(KmsError::InvalidConfiguration(e.description().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(KmsError::InvalidConfiguration(e.description().to_string())),
+ };
- Ok(AwsKms {
- kms_client: KmsClient::new(region),
- key_id: arn.to_string(),
- })
+ Ok(AwsKms {
+ kms_client: KmsClient::new(region),
+ key_id: arn.to_string(),
+ })
+ }
}
-}
-impl KmsProvider for AwsKms {
- fn encrypt_dek(&self, plaintext_dek: &PlaintextDEK) -> Result<EncryptedDEK, KmsError> {
- if plaintext_dek.len() != DEK_SIZE_BYTES {
- return Err(KmsError::InvalidKey(format!(
- "provided DEK wrong length: {}",
- plaintext_dek.len()
- )));
- }
+ impl KmsProvider for AwsKms {
+ fn encrypt_dek(&self, plaintext_dek: &PlaintextDEK) -> Result<EncryptedDEK, KmsError> {
+ if plaintext_dek.len() != DEK_SIZE_BYTES {
+ return Err(KmsError::InvalidKey(format!(
+ "provided DEK wrong length: {}",
+ plaintext_dek.len()
+ )));
+ }
- let mut encrypt_req: EncryptRequest = Default::default();
- encrypt_req.key_id = self.key_id.clone();
- encrypt_req.plaintext = plaintext_dek.clone();
-
- match self.kms_client.encrypt(encrypt_req).sync() {
- Ok(result) => {
- if let Some(ciphertext) = result.ciphertext_blob {
- Ok(ciphertext)
- } else {
- Err(KmsError::OperationFailed(
- "no ciphertext despite successful response".to_string(),
- ))
+ let mut encrypt_req: EncryptRequest = Default::default();
+ encrypt_req.key_id = self.key_id.clone();
+ encrypt_req.plaintext = plaintext_dek.clone();
+
+ match self.kms_client.encrypt(encrypt_req).sync() {
+ Ok(result) => {
+ if let Some(ciphertext) = result.ciphertext_blob {
+ Ok(ciphertext)
+ } else {
+ Err(KmsError::OperationFailed(
+ "no ciphertext despite successful response".to_string(),
+ ))
+ }
}
+ Err(e) => Err(KmsError::OperationFailed(e.description().to_string())),
}
- Err(e) => Err(KmsError::OperationFailed(e.description().to_string())),
}
- }
- fn decrypt_dek(&self, encrypted_dek: &EncryptedDEK) -> Result<PlaintextDEK, KmsError> {
- let mut decrypt_req: DecryptRequest = Default::default();
- decrypt_req.ciphertext_blob = encrypted_dek.clone();
-
- match self.kms_client.decrypt(decrypt_req).sync() {
- Ok(result) => {
- if let Some(plaintext_dek) = result.plaintext {
- if plaintext_dek.len() == DEK_SIZE_BYTES {
- Ok(plaintext_dek)
+ fn decrypt_dek(&self, encrypted_dek: &EncryptedDEK) -> Result<PlaintextDEK, KmsError> {
+ let mut decrypt_req: DecryptRequest = Default::default();
+ decrypt_req.ciphertext_blob = encrypted_dek.clone();
+
+ match self.kms_client.decrypt(decrypt_req).sync() {
+ Ok(result) => {
+ if let Some(plaintext_dek) = result.plaintext {
+ if plaintext_dek.len() == DEK_SIZE_BYTES {
+ Ok(plaintext_dek)
+ } else {
+ Err(KmsError::InvalidKey(format!(
+ "decrypted DEK wrong length: {}",
+ plaintext_dek.len()
+ )))
+ }
} else {
- Err(KmsError::InvalidKey(format!(
- "decrypted DEK wrong length: {}",
- plaintext_dek.len()
- )))
+ Err(KmsError::OperationFailed(
+ "decrypted payload is empty".to_string(),
+ ))
}
- } else {
- Err(KmsError::OperationFailed(
- "decrypted payload is empty".to_string(),
- ))
}
+ Err(e) => Err(KmsError::OperationFailed(e.description().to_string())),
}
- Err(e) => Err(KmsError::OperationFailed(e.description().to_string())),
}
}
-}
-#[cfg(feature = "kms")]
-impl fmt::Display for AwsKms {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "{}", self.key_id)
+ #[cfg(feature = "awskms")]
+ impl fmt::Display for AwsKms {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "{}", self.key_id)
+ }
}
}
+
diff --git a/src/kms/envelope.rs b/src/kms/envelope.rs
index b6b536d..1f6d615 100644
--- a/src/kms/envelope.rs
+++ b/src/kms/envelope.rs
@@ -117,7 +117,7 @@ impl EnvelopeEncryption {
///
/// Encrypt the seed value and protect the seed's encryption key using a
- /// [KmsProvider](trait.KmsProvider.html).
+ /// [`KmsProvider`](trait.KmsProvider.html).
///
/// The returned encrypted byte blob is safe to store on unsecured media.
///
diff --git a/src/kms/gcpkms.rs b/src/kms/gcpkms.rs
new file mode 100644
index 0000000..0dfdb29
--- /dev/null
+++ b/src/kms/gcpkms.rs
@@ -0,0 +1,95 @@
+// 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.
+
+extern crate hex;
+extern crate log;
+
+#[cfg(feature = "gcpkms")]
+pub mod inner {
+ extern crate base64;
+ extern crate hyper;
+ extern crate hyper_rustls;
+ extern crate yup_oauth2 as oauth2;
+ extern crate google_cloudkms1 as cloudkms1;
+
+ use std::result::Result;
+ use std::default::Default;
+ use std::error::Error;
+ use std::fmt;
+ use std::fmt::Formatter;
+ use std::str::FromStr;
+
+ use self::oauth2::{service_account_key_from_file, ServiceAccountAccess, ServiceAccountKey};
+ use self::cloudkms1::CloudKMS;
+ use self::cloudkms1::{Result as CloudKmsResult, Error as CloudKmsError, EncryptRequest, DecryptRequest};
+ use self::hyper::net::HttpsConnector;
+ use self::hyper::status::StatusCode;
+ use self::hyper_rustls::TlsClient;
+
+ use kms::{EncryptedDEK, KmsError, KmsProvider, PlaintextDEK};
+
+ pub struct GcpKms {
+ key_resource_id: String,
+ service_account: ServiceAccountKey,
+ }
+
+ impl GcpKms {
+ pub fn from_resource_id(resource_id: &str) -> Result<Self, KmsError> {
+ let client_secret = oauth2::service_account_key_from_file(&"creds.json".to_string())
+ .unwrap();
+
+ Ok(GcpKms {
+ key_resource_id: resource_id.to_string(),
+ service_account: client_secret
+ })
+ }
+ }
+
+ impl KmsProvider for GcpKms {
+ fn encrypt_dek(&self, plaintext_dek: &PlaintextDEK) -> Result<EncryptedDEK, KmsError> {
+ let client1 = hyper::Client::with_connector(HttpsConnector::new(TlsClient::new()));
+ let access = oauth2::ServiceAccountAccess::new(self.service_account.clone(), client1);
+
+ let client2 = hyper::Client::with_connector(HttpsConnector::new(TlsClient::new()));
+ let hub = CloudKMS::new(client2, access);
+
+ let mut request = EncryptRequest::default();
+ request.plaintext = Some(base64::encode(plaintext_dek));
+
+ let result = hub
+ .projects()
+ .locations_key_rings_crypto_keys_encrypt(request, &self.key_resource_id)
+ .doit();
+
+ match result {
+ Ok((http_resp, enc_resp)) => {
+ if http_resp.status == StatusCode::Ok {
+ let ciphertext = enc_resp.ciphertext.unwrap();
+ let ct = base64::decode(&ciphertext)?;
+ Ok(ct)
+ } else {
+ Err(KmsError::OperationFailed(format!("{:?}", http_resp)))
+ }
+ }
+ Err(e) => Err(KmsError::OperationFailed(e.description().to_string()))
+ }
+ }
+
+ fn decrypt_dek(&self, encrypted_dek: &EncryptedDEK) -> Result<PlaintextDEK, KmsError> {
+ Ok(Vec::new())
+ }
+ }
+}
+
+
diff --git a/src/kms/mod.rs b/src/kms/mod.rs
index 60c5a86..efc1def 100644
--- a/src/kms/mod.rs
+++ b/src/kms/mod.rs
@@ -34,8 +34,8 @@
//!
//! 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 safe
-//! to store in the Roughenough configuration.
+//! 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
@@ -51,8 +51,7 @@
mod envelope;
-pub use self::envelope::EnvelopeEncryption;
-
+use base64;
use std;
use std::error::Error;
use ring;
@@ -61,6 +60,8 @@ use config::ServerConfig;
use error;
use key::KeyProtection;
+pub use self::envelope::EnvelopeEncryption;
+
/// Errors generated by KMS operations
#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Clone)]
pub enum KmsError {
@@ -82,6 +83,12 @@ impl From<ring::error::Unspecified> for KmsError {
}
}
+impl From<base64::DecodeError> 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;
@@ -112,11 +119,17 @@ pub trait KmsProvider {
fn decrypt_dek(&self, encrypted_dek: &EncryptedDEK) -> Result<PlaintextDEK, KmsError>;
}
-#[cfg(feature = "kms")]
+#[cfg(feature = "gcpkms")]
+mod gcpkms;
+
+#[cfg(feature = "gcpkms")]
+pub use kms::gcpkms::inner::GcpKms;
+
+#[cfg(feature = "awskms")]
mod awskms;
-#[cfg(feature = "kms")]
-pub use kms::awskms::AwsKms;
+#[cfg(feature = "awskms")]
+pub use kms::awskms::inner::AwsKms;
/// Load the seed value for the long-term key.
///
@@ -130,7 +143,7 @@ pub use kms::awskms::AwsKms;
/// is parsed as a KMS key id and `EnvelopeEncryption::decrypt_seed` is called to obtain
/// the plaintext seed value.
///
-#[cfg(feature = "kms")]
+#[cfg(feature = "awskms")]
pub fn load_seed(config: &Box<ServerConfig>) -> Result<Vec<u8>, error::Error> {
use kms::envelope::EnvelopeEncryption;
@@ -154,7 +167,7 @@ pub fn load_seed(config: &Box<ServerConfig>) -> Result<Vec<u8>, error::Error> {
/// The KMS feature was disabled in this build of Roughenough. The only supported `key_protection`
/// value is `plaintext`. Any other value is an error.
///
-#[cfg(not(feature = "kms"))]
+#[cfg(not(feature = "awskms"))]
pub fn load_seed(config: &Box<ServerConfig>) -> Result<Vec<u8>, error::Error> {
match config.key_protection() {
KeyProtection::Plaintext => Ok(config.seed()),
diff --git a/src/lib.rs b/src/lib.rs
index 0d003b2..3c3453e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -48,7 +48,7 @@
//! # Server
//!
//! 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.
+//! ways it can be configured, see [`ServerConfig`](config/trait.ServerConfig.html) for details.
//!
//! To run the server with a config file:
//!
@@ -57,6 +57,7 @@
//! ```
//!
+extern crate base64;
extern crate byteorder;
extern crate core;
extern crate time;