use chrono::*;
use http_headers::*;
use hyper::header::Headers;
use openssl::hash::{MessageDigest, hash2};
use openssl::sign::Signer;
use openssl::pkey::PKey;
use rustc_serialize::base64::ToBase64;
use std::fmt;
use utils::{expand_string, squeeze_path};
use authentication::BASE64_AUTH;
use failure::Error;
#[allow(unused_imports)]
use std::ascii::AsciiExt;
pub struct Auth13 {
api_version: String,
body: Option<String>,
date: String,
key: Vec<u8>,
method: String,
path: String,
userid: String,
}
impl fmt::Debug for Auth13 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Auth13")
.field("method", &self.method)
.field("userid", &self.userid)
.field("path", &self.path)
.field("body", &self.body)
.finish()
}
}
impl Auth13 {
pub fn new(
path: &str,
key: &[u8],
method: &str,
userid: &str,
api_version: &str,
body: Option<String>,
) -> Auth13 {
let dt = Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string();
let userid: String = userid.into();
let method = String::from(method).to_ascii_uppercase();
Auth13 {
api_version: api_version.into(),
body: body,
date: dt,
key: key.into(),
method: method,
path: squeeze_path(path),
userid: userid,
}
}
fn content_hash(&self) -> Result<String, Error> {
let body = expand_string(&self.body);
debug!("Content body is: {:?}", body);
let content = hash2(MessageDigest::sha256(), body.as_bytes())?.to_base64(BASE64_AUTH);
debug!("Content hash is: {:?}", content);
Ok(content)
}
fn canonical_request(&self) -> Result<String, Error> {
let cr = format!(
"Method:{}\nPath:{}\nX-Ops-Content-Hash:{}\n\
X-Ops-Sign:version=1.3\nX-Ops-Timestamp:{}\n\
X-Ops-UserId:{}\nX-Ops-Server-API-Version:{}",
&self.method,
&self.path,
try!(self.content_hash()),
self.date,
&self.userid,
&self.api_version
);
debug!("Canonical Request is: {:?}", cr);
Ok(cr)
}
fn signed_request(&self) -> Result<String, Error> {
let key = PKey::private_key_from_pem(self.key.as_slice())?;
let cr = self.canonical_request()?;
let cr = cr.as_bytes();
let mut signer = Signer::new(MessageDigest::sha256(), &key)?;
signer.update(cr).unwrap();
let result = signer.sign_to_vec()?;
let result = result.to_base64(BASE64_AUTH);
debug!("base64 encoded result is {:?}", result);
Ok(result)
}
pub fn build(self, headers: &mut Headers) -> Result<(), Error> {
let hsh = self.content_hash()?;
headers.set(OpsContentHash(hsh));
headers.set(OpsSign(String::from("algorithm=sha256;version=1.3")));
headers.set(OpsTimestamp(self.date.clone()));
headers.set(OpsUserId(self.userid.clone()));
let enc = try!(self.signed_request());
let mut i = 1;
for h in enc.split('\n') {
let key = format!("X-Ops-Authorization-{}", i);
headers.set_raw(key, vec![h.as_bytes().to_vec()]);
i += 1;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::Auth13;
use openssl::hash::MessageDigest;
use openssl::sign::Verifier;
use openssl::pkey::PKey;
use rustc_serialize::base64::FromBase64;
use std::fs::File;
use std::io::Read;
const PATH: &'static str = "/organizations/clownco";
const BODY: &'static str = "Spec Body";
const USER: &'static str = "spec-user";
const DT: &'static str = "2009-01-01T12:00:00Z";
const PRIVATE_KEY: &'static str = "fixtures/spec-user.pem";
fn get_key_data() -> Vec<u8> {
let mut key = String::new();
File::open(PRIVATE_KEY)
.and_then(|mut fh| fh.read_to_string(&mut key))
.unwrap();
key.into_bytes()
}
#[test]
fn test_canonical_request() {
let auth = Auth13 {
api_version: String::from("1"),
body: Some(String::from(BODY)),
date: String::from(DT),
key: get_key_data(),
method: String::from("POST"),
path: String::from(PATH),
userid: String::from(USER),
};
assert_eq!(
auth.canonical_request().unwrap(),
"Method:POST\nPath:/organizations/clownco\nX-Ops-Content-Hash:\
hDlKNZhIhgso3Fs0S0pZwJ0xyBWtR1RBaeHs1DrzOho=\nX-Ops-Sign:version=1.\
3\nX-Ops-Timestamp:2009-01-01T12:00:00Z\nX-Ops-UserId:\
spec-user\nX-Ops-Server-API-Version:1"
)
}
#[test]
fn test_signed_request() {
let auth = Auth13 {
api_version: String::from("1"),
body: Some(String::from(BODY)),
date: String::from(DT),
key: get_key_data(),
method: String::from("POST"),
path: String::from(PATH),
userid: String::from(USER),
};
let sig = &auth.signed_request().unwrap();
let req = &auth.canonical_request().unwrap();
let sig_raw = sig.clone().from_base64().unwrap();
let mut key: Vec<u8> = vec![];
let mut fh = File::open(PRIVATE_KEY).unwrap();
fh.read_to_end(&mut key).unwrap();
let key = PKey::private_key_from_pem(key.as_slice()).unwrap();
let mut ver = Verifier::new(MessageDigest::sha256(), &key).unwrap();
ver.update(req.as_bytes()).unwrap();
assert!(ver.verify(sig_raw.as_slice()).unwrap());
assert_eq!(
sig,
"FZOmXAyOBAZQV/uw188iBljBJXOm+m8xQ/8KTGLkgGwZNcRFxk1m953XjE3W\n\
VGy1dFT76KeaNWmPCNtDmprfH2na5UZFtfLIKrPv7xm80V+lzEzTd9WBwsfP\n\
42dZ9N+V9I5SVfcL/lWrrlpdybfceJC5jOcP5tzfJXWUITwb6Z3Erg3DU3Uh\n\
H9h9E0qWlYGqmiNCVrBnpe6Si1gU/Jl+rXlRSNbLJ4GlArAPuL976iTYJTzE\n\
MmbLUIm3JRYi00Yb01IUCCKdI90vUq1HHNtlTEu93YZfQaJwRxXlGkCNwIJe\n\
fy49QzaCIEu1XiOx5Jn+4GmkrZch/RrK9VzQWXgs+w=="
)
}
}