use crate::hash::HashAlgorithm;
use crate::http::http_request::{HttpRequest, HttpRequestError};
use crate::key::{PrivateKey, PublicKey};
use crate::signature::{SignatureAlgorithm, SignatureError};
use base64::engine::general_purpose;
use base64::{DecodeError, Engine as _};
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt::{self, Debug};
use std::str::FromStr;
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum HttpSignatureError {
#[error("couldn't decode base64: {source}")]
Base64Decoding { source: DecodeError },
#[error("signature is not yet valid (created: {created}, now: {now})")]
NotYetValid { created: u64, now: u64 },
#[error("signature expired (not after: {not_after}, now: {now})")]
Expired { not_after: u64, now: u64 },
#[error("signature error: {source}")]
Signature { source: SignatureError },
#[error("couldn't generate signing string: {source}")]
SigningStringGeneration { source: HttpRequestError },
#[error("signing string invalid for line `{line}`")]
InvalidSigningString { line: String },
#[error("missing required builder argument `{arg}`")]
MissingBuilderArgument { arg: &'static str },
#[error("builder requires a non empty `headers` parameter")]
BuilderEmptyHeaders,
#[error("`headers` parameter shouldn't be provided when using builder with a pre-generated signing string")]
BuilderHeadersProvidedWithPreGenerated,
#[error("required parameter is missing from http signature string: {parameter}")]
MissingRequiredParameter { parameter: &'static str },
#[error("invalid parameter: {parameter}")]
InvalidParameter { parameter: &'static str },
#[error("incompatible 'algorithm' parameter: {value:?}")]
IncompatibleAlgorithm { value: SignatureAlgorithm },
}
impl From<DecodeError> for HttpSignatureError {
fn from(e: DecodeError) -> Self {
Self::Base64Decoding { source: e }
}
}
impl From<SignatureError> for HttpSignatureError {
fn from(e: SignatureError) -> Self {
Self::Signature { source: e }
}
}
impl From<HttpRequestError> for HttpSignatureError {
fn from(e: HttpRequestError) -> Self {
Self::SigningStringGeneration { source: e }
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum Header {
Name(String),
RequestTarget,
Created,
Expires,
}
impl Header {
pub const REQUEST_TARGET_STR: &'static str = "(request-target)";
pub const CREATED_STR: &'static str = "(created)";
pub const EXPIRES_STR: &'static str = "(expires)";
pub fn new_name(mut name: String) -> Self {
name.make_ascii_lowercase();
Self::Name(name)
}
pub fn as_str(&self) -> &str {
match self {
Header::Name(header_name) => header_name.as_str(),
Header::RequestTarget => Self::REQUEST_TARGET_STR,
Header::Created => Self::CREATED_STR,
Header::Expires => Self::EXPIRES_STR,
}
}
}
impl fmt::Display for Header {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl From<&str> for Header {
fn from(s: &str) -> Self {
match s {
Self::REQUEST_TARGET_STR => Self::RequestTarget,
Self::CREATED_STR => Self::Created,
Self::EXPIRES_STR => Self::Expires,
_ => Self::new_name(s.to_owned()),
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum HttpSigAlgorithm {
Known(SignatureAlgorithm),
Custom(String),
}
impl HttpSigAlgorithm {
pub fn as_known(&self) -> Option<SignatureAlgorithm> {
if let Self::Known(algo) = self {
Some(*algo)
} else {
None
}
}
pub fn is_known(&self) -> bool {
self.as_known().is_some()
}
pub fn as_custom(&self) -> Option<&str> {
if let Self::Custom(name) = self {
Some(name.as_str())
} else {
None
}
}
pub fn is_custom(&self) -> bool {
self.as_custom().is_some()
}
pub fn as_str(&self) -> &str {
match self {
Self::Known(algo) => to_http_sig_algo_str(*algo),
Self::Custom(name) => name,
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct HttpSignature {
pub key_id: String,
pub headers: Vec<Header>,
pub created: Option<u64>,
pub expires: Option<u64>,
pub signature: String,
pub algorithm: Option<HttpSigAlgorithm>,
legacy: bool,
}
impl HttpSignature {
pub fn verifier(&self) -> HttpSignatureVerifier<'_> {
HttpSignatureVerifier {
http_signature: self,
inner: Default::default(),
}
}
pub fn to_signing_string(&self) -> String {
let mut acc = Vec::with_capacity(5);
if self.legacy {
acc.push(format!(
"{} {}={}",
HTTP_SIGNATURE_HEADER, HTTP_SIGNATURE_KEY_ID, self.key_id
));
} else {
acc.push(format!(
"{} {}=\"{}\"",
HTTP_SIGNATURE_HEADER, HTTP_SIGNATURE_KEY_ID, self.key_id
));
match &self.algorithm {
Some(HttpSigAlgorithm::Custom(algorithm_name)) => {
acc.push(format!("{HTTP_SIGNATURE_ALGORITHM}=\"{algorithm_name}\""));
}
Some(HttpSigAlgorithm::Known(algorithm)) => {
acc.push(format!(
"{}=\"{}\"",
HTTP_SIGNATURE_ALGORITHM,
to_http_sig_algo_str(*algorithm)
));
}
None => {}
}
}
if let Some(created) = self.created {
acc.push(format!("{HTTP_SIGNATURE_CREATED}={created}"));
}
if let Some(expires) = self.expires {
acc.push(format!("{HTTP_SIGNATURE_EXPIRES}={expires}"));
}
if self.legacy {
acc.push(format!(
"{}={}",
HTTP_SIGNATURE_HEADERS,
self.headers
.iter()
.map(|header| header.as_str())
.collect::<Vec<&str>>()
.join(" "),
));
acc.push(format!("{}={}", HTTP_SIGNATURE_SIGNATURE, self.signature));
} else {
acc.push(format!(
"{}=\"{}\"",
HTTP_SIGNATURE_HEADERS,
self.headers
.iter()
.map(|header| header.as_str())
.collect::<Vec<&str>>()
.join(" "),
));
acc.push(format!("{}=\"{}\"", HTTP_SIGNATURE_SIGNATURE, self.signature));
}
acc.join(",")
}
}
const HTTP_SIGNATURE_HEADER: &str = "Signature";
const HTTP_SIGNATURE_KEY_ID: &str = "keyId";
const HTTP_SIGNATURE_SIGNATURE: &str = "signature";
const HTTP_SIGNATURE_CREATED: &str = "created";
const HTTP_SIGNATURE_EXPIRES: &str = "expires";
const HTTP_SIGNATURE_HEADERS: &str = "headers";
const HTTP_SIGNATURE_ALGORITHM: &str = "algorithm";
impl fmt::Display for HttpSignature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_signing_string())
}
}
impl FromStr for HttpSignature {
type Err = HttpSignatureError;
fn from_str(http_authorization_header: &str) -> Result<Self, Self::Err> {
let items = http_authorization_header
.trim_start_matches(HTTP_SIGNATURE_HEADER)
.split(',')
.collect::<Vec<&str>>();
let mut keys = HashMap::new();
for item in items {
if let Some(index) = item.find('=') {
let (key, value) = item.split_at(index);
let value = value[1..].trim().trim_matches('"');
keys.insert(key.trim(), value.trim().to_owned());
}
}
let headers = {
if let Some(headers_str) = keys.remove(HTTP_SIGNATURE_HEADERS) {
let headers_str_vec = headers_str.split(' ').collect::<Vec<&str>>();
let mut headers = Vec::with_capacity(headers_str_vec.len());
for header_str in headers_str_vec {
headers.push(Header::from(header_str));
}
headers
} else {
vec![]
}
};
let created = if let Some(created) = keys.remove(HTTP_SIGNATURE_CREATED) {
Some(
created
.parse::<u64>()
.map_err(|_| HttpSignatureError::InvalidParameter {
parameter: HTTP_SIGNATURE_CREATED,
})?,
)
} else {
None
};
let expires = if let Some(created) = keys.remove(HTTP_SIGNATURE_EXPIRES) {
Some(
created
.parse::<u64>()
.map_err(|_| HttpSignatureError::InvalidParameter {
parameter: HTTP_SIGNATURE_EXPIRES,
})?,
)
} else {
None
};
let algorithm = keys.remove(HTTP_SIGNATURE_ALGORITHM).map(|val| {
if let Some(algo) = from_http_sig_algo_str(&val) {
HttpSigAlgorithm::Known(algo)
} else {
HttpSigAlgorithm::Custom(val)
}
});
let signature = keys
.remove(HTTP_SIGNATURE_SIGNATURE)
.ok_or(HttpSignatureError::MissingRequiredParameter {
parameter: HTTP_SIGNATURE_SIGNATURE,
})?;
let legacy = !signature.contains(['/', '+']);
Ok(HttpSignature {
key_id: keys
.remove(HTTP_SIGNATURE_KEY_ID)
.ok_or(HttpSignatureError::MissingRequiredParameter {
parameter: HTTP_SIGNATURE_KEY_ID,
})?,
headers,
created,
expires,
signature,
algorithm,
legacy,
})
}
}
macro_rules! builder_argument_missing_err {
($field:ident) => {{
const _: fn() = || {
let HttpSignatureBuilderInner { $field: _, .. };
};
HttpSignatureError::MissingBuilderArgument {
arg: stringify!($field),
}
}};
}
#[derive(Clone)]
enum SigningStringGenMethod<'a> {
PreGenerated(&'a str),
FromHttpRequest(&'a dyn HttpRequest),
}
impl Debug for SigningStringGenMethod<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SigningStringGenMethod::")?;
match self {
SigningStringGenMethod::PreGenerated(signing_string) => write!(f, "PreGenerated({signing_string})"),
SigningStringGenMethod::FromHttpRequest(_) => write!(f, "FromHttpRequest(...)"),
}
}
}
#[derive(Default, Clone, Debug)]
struct HttpSignatureBuilderInner<'a> {
key_id: Option<String>,
signature_method: Option<(&'a PrivateKey, SignatureAlgorithm)>,
created: Option<u64>,
expires: Option<u64>,
headers: Vec<Header>,
signing_string_generation: Option<SigningStringGenMethod<'a>>,
legacy: bool,
}
#[derive(Default, Clone, Debug)]
pub struct HttpSignatureBuilder<'a> {
inner: RefCell<HttpSignatureBuilderInner<'a>>,
}
impl<'a> HttpSignatureBuilder<'a> {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn key_id<S: Into<String>>(&self, key_id: S) -> &Self {
self.inner.borrow_mut().key_id = Some(key_id.into());
self
}
#[inline]
pub fn signature_method(&self, private_key: &'a PrivateKey, signature_type: SignatureAlgorithm) -> &Self {
self.inner.borrow_mut().signature_method = Some((private_key, signature_type));
self
}
#[inline]
pub fn created(&self, unix_timestamp: u64) -> &Self {
let mut inner_mut = self.inner.borrow_mut();
inner_mut.created = Some(unix_timestamp);
inner_mut.headers.push(Header::Created);
drop(inner_mut);
self
}
#[inline]
pub fn expires(&self, unix_timestamp: u64) -> &Self {
let mut inner_mut = self.inner.borrow_mut();
inner_mut.expires = Some(unix_timestamp);
inner_mut.headers.push(Header::Expires);
drop(inner_mut);
self
}
#[inline]
pub fn request_target(&self) -> &Self {
self.inner.borrow_mut().headers.push(Header::RequestTarget);
self
}
#[inline]
pub fn http_header<S: Into<String>>(&self, header: S) -> &Self {
self.inner.borrow_mut().headers.push(Header::new_name(header.into()));
self
}
#[inline]
pub fn generate_signing_string_using_http_request(&self, http_request: &'a dyn HttpRequest) -> &Self {
self.inner.borrow_mut().signing_string_generation = Some(SigningStringGenMethod::FromHttpRequest(http_request));
self
}
#[inline]
pub fn pre_generated_signing_string(&self, signing_string: &'a str) -> &Self {
self.inner.borrow_mut().signing_string_generation = Some(SigningStringGenMethod::PreGenerated(signing_string));
self
}
#[inline]
#[doc(hidden)]
pub fn legacy(&self) -> &Self {
self.inner.borrow_mut().legacy = true;
self
}
pub fn build(&self) -> Result<HttpSignature, HttpSignatureError> {
let mut inner = self.inner.borrow_mut();
let (private_key, signature_type) = {
inner
.signature_method
.take()
.ok_or(builder_argument_missing_err!(signature_method))?
};
let key_id = inner.key_id.take().ok_or(builder_argument_missing_err!(key_id))?;
let signing_string_generation = inner
.signing_string_generation
.take()
.ok_or(builder_argument_missing_err!(signing_string_generation))?;
let mut created = inner.created.take();
let mut expires = inner.expires.take();
let mut headers: Vec<Header> = inner.headers.drain(..).collect();
let legacy = inner.legacy;
drop(inner);
let signature_binary =
match signing_string_generation {
SigningStringGenMethod::PreGenerated(signing_string) => {
if !headers.is_empty() {
return Err(HttpSignatureError::BuilderHeadersProvidedWithPreGenerated);
}
for line in signing_string.lines() {
let mut split = line.split(':');
let key = split.next().expect("there is always at least one element in the split");
if let Some(value) = split.next() {
match key {
Header::REQUEST_TARGET_STR => {
headers.push(Header::RequestTarget);
}
Header::CREATED_STR => {
headers.push(Header::Created);
created = Some(value.trim().parse().map_err(|_| {
HttpSignatureError::InvalidSigningString { line: line.to_owned() }
})?);
}
Header::EXPIRES_STR => {
headers.push(Header::Expires);
expires = Some(value.trim().parse().map_err(|_| {
HttpSignatureError::InvalidSigningString { line: line.to_owned() }
})?);
}
header_name => headers.push(Header::new_name(header_name.to_owned())),
}
} else {
return Err(HttpSignatureError::InvalidSigningString { line: line.to_owned() });
}
}
signature_type.sign(signing_string.as_bytes(), private_key)?
}
SigningStringGenMethod::FromHttpRequest(http_request) => {
if headers.is_empty() {
return Err(HttpSignatureError::BuilderEmptyHeaders);
}
let mut acc = Vec::with_capacity(headers.len());
for header in &headers {
match header {
Header::Name(header_name) => {
let concatenated_values = http_request.get_header_concatenated_values(header_name)?;
if concatenated_values.is_empty() {
acc.push(format!("{}:", header_name.as_str()));
} else {
acc.push(format!("{}: {}", header_name.as_str(), concatenated_values));
}
}
Header::RequestTarget => {
acc.push(format!(
"{}: {} {}",
header.as_str(),
http_request.get_lowercased_method()?,
http_request.get_target()?
));
}
Header::Created => acc.push(format!(
"{}: {}",
header.as_str(),
created.expect("Some by builder construction")
)),
Header::Expires => acc.push(format!(
"{}: {}",
header.as_str(),
expires.expect("Some by builder construction")
)),
}
}
let signing_string = acc.join("\n");
signature_type.sign(signing_string.as_bytes(), private_key)?
}
};
Ok(HttpSignature {
key_id,
headers,
created,
expires,
signature: if legacy {
general_purpose::URL_SAFE_NO_PAD.encode(&signature_binary)
} else {
general_purpose::STANDARD.encode(&signature_binary)
},
algorithm: Some(HttpSigAlgorithm::Known(signature_type)),
legacy,
})
}
}
macro_rules! verifier_argument_missing_err {
($field:ident) => {{
const _: fn() = || {
let HttpSignatureVerifierInner { $field: _, .. };
};
HttpSignatureError::MissingBuilderArgument {
arg: stringify!($field),
}
}};
}
#[derive(Default, Clone, Debug)]
struct HttpSignatureVerifierInner<'a> {
now: Option<u64>,
leeway: u64,
signature_method: Option<(&'a PublicKey, SignatureAlgorithm)>,
signing_string_generation: Option<SigningStringGenMethod<'a>>,
}
#[derive(Clone, Debug)]
pub struct HttpSignatureVerifier<'a> {
http_signature: &'a HttpSignature,
inner: RefCell<HttpSignatureVerifierInner<'a>>,
}
impl<'a> HttpSignatureVerifier<'a> {
#[inline]
pub fn now(&self, unix_timestamp: u64) -> &Self {
self.inner.borrow_mut().now = Some(unix_timestamp);
self
}
#[inline]
pub fn leeway(&self, leeway: u64) -> &Self {
self.inner.borrow_mut().leeway = leeway;
self
}
#[inline]
pub fn signature_method(&self, public_key: &'a PublicKey, signature_type: SignatureAlgorithm) -> &Self {
self.inner.borrow_mut().signature_method = Some((public_key, signature_type));
self
}
#[inline]
pub fn generate_signing_string_using_http_request(&self, http_request: &'a dyn HttpRequest) -> &Self {
self.inner.borrow_mut().signing_string_generation = Some(SigningStringGenMethod::FromHttpRequest(http_request));
self
}
#[inline]
pub fn pre_generated_signing_string(&self, signing_string: &'a str) -> &Self {
self.inner.borrow_mut().signing_string_generation = Some(SigningStringGenMethod::PreGenerated(signing_string));
self
}
pub fn verify(&self) -> Result<(), HttpSignatureError> {
let mut inner = self.inner.borrow_mut();
let (public_key, signature_type) = {
inner
.signature_method
.take()
.ok_or(verifier_argument_missing_err!(signature_method))?
};
if let Some(HttpSigAlgorithm::Known(http_sig_algo)) = self.http_signature.algorithm {
if http_sig_algo != signature_type || !is_algo_compatible_with_key(http_sig_algo, public_key) {
return Err(HttpSignatureError::IncompatibleAlgorithm { value: http_sig_algo });
}
}
let signing_string_generation = inner
.signing_string_generation
.take()
.ok_or(verifier_argument_missing_err!(signing_string_generation))?;
if let Some(expires) = self.http_signature.expires {
let now = inner.now.ok_or(verifier_argument_missing_err!(now))?;
if now - inner.leeway > expires {
return Err(HttpSignatureError::Expired {
not_after: expires,
now,
});
}
}
if let Some(created) = self.http_signature.created {
let now = inner.now.ok_or(verifier_argument_missing_err!(now))?;
if now + inner.leeway < created {
return Err(HttpSignatureError::NotYetValid { created, now });
}
}
drop(inner);
let signing_string = match signing_string_generation {
SigningStringGenMethod::PreGenerated(signing_string) => Cow::Borrowed(signing_string),
SigningStringGenMethod::FromHttpRequest(http_request) => {
let headers = if self.http_signature.headers.is_empty() {
&[Header::Created][..]
} else {
self.http_signature.headers.as_slice()
};
let mut acc = Vec::with_capacity(headers.len());
for header in headers {
match header {
Header::Name(header_name) => {
let concatenated_values = http_request.get_header_concatenated_values(header_name)?;
if concatenated_values.is_empty() {
acc.push(format!("{}:", header_name.as_str()));
} else {
acc.push(format!("{}: {}", header_name.as_str(), concatenated_values));
}
}
Header::RequestTarget => {
acc.push(format!(
"{}: {} {}",
header.as_str(),
http_request.get_lowercased_method()?,
http_request.get_target()?
));
}
Header::Created => acc.push(format!(
"{}: {}",
header.as_str(),
self.http_signature
.created
.ok_or(HttpSignatureError::MissingRequiredParameter {
parameter: HTTP_SIGNATURE_CREATED,
})?
)),
Header::Expires => acc.push(format!(
"{}: {}",
header.as_str(),
self.http_signature
.expires
.ok_or(HttpSignatureError::MissingRequiredParameter {
parameter: HTTP_SIGNATURE_EXPIRES,
})?
)),
}
}
Cow::Owned(acc.join("\n"))
}
};
let decoded_signature = if self.http_signature.legacy {
general_purpose::URL_SAFE_NO_PAD.decode(&self.http_signature.signature)?
} else {
general_purpose::STANDARD.decode(&self.http_signature.signature)?
};
signature_type.verify(public_key, signing_string.as_bytes(), &decoded_signature)?;
Ok(())
}
}
const HTTP_SIG_ALGO_RSA_MD5: &str = "rsa-md5";
const HTTP_SIG_ALGO_RSA_SHA_1: &str = "rsa-sha1";
const HTTP_SIG_ALGO_RSA_SHA_224: &str = "rsa-sha224";
const HTTP_SIG_ALGO_RSA_SHA_256: &str = "rsa-sha256";
const HTTP_SIG_ALGO_RSA_SHA_384: &str = "rsa-sha384";
const HTTP_SIG_ALGO_RSA_SHA_512: &str = "rsa-sha512";
const HTTP_SIG_ALGO_RSA_SHA2_224: &str = "rsa-sha2-224";
const HTTP_SIG_ALGO_RSA_SHA2_256: &str = "rsa-sha2-256";
const HTTP_SIG_ALGO_RSA_SHA2_384: &str = "rsa-sha2-384";
const HTTP_SIG_ALGO_RSA_SHA2_512: &str = "rsa-sha2-512";
const HTTP_SIG_ALGO_RSA_SHA3_384: &str = "rsa-sha3-384";
const HTTP_SIG_ALGO_RSA_SHA3_512: &str = "rsa-sha3-512";
const HTTP_SIG_ALGO_ECDSA_SHA_256: &str = "ecdsa-sha256";
const HTTP_SIG_ALGO_ECDSA_SHA_384: &str = "ecdsa-sha384";
const HTTP_SIG_ALGO_ED25519_SHA512: &str = "ed25519-sha512";
fn to_http_sig_algo_str(algo: SignatureAlgorithm) -> &'static str {
match algo {
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::MD5) => HTTP_SIG_ALGO_RSA_MD5,
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA1) => HTTP_SIG_ALGO_RSA_SHA_1,
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_224) => HTTP_SIG_ALGO_RSA_SHA_224,
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_256) => HTTP_SIG_ALGO_RSA_SHA_256,
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_384) => HTTP_SIG_ALGO_RSA_SHA_384,
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_512) => HTTP_SIG_ALGO_RSA_SHA_512,
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA3_384) => HTTP_SIG_ALGO_RSA_SHA3_384,
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA3_512) => HTTP_SIG_ALGO_RSA_SHA3_512,
SignatureAlgorithm::Ecdsa(HashAlgorithm::SHA2_256) => HTTP_SIG_ALGO_ECDSA_SHA_256,
SignatureAlgorithm::Ecdsa(HashAlgorithm::SHA2_384) => HTTP_SIG_ALGO_ECDSA_SHA_384,
SignatureAlgorithm::Ed25519 => HTTP_SIG_ALGO_ED25519_SHA512,
SignatureAlgorithm::Ecdsa(_) => "ECDSA unsupported algorithm",
}
}
fn from_http_sig_algo_str(s: &str) -> Option<SignatureAlgorithm> {
match s {
HTTP_SIG_ALGO_RSA_MD5 => Some(SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::MD5)),
HTTP_SIG_ALGO_RSA_SHA_1 => Some(SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA1)),
HTTP_SIG_ALGO_RSA_SHA_224 | HTTP_SIG_ALGO_RSA_SHA2_224 => {
Some(SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_224))
}
HTTP_SIG_ALGO_RSA_SHA_256 | HTTP_SIG_ALGO_RSA_SHA2_256 => {
Some(SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_256))
}
HTTP_SIG_ALGO_RSA_SHA_384 | HTTP_SIG_ALGO_RSA_SHA2_384 => {
Some(SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_384))
}
HTTP_SIG_ALGO_RSA_SHA_512 | HTTP_SIG_ALGO_RSA_SHA2_512 => {
Some(SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_512))
}
HTTP_SIG_ALGO_RSA_SHA3_384 => Some(SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA3_384)),
HTTP_SIG_ALGO_RSA_SHA3_512 => Some(SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA3_512)),
HTTP_SIG_ALGO_ECDSA_SHA_256 => Some(SignatureAlgorithm::Ecdsa(HashAlgorithm::SHA2_256)),
HTTP_SIG_ALGO_ECDSA_SHA_384 => Some(SignatureAlgorithm::Ecdsa(HashAlgorithm::SHA2_384)),
HTTP_SIG_ALGO_ED25519_SHA512 => Some(SignatureAlgorithm::Ed25519),
_ => None,
}
}
fn is_algo_compatible_with_key(algo: SignatureAlgorithm, key: &PublicKey) -> bool {
use picky_asn1_x509::oids::*;
let key_algo = Into::<String>::into(key.as_inner().algorithm.oid());
match algo {
_ if key_algo == RSA_ENCRYPTION => true,
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA1) if key_algo == SHA1_WITH_RSA_ENCRYPTION => true,
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_224) if key_algo == SHA224_WITH_RSA_ENCRYPTION => true,
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_256) if key_algo == SHA256_WITH_RSA_ENCRYPTION => true,
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_384) if key_algo == SHA384_WITH_RSA_ENCRYPTION => true,
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_512) if key_algo == SHA512_WITH_RSA_ENCRYPTION => true,
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA3_384) if key_algo == ID_RSASSA_PKCS1_V1_5_WITH_SHA3_384 => {
true
}
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA3_512) if key_algo == ID_RSASSA_PKCS1_V1_5_WITH_SHA3_512 => {
true
}
SignatureAlgorithm::Ecdsa(HashAlgorithm::SHA2_256) if key_algo == ECDSA_WITH_SHA256 => true,
SignatureAlgorithm::Ecdsa(HashAlgorithm::SHA2_384) if key_algo == ECDSA_WITH_SHA384 => true,
SignatureAlgorithm::Ed25519 if key_algo == ED25519 => true,
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pem::Pem;
use http::method::Method;
use http::{header, request};
use picky_asn1_x509::{AlgorithmIdentifier, SubjectPublicKeyInfo};
const HTTP_SIGNATURE_EXAMPLE: &str = "Signature keyId=\"my-rsa-key\",algorithm=\"rsa-sha256\"\
,created=1402170695,headers=\"(request-target) (created) date\",\
signature=\"bw579lDtTDsp7zif/F7Fy93KXrM6qUfCb43JMJtiL4+3nazIPlxcxVsRJEgZzK/QQPDoeUQ\
p4BYCzi2CbthYhHJMn/Wv008gNMcQQTuEw/KcnMrFWxqqUnVZQbCQvNai2y80WrBiOFZvN2VIdLUSO4SoIa\
OHvrvEoQhl3sqpv1z7yCVbQtJHwnPOWoy/11p+SU3X2ARJXN555q5wSn+DykM0Ohq1cXD84MHXP5ulI0Fa8\
4zQ5waxoXsieex4FI+zXSlngGmchBPXMUC437u2wXA1zLA4KGUL/uNScL1MKrTMqgV0MK4o6sR0LHOqHmIi\
MJ7h++UmOW/0Iw74CL2UGQ==\"";
const HTTP_SIGNATURE_WEIRD_FORMAT: &str = "Signature keyId = my-rsa-key ,created= \"1402170695\",\
,algorithm =\"rsa-sha256 \",headers=(request-target) (created) date ,\
signature=bw579lDtTDsp7zif/F7Fy93KXrM6qUfCb43JMJtiL4+3nazIPlxcxVsRJEgZzK/QQPDoeUQ\
p4BYCzi2CbthYhHJMn/Wv008gNMcQQTuEw/KcnMrFWxqqUnVZQbCQvNai2y80WrBiOFZvN2VIdLUSO4SoIa\
OHvrvEoQhl3sqpv1z7yCVbQtJHwnPOWoy/11p+SU3X2ARJXN555q5wSn+DykM0Ohq1cXD84MHXP5ulI0Fa8\
4zQ5waxoXsieex4FI+zXSlngGmchBPXMUC437u2wXA1zLA4KGUL/uNScL1MKrTMqgV0MK4o6sR0LHOqHmIi\
MJ7h++UmOW/0Iw74CL2UGQ==";
fn private_key_1() -> PrivateKey {
let pem = picky_test_data::RSA_2048_PK_7.parse::<Pem>().expect("pem 1");
PrivateKey::from_pem(&pem).expect("private key 1")
}
fn private_key_2() -> PrivateKey {
let pem = picky_test_data::RSA_2048_PK_1.parse::<Pem>().expect("pem 2");
PrivateKey::from_pem(&pem).expect("private key 2")
}
#[test]
fn sign() {
let private_key = private_key_1();
let http_signature_builder = HttpSignatureBuilder::new();
http_signature_builder
.key_id("my-rsa-key")
.signature_method(&private_key, SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_256))
.request_target()
.created(1402170695)
.http_header("Date");
let req = request::Builder::new()
.method(Method::GET)
.uri("/foo")
.header(header::DATE, "Tue, 07 Jun 2014 20:51:35 GMT")
.header(header::CACHE_CONTROL, "max-age=60") .header(header::CACHE_CONTROL, "must-revalidate") .body(())
.expect("couldn't build request");
let (parts, _) = req.into_parts();
let http_signature = http_signature_builder
.clone()
.generate_signing_string_using_http_request(&parts)
.build()
.expect("couldn't generate http signature");
let http_signature_str = http_signature.to_signing_string();
pretty_assertions::assert_eq!(http_signature_str, HTTP_SIGNATURE_EXAMPLE);
let req_2 = request::Builder::new()
.method(Method::GET)
.uri("/foo")
.header(header::DATE, "Tue, 07 Jun 2014 20:51:35 GMT")
.header(header::CACHE_CONTROL, "max-age=222") .body(())
.expect("couldn't build request");
let (parts_2, _) = req_2.into_parts();
let http_signature_2 = http_signature_builder
.generate_signing_string_using_http_request(&parts_2)
.build()
.expect("couldn't generate http signature 2");
let http_signature_str_2 = http_signature_2.to_signing_string();
pretty_assertions::assert_eq!(http_signature_str_2, http_signature_str);
}
#[test]
fn verify() {
let req = request::Builder::new()
.method(Method::GET)
.uri("/foo")
.header(header::DATE, "Tue, 07 Jun 2014 20:51:35 GMT")
.header("something-else", "owowo") .body(())
.expect("couldn't build request");
let (parts, _) = req.into_parts();
for http_signature in &[
HttpSignature::from_str(HTTP_SIGNATURE_EXAMPLE).expect("http signature example"),
HttpSignature::from_str(HTTP_SIGNATURE_WEIRD_FORMAT).expect("http signature weird format"),
] {
assert!(!http_signature.legacy);
http_signature
.verifier()
.now(1402170700)
.signature_method(
&private_key_1().to_public_key().unwrap(),
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_256),
)
.generate_signing_string_using_http_request(&parts)
.verify()
.expect("couldn't verify");
}
}
#[test]
fn invalid_signature_err() {
let req = request::Builder::new()
.method(Method::GET)
.uri("/foo")
.header(header::DATE, "Tue, 07 Jun 2014 20:51:35 GMT")
.body(())
.expect("couldn't build request");
let (parts, _) = req.into_parts();
let http_signature = HttpSignatureBuilder::new()
.key_id("my-rsa-key")
.signature_method(
&private_key_1(),
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_256),
)
.request_target()
.created(1402170695)
.expires(1402170705)
.http_header("Date")
.generate_signing_string_using_http_request(&parts)
.build()
.expect("couldn't generate http signature");
let err = http_signature
.verifier()
.now(1402170700)
.signature_method(
&private_key_2().to_public_key().unwrap(),
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_256),
)
.generate_signing_string_using_http_request(&parts)
.verify()
.expect_err("verify");
assert_eq!(err.to_string(), "signature error: invalid signature");
let err = http_signature
.verifier()
.now(1402170700)
.signature_method(
&private_key_1().to_public_key().unwrap(),
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA1),
)
.generate_signing_string_using_http_request(&parts)
.verify()
.expect_err("verify");
assert_eq!(
err.to_string(),
"incompatible \'algorithm\' parameter: RsaPkcs1v15(SHA2_256)"
);
let err = http_signature
.verifier()
.now(1402170710)
.signature_method(
&private_key_1().to_public_key().unwrap(),
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_256),
)
.generate_signing_string_using_http_request(&parts)
.verify()
.expect_err("verify");
assert_eq!(
err.to_string(),
"signature expired (not after: 1402170705, now: 1402170710)"
);
let err = http_signature
.verifier()
.now(1402170600)
.signature_method(
&private_key_1().to_public_key().unwrap(),
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_256),
)
.generate_signing_string_using_http_request(&parts)
.verify()
.expect_err("verify");
assert_eq!(
err.to_string(),
"signature is not yet valid (created: 1402170695, now: 1402170600)"
);
let req_2 = request::Builder::new()
.method(Method::GET)
.uri("/foo")
.header(header::DATE, "Tue, 08 Jun 2014 20:51:35 GMT")
.body(())
.expect("couldn't build request");
let (parts_2, _) = req_2.into_parts();
let err = http_signature
.verifier()
.now(1402170700)
.signature_method(
&private_key_1().to_public_key().unwrap(),
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_256),
)
.generate_signing_string_using_http_request(&parts_2)
.verify()
.expect_err("verify");
assert_eq!(err.to_string(), "signature error: invalid signature");
let mut invalid_algorithm_http_sig = http_signature;
invalid_algorithm_http_sig.algorithm = None;
let err = invalid_algorithm_http_sig
.verifier()
.now(1402170700)
.signature_method(
&private_key_1().to_public_key().unwrap(),
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA1),
)
.generate_signing_string_using_http_request(&parts)
.verify()
.expect_err("verify");
assert_eq!(err.to_string(), "signature error: invalid signature");
}
#[test]
fn sign_with_pre_generated_signing_string() {
let signing_string = "(request-target): get /foo\n(created): 1402170695\ndate: Tue, 07 Jun 2014 20:51:35 GMT";
let http_signature = HttpSignatureBuilder::new()
.key_id("my-rsa-key")
.signature_method(
&private_key_1(),
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_256),
)
.pre_generated_signing_string(signing_string)
.build()
.expect("couldn't generate http signature using pre-generated signing string");
let http_signature_str = http_signature.to_signing_string();
assert_eq!(http_signature_str, HTTP_SIGNATURE_EXAMPLE);
}
#[test]
fn verify_with_pre_generated_signing_string() {
let signing_string = "(request-target): get /foo\n(created): 1402170695\ndate: Tue, 07 Jun 2014 20:51:35 GMT";
let http_signature = HttpSignature::from_str(HTTP_SIGNATURE_EXAMPLE).expect("http signature");
http_signature
.verifier()
.now(1402170700)
.signature_method(
&private_key_1().to_public_key().unwrap(),
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_256),
)
.pre_generated_signing_string(signing_string)
.verify()
.expect("couldn't verify");
}
#[test]
fn verify_with_leeway() {
let signing_string = "(request-target): get /foo\n(created): 1402170695\ndate: Tue, 07 Jun 2014 20:51:35 GMT";
let http_signature = HttpSignature::from_str(HTTP_SIGNATURE_EXAMPLE).expect("http signature");
http_signature
.verifier()
.now(1402170690)
.leeway(10)
.signature_method(
&private_key_1().to_public_key().unwrap(),
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_256),
)
.pre_generated_signing_string(signing_string)
.verify()
.expect("couldn't verify");
}
fn parse_err(http_signature: &str) -> String {
http_signature
.parse::<HttpSignature>()
.expect_err("no parse error")
.to_string()
}
#[test]
fn http_signature_parse_err() {
pretty_assertions::assert_eq!(
parse_err("Signature signature=\"some sig\""),
"required parameter is missing from http signature string: keyId"
);
pretty_assertions::assert_eq!(
parse_err(
"Signature keyId=\"my-rsa-key\", created=\"HQHQHQ\", \
signature=\"some sig\""
),
"invalid parameter: created"
);
}
const HTTP_SIGNATURE_LEGACY: &str = "Signature keyId=my-rsa-key,created=1402170695,\
headers=(request-target) (created) date,\
signature=bw579lDtTDsp7zif_F7Fy93KXrM6qUfCb43JMJtiL4-3nazIPlxcxVsRJEgZzK_QQPDoeUQp4B\
YCzi2CbthYhHJMn_Wv008gNMcQQTuEw_KcnMrFWxqqUnVZQbCQvNai2y80WrBiOFZvN2VIdLUSO4SoIaOHvr\
vEoQhl3sqpv1z7yCVbQtJHwnPOWoy_11p-SU3X2ARJXN555q5wSn-DykM0Ohq1cXD84MHXP5ulI0Fa84zQ5w\
axoXsieex4FI-zXSlngGmchBPXMUC437u2wXA1zLA4KGUL_uNScL1MKrTMqgV0MK4o6sR0LHOqHmIiMJ7h--\
UmOW_0Iw74CL2UGQ";
#[test]
fn legacy() {
let req = request::Builder::new()
.method(Method::GET)
.uri("/foo")
.header(header::DATE, "Tue, 07 Jun 2014 20:51:35 GMT")
.body(())
.expect("couldn't build request");
let (parts, _) = req.into_parts();
{
let private_key = private_key_1();
let http_signature = HttpSignatureBuilder::new()
.key_id("my-rsa-key")
.signature_method(&private_key, SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_256))
.request_target()
.created(1402170695)
.generate_signing_string_using_http_request(&parts)
.http_header("Date")
.legacy()
.build()
.expect("build http signature");
pretty_assertions::assert_eq!(http_signature.to_signing_string(), HTTP_SIGNATURE_LEGACY);
}
{
let http_signature = HttpSignature::from_str(HTTP_SIGNATURE_LEGACY).expect("http signature legacy");
http_signature
.verifier()
.now(1402170700)
.signature_method(
&private_key_1().to_public_key().unwrap(),
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_256),
)
.generate_signing_string_using_http_request(&parts)
.verify()
.expect("couldn't verify");
}
}
#[test]
fn incompatible_algorithm_err() {
let req = request::Builder::new()
.method(Method::GET)
.uri("/foo")
.header(header::DATE, "Tue, 07 Jun 2014 20:51:35 GMT")
.body(())
.expect("couldn't build request");
let (parts, _) = req.into_parts();
let private_key = private_key_1();
let http_signature = HttpSignatureBuilder::new()
.key_id("my-rsa-key")
.signature_method(&private_key, SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_384))
.request_target()
.created(1402170695)
.generate_signing_string_using_http_request(&parts)
.http_header("Date")
.build()
.expect("build http signature");
let mut spki = SubjectPublicKeyInfo::from(private_key_1().to_public_key().unwrap());
spki.algorithm = AlgorithmIdentifier::new_sha512_with_rsa_encryption();
let sha512_only_key = PublicKey::from(spki);
let err = http_signature
.verifier()
.now(1402170700)
.signature_method(
&sha512_only_key,
SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_384),
)
.generate_signing_string_using_http_request(&parts)
.verify()
.expect_err("verify");
assert_eq!(
err.to_string(),
"incompatible 'algorithm' parameter: RsaPkcs1v15(SHA2_384)"
);
}
const HTTP_SIGNATURE_UNKNOWN_ALGO: &str = "Signature keyId=\"my-rsa-key\",algorithm=\"magical-algo\",\
headers=\"(request-target)\",signature=\"GARBAGE\"";
#[test]
fn unknown_algorithms_are_ignored() {
let http_signature = HttpSignature::from_str(HTTP_SIGNATURE_UNKNOWN_ALGO).expect("from str");
assert_eq!(http_signature.algorithm.unwrap().as_str(), "magical-algo");
}
}