use std::{collections::BTreeMap, sync::Arc};
use base64::encode;
use ring::{
hmac::{self, SigningKey},
rand,
signature::{self, RSAKeyPair},
};
use crate::{error::CreationError, ShaSize, SignatureAlgorithm};
pub enum CreateKey {
RSA(RSAKeyPair, ShaSize),
HMAC(SigningKey, ShaSize),
}
impl CreateKey {
#[cfg(feature = "openssl")]
pub fn from_openssl_rsa(
rsa: &openssl::rsa::Rsa<openssl::pkey::Private>,
size: ShaSize,
) -> Result<Self, failure::Error> {
let vec = rsa.private_key_to_der()?;
let input = untrusted::Input::from(&vec);
Ok(CreateKey::RSA(RSAKeyPair::from_der(input)?, size))
}
pub fn rsa(key: RSAKeyPair, size: ShaSize) -> Self {
CreateKey::RSA(key, size)
}
pub fn hmac(key: SigningKey, size: ShaSize) -> Self {
CreateKey::HMAC(key, size)
}
}
pub struct HttpSignature {
key_id: String,
key: CreateKey,
headers: BTreeMap<String, Vec<String>>,
}
impl HttpSignature {
pub fn new(
key_id: String,
key: CreateKey,
headers: BTreeMap<String, Vec<String>>,
) -> Result<Self, CreationError> {
let key = key.into();
if headers.is_empty() {
return Err(CreationError::NoHeaders);
}
Ok(HttpSignature {
key_id,
key,
headers,
})
}
pub fn key_id(&self) -> &str {
&self.key_id
}
pub fn headers(&self) -> &BTreeMap<String, Vec<String>> {
&self.headers
}
pub fn authorization_header(self) -> Result<String, CreationError> {
Ok(self.signature()?.authorization())
}
pub fn signature_header(self) -> Result<String, CreationError> {
Ok(self.signature()?.signature())
}
pub fn signature(self) -> Result<Signature, CreationError> {
let signing_string: SigningString = self.into();
Signature::from_signing_string(signing_string)
}
}
pub struct SigningString {
key_id: String,
key: CreateKey,
headers: Vec<String>,
pub signing_string: String,
}
impl From<HttpSignature> for SigningString {
fn from(http_signature: HttpSignature) -> Self {
let (header_keys, signing_vec): (Vec<_>, Vec<_>) = http_signature
.headers
.iter()
.map(|(header, values)| {
(
header.to_lowercase(),
format!("{}: {}", header.to_lowercase(), values.join(", ")),
)
})
.unzip();
SigningString {
key_id: http_signature.key_id,
key: http_signature.key,
headers: header_keys,
signing_string: signing_vec.join("\n"),
}
}
}
#[derive(Clone, Debug)]
pub struct Signature {
sig: String,
key_id: String,
headers: Vec<String>,
algorithm: SignatureAlgorithm,
}
impl Signature {
pub fn authorization(self) -> String {
format!("Signature {}", self.header())
}
pub fn signature(self) -> String {
self.header()
}
fn header(self) -> String {
let alg: &str = self.algorithm.into();
format!(
"Signature keyId=\"{}\",algorithm=\"{}\",headers=\"{}\",signature=\"{}\"",
self.key_id,
alg,
self.headers.join(" "),
self.sig,
)
}
fn rsa(key: RSAKeyPair, size: ShaSize, signing_string: &[u8]) -> Result<String, CreationError> {
let key_pair = Arc::new(key);
let mut signing_state =
signature::RSASigningState::new(key_pair).map_err(|_| CreationError::SigningError)?;
let rng = rand::SystemRandom::new();
let mut signature = vec![0; signing_state.key_pair().public_modulus_len()];
signing_state
.sign(
size.rsa_algorithm(),
&rng,
signing_string,
signature.as_mut_slice(),
)
.map_err(|_| CreationError::SigningError)?;
Ok(encode(signature.as_slice()))
}
fn hmac(hmac_key: SigningKey, signing_string: &[u8]) -> Result<String, CreationError> {
let signature = hmac::sign(&hmac_key, signing_string);
Ok(encode(signature.as_ref()))
}
fn from_signing_string(signing_string: SigningString) -> Result<Self, CreationError> {
Ok(match signing_string.key {
CreateKey::RSA(rsa_key_pair, sha_size) => Signature {
sig: Signature::rsa(
rsa_key_pair,
sha_size,
signing_string.signing_string.as_ref(),
)?,
key_id: signing_string.key_id,
headers: signing_string.headers,
algorithm: SignatureAlgorithm::RSA(sha_size),
},
CreateKey::HMAC(signing_key, sha_size) => Signature {
sig: Signature::hmac(signing_key, signing_string.signing_string.as_ref())?,
key_id: signing_string.key_id,
headers: signing_string.headers,
algorithm: SignatureAlgorithm::HMAC(sha_size),
},
})
}
}