use std::collections::BTreeMap;
use crate::{
create::{CreateKey, HttpSignature},
error::{CreationError, Error},
prelude::*,
REQUEST_TARGET,
};
use reqwest::{header::HeaderValue, Request as ReqwestRequest};
impl AsHttpSignature for ReqwestRequest {
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.url().query() {
format!(
"{} {}?{}",
self.method().as_ref().to_lowercase(),
self.url().path(),
query
)
} else {
format!(
"{} {}",
self.method().as_ref().to_lowercase(),
self.url().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 WithHttpSignature for ReqwestRequest {
fn with_authorization_header(
&mut self,
key_id: String,
key: CreateKey,
) -> Result<&mut Self, Error> {
use reqwest::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 reqwest::{
header::{HeaderMap, CONTENT_LENGTH, CONTENT_TYPE, DATE, HOST},
Client, Request,
};
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 = "http://example.org/foo";
let req = Client::new().post(uri).build().unwrap();
test_request(req, "(request-target): post /foo");
}
#[test]
fn full_test() {
let uri = "http://example.org/foo";
let mut headers = HeaderMap::new();
headers.insert(HOST, "example.org".parse().unwrap());
headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
headers.insert(
"Digest",
"SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE="
.parse()
.unwrap(),
);
headers.insert(DATE, "Tue, 07 Jun 2014 20:51:35 GMT".parse().unwrap());
headers.insert(CONTENT_LENGTH, "18".parse().unwrap());
let req = Client::new()
.post(uri)
.headers(headers)
.body(r#"{"hello": "world"}"#)
.build()
.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(req: Request, 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))
.unwrap();
let signing_string: SigningString = http_sig.into();
assert_eq!(signing_string.signing_string, s);
}
}