#![feature(try_from)]
#[cfg(feature = "use_hyper")]
extern crate hyper;
#[cfg(feature = "use_reqwest")]
extern crate reqwest;
#[cfg(feature = "use_rocket")]
extern crate rocket;
extern crate ring;
extern crate untrusted;
extern crate base64;
use std::convert::TryFrom;
#[cfg(feature = "use_hyper")]
mod use_hyper_client;
#[cfg(feature = "use_hyper")]
mod use_hyper_server;
#[cfg(feature = "use_reqwest")]
mod use_reqwest;
#[cfg(feature = "use_rocket")]
mod use_rocket;
mod create;
mod verify;
mod error;
use error::DecodeError;
pub use create::{AsHttpSignature, WithHttpSignature, HttpSignature};
pub use verify::{SignedHeader, VerifyHeader, GetKey};
pub use error::Error;
pub const REQUEST_TARGET: &'static str = "(request-target)";
#[derive(Debug, Clone)]
pub enum ShaSize {
TwoFiftySix,
ThreeEightyFour,
FiveTwelve,
}
#[derive(Debug, Clone)]
pub enum SignatureAlgorithm {
RSA(ShaSize),
HMAC(ShaSize),
}
impl<'a> TryFrom<&'a str> for SignatureAlgorithm {
type Error = DecodeError;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
match s {
"rsa-sha256" => Ok(SignatureAlgorithm::RSA(ShaSize::TwoFiftySix)),
"rsa-sha384" => Ok(SignatureAlgorithm::RSA(ShaSize::ThreeEightyFour)),
"rsa-sha512" => Ok(SignatureAlgorithm::RSA(ShaSize::FiveTwelve)),
"hmac-sha256" => Ok(SignatureAlgorithm::HMAC(ShaSize::TwoFiftySix)),
"hmac-sha384" => Ok(SignatureAlgorithm::HMAC(ShaSize::ThreeEightyFour)),
"hmac-sha512" => Ok(SignatureAlgorithm::HMAC(ShaSize::FiveTwelve)),
e => Err(DecodeError::InvalidAlgorithm(e.into())),
}
}
}
impl From<SignatureAlgorithm> for &'static str {
fn from(alg: SignatureAlgorithm) -> Self {
match alg {
SignatureAlgorithm::RSA(size) => {
match size {
ShaSize::TwoFiftySix => "rsa-sha256",
ShaSize::ThreeEightyFour => "rsa-sha384",
ShaSize::FiveTwelve => "rsa-sha512",
}
}
SignatureAlgorithm::HMAC(size) => {
match size {
ShaSize::TwoFiftySix => "hmac-sha256",
ShaSize::ThreeEightyFour => "hmac-sha384",
ShaSize::FiveTwelve => "hmac-sha512",
}
}
}
}
}
#[cfg(test)]
mod tests {
use ring::{digest, hmac, rand};
use std::collections::HashMap;
use std::io::Cursor;
use std::fs::File;
use super::SignatureAlgorithm;
use super::ShaSize;
use super::REQUEST_TARGET;
use create::HttpSignature;
use verify::GetKey;
use verify::SignedHeader;
use error::VerificationError;
struct HmacKeyGetter {
key: Vec<u8>,
}
impl GetKey for HmacKeyGetter {
type Key = Cursor<Vec<u8>>;
type Error = VerificationError;
fn get_key(self, _: &str) -> Result<Self::Key, Self::Error> {
Ok(Cursor::new(self.key))
}
}
struct RsaKeyGetter {
key: File,
}
impl GetKey for RsaKeyGetter {
type Key = File;
type Error = VerificationError;
fn get_key(self, _: &str) -> Result<Self::Key, Self::Error> {
Ok(self.key)
}
}
#[test]
fn hmac_256_can_sign_and_verify() {
hmac_can_sign_and_verify(ShaSize::TwoFiftySix, &digest::SHA256);
}
#[test]
fn hmac_384_can_sign_and_verify() {
hmac_can_sign_and_verify(ShaSize::ThreeEightyFour, &digest::SHA384);
}
#[test]
fn hmac_512_can_sign_and_verify() {
hmac_can_sign_and_verify(ShaSize::FiveTwelve, &digest::SHA512);
}
#[test]
fn rsa_256_can_sign_and_verify() {
rsa_can_sign_and_verify(ShaSize::TwoFiftySix);
}
#[test]
fn rsa_384_can_sign_and_verify() {
rsa_can_sign_and_verify(ShaSize::ThreeEightyFour);
}
#[test]
fn rsa_512_can_sign_and_verify() {
rsa_can_sign_and_verify(ShaSize::FiveTwelve);
}
fn hmac_can_sign_and_verify(sha_size: ShaSize, digest: &'static digest::Algorithm) {
let rng = rand::SystemRandom::new();
let len = hmac::recommended_key_len(digest);
let mut key_vec: Vec<u8> = Vec::new();
for _ in 0..len {
key_vec.push(0);
}
let _ = hmac::SigningKey::generate_serializable(digest, &rng, key_vec.as_mut_slice())
.unwrap();
let key_getter = HmacKeyGetter { key: key_vec.clone() };
let method = "GET";
let path = "/test";
let query = "key=value";
let mut headers_one: HashMap<String, Vec<String>> = HashMap::new();
headers_one.insert("Accept".into(), vec!["application/json".into()]);
headers_one.insert(
REQUEST_TARGET.into(),
vec![format!("{} {}?{}", method.to_lowercase(), path, query)],
);
let mut headers_two = Vec::new();
headers_two.push(("Accept".into(), "application/json".into()));
let algorithm = SignatureAlgorithm::HMAC(sha_size);
let key_id = "1".into();
let auth_header = HttpSignature::new(key_id, Cursor::new(key_vec), algorithm, headers_one)
.unwrap()
.authorization_header()
.unwrap();
let auth_header = SignedHeader::new(&auth_header).unwrap();
auth_header
.verify(&headers_two, method, path, Some(query), key_getter)
.unwrap();
}
fn rsa_can_sign_and_verify(sha_size: ShaSize) {
let priv_key = File::open("test/assets/private.der").unwrap();
let pub_key = File::open("test/assets/public.der").unwrap();
let key_getter = RsaKeyGetter { key: pub_key };
let method = "GET";
let path = "/test";
let query = "key=value";
let mut headers_one: HashMap<String, Vec<String>> = HashMap::new();
headers_one.insert("Accept".into(), vec!["application/json".into()]);
headers_one.insert(
REQUEST_TARGET.into(),
vec![format!("{} {}?{}", method.to_lowercase(), path, query)],
);
let mut headers_two = Vec::new();
headers_two.push(("Accept".into(), "application/json".into()));
let algorithm = SignatureAlgorithm::RSA(sha_size);
let key_id = "1".into();
let auth_header = HttpSignature::new(key_id, priv_key, algorithm, headers_one)
.unwrap()
.signature_header()
.unwrap();
let auth_header = SignedHeader::new(&auth_header).unwrap();
auth_header
.verify(&headers_two, method, path, Some(query), key_getter)
.unwrap();
}
}