#[cfg(feature = "use_actix_web")]
pub mod use_actix_web_client;
#[cfg(feature = "use_actix_web")]
pub mod use_actix_web_server;
#[cfg(feature = "use_hyper")]
pub mod use_hyper_client;
#[cfg(feature = "use_hyper")]
pub mod use_hyper_server;
#[cfg(feature = "use_reqwest")]
pub mod use_reqwest;
#[cfg(feature = "use_rocket")]
pub mod use_rocket;
mod create;
mod error;
pub mod prelude;
mod verify;
use std::str::FromStr;
use ring::{
digest::{Algorithm, SHA256, SHA384, SHA512},
signature::{
RSAEncoding, VerificationAlgorithm, RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_2048_8192_SHA384,
RSA_PKCS1_2048_8192_SHA512, RSA_PKCS1_SHA256, RSA_PKCS1_SHA384, RSA_PKCS1_SHA512,
},
};
use self::error::DecodeError;
pub use self::{
create::{CreateKey, HttpSignature},
error::Error,
verify::{SignedHeader, VerifyKey},
};
pub const REQUEST_TARGET: &str = "(request-target)";
#[derive(Clone, Copy, Debug)]
pub enum ShaSize {
SHA256,
SHA384,
SHA512,
}
impl ShaSize {
pub fn hmac_algorithm(self) -> &'static Algorithm {
match self {
ShaSize::SHA256 => &SHA256,
ShaSize::SHA384 => &SHA384,
ShaSize::SHA512 => &SHA512,
}
}
pub fn rsa_algorithm(self) -> &'static dyn RSAEncoding {
match self {
ShaSize::SHA256 => &RSA_PKCS1_SHA256,
ShaSize::SHA384 => &RSA_PKCS1_SHA384,
ShaSize::SHA512 => &RSA_PKCS1_SHA512,
}
}
pub fn verification_algorithm(self) -> &'static dyn VerificationAlgorithm {
match self {
ShaSize::SHA256 => &RSA_PKCS1_2048_8192_SHA256,
ShaSize::SHA384 => &RSA_PKCS1_2048_8192_SHA384,
ShaSize::SHA512 => &RSA_PKCS1_2048_8192_SHA512,
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum SignatureAlgorithm {
RSA(ShaSize),
HMAC(ShaSize),
}
impl FromStr for SignatureAlgorithm {
type Err = DecodeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"rsa-sha256" => Ok(SignatureAlgorithm::RSA(ShaSize::SHA256)),
"rsa-sha384" => Ok(SignatureAlgorithm::RSA(ShaSize::SHA384)),
"rsa-sha512" => Ok(SignatureAlgorithm::RSA(ShaSize::SHA512)),
"hmac-sha256" => Ok(SignatureAlgorithm::HMAC(ShaSize::SHA256)),
"hmac-sha384" => Ok(SignatureAlgorithm::HMAC(ShaSize::SHA384)),
"hmac-sha512" => Ok(SignatureAlgorithm::HMAC(ShaSize::SHA512)),
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::SHA256 => "rsa-sha256",
ShaSize::SHA384 => "rsa-sha384",
ShaSize::SHA512 => "rsa-sha512",
},
SignatureAlgorithm::HMAC(size) => match size {
ShaSize::SHA256 => "hmac-sha256",
ShaSize::SHA384 => "hmac-sha384",
ShaSize::SHA512 => "hmac-sha512",
},
}
}
}
#[cfg(test)]
mod tests {
use ring::{
hmac::{self, SigningKey},
rand,
signature::RSAKeyPair,
};
use untrusted::Input;
use std::{collections::BTreeMap, fs::File};
use crate::{
create::{CreateKey, HttpSignature},
verify::{SignedHeader, VerifyKey},
ShaSize, REQUEST_TARGET,
};
#[test]
fn hmac_256_can_sign_and_verify() {
hmac_can_sign_and_verify(ShaSize::SHA256);
}
#[test]
fn hmac_384_can_sign_and_verify() {
hmac_can_sign_and_verify(ShaSize::SHA384);
}
#[test]
fn hmac_512_can_sign_and_verify() {
hmac_can_sign_and_verify(ShaSize::SHA512);
}
#[test]
fn rsa_256_can_sign_and_verify() {
rsa_can_sign_and_verify(ShaSize::SHA256);
}
#[test]
fn rsa_384_can_sign_and_verify() {
rsa_can_sign_and_verify(ShaSize::SHA384);
}
#[test]
fn rsa_512_can_sign_and_verify() {
rsa_can_sign_and_verify(ShaSize::SHA512);
}
fn hmac_can_sign_and_verify(sha_size: ShaSize) {
let algorithm = sha_size.hmac_algorithm();
let rng = rand::SystemRandom::new();
let len = hmac::recommended_key_len(&algorithm);
let mut key_vec: Vec<u8> = Vec::new();
for _ in 0..len {
key_vec.push(0);
}
let _ =
SigningKey::generate_serializable(&algorithm, &rng, key_vec.as_mut_slice()).unwrap();
let key = SigningKey::new(&algorithm, key_vec.as_ref());
let creation_key = CreateKey::hmac(key, sha_size);
let method = "GET";
let path = "/test";
let query = "key=value";
let mut headers_one: BTreeMap<String, Vec<String>> = BTreeMap::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 key_id = "1".into();
let auth_header = HttpSignature::new(key_id, creation_key, headers_one)
.unwrap()
.authorization_header()
.unwrap();
let auth_header = SignedHeader::new(&auth_header).unwrap();
auth_header
.verify(
&headers_two,
method,
path,
Some(query),
VerifyKey::unchecked_from_vec(key_vec),
)
.unwrap();
}
fn rsa_can_sign_and_verify(sha_size: ShaSize) {
use std::io::Read;
let mut priv_key = File::open("tests/assets/private.der").unwrap();
let mut priv_key_vec = Vec::new();
priv_key.read_to_end(&mut priv_key_vec).unwrap();
let priv_key_input = Input::from(&priv_key_vec);
let priv_key = RSAKeyPair::from_der(priv_key_input).unwrap();
let priv_creation_key = CreateKey::rsa(priv_key, sha_size);
let mut pub_key = File::open("tests/assets/public.der").unwrap();
let mut pub_key_vec = Vec::new();
pub_key.read_to_end(&mut pub_key_vec).unwrap();
let method = "GET";
let path = "/test";
let query = "key=value";
let mut headers_one: BTreeMap<String, Vec<String>> = BTreeMap::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 key_id = "1".into();
let auth_header = HttpSignature::new(key_id, priv_creation_key, headers_one)
.unwrap()
.signature_header()
.unwrap();
let auth_header = SignedHeader::new(&auth_header).unwrap();
auth_header
.verify(
&headers_two,
method,
path,
Some(query),
VerifyKey::unchecked_from_vec(pub_key_vec),
)
.unwrap();
}
}