diff --git a/.travis.yml b/.travis.yml index 127bf6a..f18b345 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ script: - travis-cargo test - travis-cargo build -- --manifest-path examples/drive_example/Cargo.toml - travis-cargo build -- --manifest-path examples/service_account/Cargo.toml + - travis-cargo test -- --no-default-features --features no-openssl after_success: - "[[ $TRAVIS_OS_NAME = linux ]] && travis-cargo --only stable coveralls" env: diff --git a/Cargo.toml b/Cargo.toml index ce4ae99..262d388 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,12 +17,17 @@ hyper = "0.10.2" hyper-native-tls = "0.3" itertools = "0.7" log = "0.3" -openssl = "0.10" +openssl = {version = "0.10", optional = true} +rustls = {version = "0.14", optional = true} serde = "1.0" serde_json = "1.0" serde_derive = "1.0" url = "1" +[features] +default = ["openssl"] +no-openssl = ["rustls"] + [dev-dependencies] getopts = "0.2" open = "1.1" diff --git a/src/service_account.rs b/src/service_account.rs index 1f0cfb7..2cfbade 100644 --- a/src/service_account.rs +++ b/src/service_account.rs @@ -25,10 +25,23 @@ use crate::types::{StringError, Token}; use hyper::header; use url::form_urlencoded; -use openssl::sign::Signer; -use openssl::hash::MessageDigest; -use openssl::pkey::{PKey, Private}; -use openssl::rsa::Padding; +#[cfg(not(feature = "no-openssl"))] +use openssl::{ + sign::Signer, + hash::MessageDigest, + pkey::{PKey, Private}, + rsa::Padding, +}; + +#[cfg(feature = "no-openssl")] +use rustls::{ + self, + PrivateKey, + sign::{self, SigningKey}, + internal::pemfile, +}; +#[cfg(feature = "no-openssl")] +use std::io; use base64; use chrono; @@ -43,11 +56,30 @@ fn encode_base64>(s: T) -> String { base64::encode_config(s.as_ref(), base64::URL_SAFE) } +#[cfg(not(feature = "no-openssl"))] fn decode_rsa_key(pem_pkcs8: &str) -> Result, Box> { let private = pem_pkcs8.to_string().replace("\\n", "\n").into_bytes(); Ok(PKey::private_key_from_pem(&private)?) } +#[cfg(feature = "no-openssl")] +fn decode_rsa_key(pem_pkcs8: &str) -> Result> { + let private = pem_pkcs8.to_string().replace("\\n", "\n").into_bytes(); + let mut private_reader: &[u8] = private.as_ref(); + let private_keys = pemfile::pkcs8_private_keys(&mut private_reader); + + if let Ok(pk) = private_keys { + if pk.len() > 0 { + Ok(pk[0].clone()) + } else { + Err(Box::new(io::Error::new(io::ErrorKind::InvalidInput, + "Not enough private keys in PEM"))) + } + } else { + Err(Box::new(io::Error::new(io::ErrorKind::InvalidInput, "Error reading key from PEM"))) + } +} + /// JSON schema of secret service account key. You can obtain the key from /// the Cloud Console at https://console.cloud.google.com/. /// @@ -101,6 +133,7 @@ impl JWT { head } + #[cfg(not(feature = "no-openssl"))] fn sign(&self, private_key: &str) -> Result> { let mut jwt_head = self.encode_claims(); let key = decode_rsa_key(private_key)?; @@ -115,6 +148,23 @@ impl JWT { Ok(jwt_head) } + + #[cfg(feature = "no-openssl")] + fn sign(&self, private_key: &str) -> Result> { + let mut jwt_head = self.encode_claims(); + let key = decode_rsa_key(private_key)?; + let signing_key = sign::RSASigningKey::new(&key) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "Couldn't initialize signer"))?; + let signer = signing_key.choose_scheme(&[rustls::SignatureScheme::RSA_PKCS1_SHA256]) + .ok_or(io::Error::new(io::ErrorKind::Other, "Couldn't choose signing scheme"))?; + let signature = signer.sign(jwt_head.as_bytes())?; + let signature_b64 = encode_base64(signature); + + jwt_head.push_str("."); + jwt_head.push_str(&signature_b64); + + Ok(jwt_head) + } } fn init_claims_from_key<'a, I, T>(key: &ServiceAccountKey, scopes: I) -> Claims