use std::collections::BTreeMap;
use crate::{
create::{CreateKey, HttpSignature},
error::{CreationError, Error},
prelude::*,
REQUEST_TARGET,
};
use hyper::{header::HeaderValue, Request as HyperRequest};
impl<B> AsHttpSignature for HyperRequest<B> {
fn as_http_signature<'a>(
&self,
key_id: String,
key: CreateKey,
) -> Result<HttpSignature, Error> {
let mut headers = BTreeMap::new();
headers.insert(
REQUEST_TARGET.into(),
vec![if let Some(query) = self.uri().query() {
format!(
"{} {}?{}",
self.method().as_ref().to_lowercase(),
self.uri().path(),
query
)
} else {
format!(
"{} {}",
self.method().as_ref().to_lowercase(),
self.uri().path()
)
}],
);
let headers =
self.headers()
.iter()
.fold(headers, |mut acc, (header_name, header_value)| {
let _ = header_value.to_str().map(|header_value| {
acc.entry(header_name.as_str().to_string())
.or_insert_with(Vec::new)
.push(header_value.to_string())
});
acc
});
HttpSignature::new(key_id, key, headers).map_err(Error::from)
}
}
impl<B> WithHttpSignature for HyperRequest<B> {
fn with_authorization_header(
&mut self,
key_id: String,
key: CreateKey,
) -> Result<&mut Self, Error> {
use hyper::header::AUTHORIZATION;
let auth_header = self.authorization_header(key_id, key)?;
let header = HeaderValue::from_str(&auth_header).or(Err(CreationError::NoHeaders))?;
self.headers_mut().insert(AUTHORIZATION, header);
Ok(self)
}
fn with_signature_header(
&mut self,
key_id: String,
key: CreateKey,
) -> Result<&mut Self, Error> {
let sig_header = self.signature_header(key_id, key)?;
let header = HeaderValue::from_str(&sig_header).or(Err(CreationError::NoHeaders))?;
self.headers_mut().insert("Signature", header);
Ok(self)
}
}
#[cfg(test)]
mod tests {
use std::{fs::File, io::Read};
use hyper::{
header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE, DATE, HOST},
Request, Uri,
};
use ring::signature::RSAKeyPair;
use untrusted::Input;
use crate::{
create::{CreateKey, SigningString},
prelude::*,
ShaSize,
};
const KEY_ID: &'static str = "rsa-key-1";
const PRIVATE_KEY_PATH: &'static str = "tests/assets/private.der";
#[test]
fn min_test() {
let uri: Uri = "http://example.org/foo".parse().unwrap();
let req = Request::post(uri).body(()).unwrap();
test_request(req, "(request-target): post /foo");
}
#[test]
fn full_test() {
let uri: Uri = "http://example.org/foo".parse().unwrap();
let body = r#"{"hello": "world"}"#;
let mut req = Request::post(uri).body(body).unwrap();
req.headers_mut()
.insert(HOST, HeaderValue::from_str("example.org").unwrap());
req.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_str("application/json").unwrap(),
);
req.headers_mut().insert(
"Digest",
HeaderValue::from_str("SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=").unwrap(),
);
req.headers_mut().insert(
DATE,
HeaderValue::from_str("Tue, 07 Jun 2014 20:51:35 GMT").unwrap(),
);
req.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_str("18").unwrap());
test_request(
req,
"(request-target): post /foo
content-length: 18
content-type: application/json
date: Tue, 07 Jun 2014 20:51:35 GMT
digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=
host: example.org",
)
}
fn test_request<B>(req: Request<B>, s: &str) {
let mut key = File::open(PRIVATE_KEY_PATH).unwrap();
let mut key_vec = Vec::new();
key.read_to_end(&mut key_vec).unwrap();
let key_input = Input::from(&key_vec);
let key = RSAKeyPair::from_der(key_input).unwrap();
let http_sig = req
.as_http_signature(KEY_ID.into(), CreateKey::rsa(key, ShaSize::SHA256).into())
.unwrap();
let signing_string: SigningString = http_sig.into();
assert_eq!(signing_string.signing_string, s);
}
}