// 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 google_cloudkms1 as cloudkms1; extern crate hyper; extern crate hyper_rustls; extern crate yup_oauth2 as oauth2; use std::fmt; use std::env; use std::fmt::Formatter; use std::str::FromStr; use std::result::Result; use std::default::Default; use std::error::Error; use std::path::Path; use std::time::Duration; use self::cloudkms1::CloudKMS; use self::cloudkms1::{ DecryptRequest, EncryptRequest, Error as CloudKmsError, Result as CloudKmsResult, }; use self::hyper::net::HttpsConnector; use self::hyper::header::Headers; use self::hyper::status::StatusCode; use self::hyper_rustls::TlsClient; use self::oauth2::{service_account_key_from_file, ServiceAccountAccess, ServiceAccountKey}; 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 { let svc_acct = load_gcp_credential()?; Ok(GcpKms { key_resource_id: resource_id.to_string(), service_account: svc_acct }) } fn new_hub(&self) -> CloudKMS> { 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())); CloudKMS::new(client2, access) } fn pretty_http_error(&self, resp: &hyper::client::Response) -> KmsError { let code = resp.status; let url = &resp.url; KmsError::OperationFailed(format!("Response {} from {}", code, url)) } } impl KmsProvider for GcpKms { fn encrypt_dek(&self, plaintext_dek: &PlaintextDEK) -> Result { let mut request = EncryptRequest::default(); request.plaintext = Some(base64::encode(plaintext_dek)); request.additional_authenticated_data = Some(base64::encode(AD)); let hub = self.new_hub(); 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(self.pretty_http_error(&http_resp)) } } Err(e) => Err(KmsError::OperationFailed(format!("encrypt_dek() {:?}", e))), } } fn decrypt_dek(&self, encrypted_dek: &EncryptedDEK) -> Result { let mut request = DecryptRequest::default(); request.ciphertext = Some(base64::encode(encrypted_dek)); request.additional_authenticated_data = Some(base64::encode(AD)); let hub = self.new_hub(); let result = hub .projects() .locations_key_rings_crypto_keys_decrypt(request, &self.key_resource_id) .doit(); match result { Ok((http_resp, enc_resp)) => { if http_resp.status == StatusCode::Ok { let plaintext = enc_resp.plaintext.unwrap(); let ct = base64::decode(&plaintext)?; Ok(ct) } else { Err(self.pretty_http_error(&http_resp)) } } Err(e) => Err(KmsError::OperationFailed(format!("decrypt_dek() {:?}", e))) } } } /// Minimal implementation of Application Default Credentials. /// https://cloud.google.com/docs/authentication/production /// /// 1. Look for GOOGLE_APPLICATION_CREDENTIALS and load service account /// credentials if found. /// 2. If not, error fn load_gcp_credential() -> Result { if let Ok(gac) = env::var("GOOGLE_APPLICATION_CREDENTIALS") { if Path::new(&gac).exists() { match oauth2::service_account_key_from_file(&gac) { Ok(svc_acct_key) => return Ok(svc_acct_key), Err(e) => { return Err(KmsError::InvalidConfiguration( format!("Can't load service account credential '{}': {:?}", gac, e))) } } } else { return Err(KmsError::InvalidConfiguration( format!("GOOGLE_APPLICATION_CREDENTIALS='{}' does not exist", gac))) } } // TODO: call to metadata service to get default credential from // http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token panic!("Failed to load service account credential. Is GOOGLE_APPLICATION_CREDENTIALS set?"); } }