use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::error::Error;
use std::fmt::{self, Debug, Display};
use std::sync::Arc;
use std::time::Duration;
use chrono::{DateTime, NaiveDateTime, Utc};
use http::header::{HeaderName, HeaderValue, AUTHORIZATION, DATE};
use sha2::{Digest, Sha256, Sha512};
use subtle::ConstantTimeEq;
use crate::algorithm::{HttpDigest, HttpSignatureVerify};
use crate::canonicalize::{CanonicalizeConfig, CanonicalizeExt};
use crate::header::{Header, PseudoHeader};
use crate::{DefaultDigestAlgorithm, RequestLike, DATE_FORMAT};
#[derive(Debug)]
#[non_exhaustive]
pub struct VerifyingError<Remnant> {
remnant: Remnant,
}
impl<Remnant> VerifyingError<Remnant> {
pub fn into_remnant(self) -> Remnant {
self.remnant
}
}
impl<Remnant: Debug> Error for VerifyingError<Remnant> {}
impl<Remnant> Display for VerifyingError<Remnant> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("VerifyingError")
}
}
pub trait KeyProvider: Debug + Sync + 'static {
fn provide_keys(&self, key_id: &str) -> Vec<Arc<dyn HttpSignatureVerify>>;
}
#[derive(Debug, Default, Clone)]
pub struct SimpleKeyProvider {
keys: HashMap<String, Vec<Arc<dyn HttpSignatureVerify>>>,
}
impl SimpleKeyProvider {
pub fn new<I, S, K>(key_iter: I) -> Self
where
I: IntoIterator<Item = (S, K)>,
S: Into<String>,
K: Into<Arc<dyn HttpSignatureVerify>>,
{
let mut keys: HashMap<String, Vec<_>> = HashMap::new();
for (key_id, key) in key_iter.into_iter() {
keys.entry(key_id.into()).or_default().push(key.into());
}
Self { keys }
}
pub fn add(&mut self, key_id: &str, key: Arc<dyn HttpSignatureVerify>) {
self.keys.entry(key_id.into()).or_default().push(key);
}
pub fn clear(&mut self) {
self.keys.clear();
}
pub fn remove_all(&mut self, key_id: &str) {
self.keys.remove(key_id);
}
}
impl KeyProvider for SimpleKeyProvider {
fn provide_keys(&self, key_id: &str) -> Vec<Arc<dyn HttpSignatureVerify>> {
self.keys.get(key_id).unwrap_or(&Vec::new()).to_vec()
}
}
pub trait DigestProvider: Debug + Sync + 'static {
fn provide_digest(&self, name: &str) -> Option<Box<dyn HttpDigest>>;
}
#[derive(Debug, Default, Copy, Clone)]
pub struct DefaultDigestProvider;
impl DigestProvider for DefaultDigestProvider {
fn provide_digest(&self, name: &str) -> Option<Box<dyn HttpDigest>> {
let name = name.to_ascii_uppercase();
match name.as_str() {
"SHA-256" => Some(Box::new(Sha256::new())),
"SHA-512" => Some(Box::new(Sha512::new())),
_ => None,
}
}
}
#[derive(Debug)]
pub struct VerifyingConfig {
key_provider: Arc<dyn KeyProvider>,
digest_provider: Arc<dyn DigestProvider>,
required_headers: BTreeSet<Header>,
require_digest: bool,
validate_digest: bool,
validate_date: bool,
date_leeway: Duration,
}
impl VerifyingConfig {
pub fn new<KP: KeyProvider>(key_provider: KP) -> Self {
VerifyingConfig {
key_provider: Arc::new(key_provider),
digest_provider: Arc::new(DefaultDigestProvider),
required_headers: [
Header::Pseudo(PseudoHeader::RequestTarget),
Header::Normal(DATE),
]
.iter()
.cloned()
.collect(),
require_digest: true,
validate_digest: true,
validate_date: true,
date_leeway: Duration::from_secs(30),
}
}
pub fn key_provider(&self) -> &dyn KeyProvider {
&*self.key_provider
}
pub fn digest_provider(&self) -> &dyn DigestProvider {
&*self.digest_provider
}
fn set_digest_provider<DP: DigestProvider>(&mut self, digest_provider: DP) -> &mut Self {
self.digest_provider = Arc::new(digest_provider);
self
}
pub fn with_digest<DP: DigestProvider>(mut self, digest_provider: DP) -> Self {
self.set_digest_provider(digest_provider);
self
}
pub fn require_digest(&self) -> bool {
self.require_digest
}
pub fn set_require_digest(&mut self, require_digest: bool) -> &mut Self {
self.require_digest = require_digest;
self
}
pub fn with_require_digest(mut self, require_digest: bool) -> Self {
self.set_require_digest(require_digest);
self
}
pub fn validate_digest(&self) -> bool {
self.validate_digest
}
pub fn set_validate_digest(&mut self, validate_digest: bool) -> &mut Self {
self.validate_digest = validate_digest;
self
}
pub fn with_validate_digest(mut self, validate_digest: bool) -> Self {
self.set_validate_digest(validate_digest);
self
}
pub fn validate_date(&self) -> bool {
self.validate_date
}
pub fn set_validate_date(&mut self, validate_date: bool) -> &mut Self {
self.validate_date = validate_date;
self
}
pub fn with_validate_date(mut self, validate_date: bool) -> Self {
self.set_validate_date(validate_date);
self
}
pub fn date_leeway(&self) -> Duration {
self.date_leeway
}
pub fn set_date_leeway(&mut self, date_leeway: Duration) -> &mut Self {
self.date_leeway = date_leeway;
self
}
pub fn with_date_leeway(mut self, date_leeway: Duration) -> Self {
self.set_date_leeway(date_leeway);
self
}
pub fn required_headers(&self) -> impl IntoIterator<Item = &Header> {
&self.required_headers
}
pub fn set_required_headers(&mut self, required_headers: &[Header]) -> &mut Self {
self.required_headers = required_headers.iter().cloned().collect();
self
}
pub fn with_required_headers(mut self, required_headers: &[Header]) -> Self {
self.set_required_headers(required_headers);
self
}
}
pub trait ServerRequestLike: RequestLike {
type Remnant;
fn complete_with_digest(self, digest: &dyn HttpDigest) -> (Option<String>, Self::Remnant);
fn complete(self) -> Self::Remnant;
}
#[derive(Debug)]
pub struct VerificationDetails {
key_id: String,
}
impl VerificationDetails {
pub fn key_id(&self) -> &str {
&self.key_id
}
}
pub trait VerifyingExt {
type Remnant;
fn verify(
self,
config: &VerifyingConfig,
) -> Result<(Self::Remnant, VerificationDetails), VerifyingError<Self::Remnant>>;
}
fn verify_signature_only<T: ServerRequestLike>(
req: &T,
config: &VerifyingConfig,
) -> Option<(BTreeMap<Header, HeaderValue>, VerificationDetails)> {
let auth_header = req.header(&AUTHORIZATION.into()).or_else(|| {
info!("Verification Failed: No 'Authorization' header");
None
})?;
let mut auth_header = auth_header
.to_str()
.ok()
.or_else(|| {
info!("Verification Failed: Non-ascii 'Authorization' header");
None
})?
.splitn(2, ' ');
let auth_scheme = auth_header.next().or_else(|| {
info!("Verification Failed: Malformed 'Authorization' header");
None
})?;
let auth_args = auth_header.next().or_else(|| {
info!("Verification Failed: Malformed 'Authorization' header");
None
})?;
if !auth_scheme.eq_ignore_ascii_case("Signature") {
info!("Verification Failed: Not using Signature auth");
return None;
}
let auth_args = auth_args
.split(',')
.map(|part: &str| {
let mut kv = part.splitn(2, '=');
let k = kv.next()?.trim();
let v = kv.next()?.trim().trim_matches('"');
Some((k, v))
})
.collect::<Option<BTreeMap<_, _>>>()
.or_else(|| {
info!("Verification Failed: Unable to parse 'Authorization' header");
None
})?;
let key_id = *auth_args.get("keyId").or_else(|| {
info!("Verification Failed: Missing required 'keyId' in 'Authorization' header");
None
})?;
let provided_signature = auth_args.get("signature").or_else(|| {
info!("Verification Failed: Missing required 'signature' in 'Authorization' header");
None
})?;
let algorithm_name = auth_args.get("algorithm").copied();
let verification_details = VerificationDetails {
key_id: key_id.into(),
};
let algorithms = config.key_provider.provide_keys(key_id);
if algorithms.is_empty() {
info!(
"Verification Failed: Unknown key (keyId={:?}, algorithm={:?})",
key_id,
algorithm_name.unwrap_or_default()
);
return None;
}
let mut canonicalize_config = CanonicalizeConfig::new();
if let Some(headers) = auth_args.get("headers") {
canonicalize_config.set_headers(
headers
.split(' ')
.map(str::to_ascii_lowercase)
.map(|header| {
header.parse::<Header>().ok().or_else(|| {
info!("Verification Failed: Invalid header name {:?}", header);
None
})
})
.collect::<Option<_>>()?,
);
}
if let Some(created) = auth_args.get("created") {
canonicalize_config.set_signature_created(created.parse::<HeaderValue>().ok().or_else(
|| {
info!(
"Verification Failed: Invalid signature creation date {:?}",
created
);
None
},
)?);
}
if let Some(expires) = auth_args.get("expires") {
canonicalize_config.set_signature_expires(expires.parse::<HeaderValue>().ok().or_else(
|| {
info!(
"Verification Failed: Invalid signature expires date {:?}",
expires
);
None
},
)?);
}
let content = req
.canonicalize(&canonicalize_config)
.map_err(|e| {
info!("Canonicalization Failed: {}", e);
})
.ok()?;
for algorithm in &algorithms {
if algorithm.http_verify(content.as_bytes(), provided_signature) {
return Some((content.headers.into_iter().collect(), verification_details));
}
}
if algorithms.is_empty() {
info!("Verification Failed: No keys found for this keyId");
} else {
info!("Verification Failed: Invalid signature provided");
}
None
}
fn verify_except_digest<T: ServerRequestLike>(
req: &T,
config: &VerifyingConfig,
) -> Option<(BTreeMap<Header, HeaderValue>, VerificationDetails)> {
let (headers, verification_details) = verify_signature_only(req, config)?;
for header in &config.required_headers {
if !headers.contains_key(header) {
info!(
"Verification Failed: Missing header '{}' required by configuration",
header.as_str()
);
return None;
}
}
if config.validate_date {
if let Some(date_value) = headers.get(&DATE.into()) {
let date_value = date_value.to_str().ok().or_else(|| {
info!("Verification Failed: Non-ascii value for 'date' header");
None
})?;
let provided_date = DateTime::<Utc>::from_naive_utc_and_offset(
NaiveDateTime::parse_from_str(date_value, DATE_FORMAT)
.ok()
.or_else(|| {
info!("Verification Failed: Failed to parse 'date' header");
None
})?,
Utc,
);
let chrono_delta = provided_date.signed_duration_since(Utc::now());
let delta = chrono_delta
.to_std()
.or_else(|_| (-chrono_delta).to_std())
.expect("Should only fail on negative values");
if delta > config.date_leeway {
info!(
"Verification Failed: Date skew of '{}' is outside allowed range",
chrono_delta
);
return None;
}
}
}
Some((headers, verification_details))
}
impl<T: ServerRequestLike> VerifyingExt for T {
type Remnant = T::Remnant;
fn verify(
self,
config: &VerifyingConfig,
) -> Result<(Self::Remnant, VerificationDetails), VerifyingError<Self::Remnant>> {
let digest_header: Header = HeaderName::from_static("digest").into();
let (headers, verification_details) = if let Some(res) = verify_except_digest(&self, config)
{
res
} else {
return Err(VerifyingError {
remnant: self.complete(),
});
};
if let Some(digest_value) = headers.get(&digest_header) {
if config.validate_digest {
let digest_value = match digest_value.to_str() {
Ok(v) => v,
Err(_) => {
info!("Verification Failed: Non-ascii value for 'digest' header");
return Err(VerifyingError {
remnant: self.complete(),
});
}
};
if let Some((digest_alg, provided_digest)) = digest_value
.split(',')
.filter_map(|part| {
let mut kv = part.splitn(2, '=');
let k = kv.next()?.trim();
let v = kv.next()?.trim();
let digest = config.digest_provider.provide_digest(k)?;
Some((digest, v))
})
.next()
{
let (maybe_digest, remnant) = self.complete_with_digest(&*digest_alg);
match maybe_digest {
Some(expected_digest)
if provided_digest
.as_bytes()
.ct_eq(expected_digest.as_bytes())
.into() =>
{
Ok((remnant, verification_details))
}
None => {
info!("Verification Failed: Unable to compute digest for comparison");
Err(VerifyingError { remnant })
}
_ => {
info!("Verification Failed: Computed digest did not match the 'digest' header");
Err(VerifyingError { remnant })
}
}
} else {
info!("Verification Failed: No supported digest algorithms were used");
Err(VerifyingError {
remnant: self.complete(),
})
}
} else {
Ok((self.complete(), verification_details))
}
} else if config.require_digest {
let (maybe_digest, remnant) = self.complete_with_digest(&DefaultDigestAlgorithm::new());
if maybe_digest.is_some() {
info!("Verification Failed: 'digest' header was not included in signature, but is required by configuration");
Err(VerifyingError { remnant })
} else {
Ok((remnant, verification_details))
}
} else {
Ok((self.complete(), verification_details))
}
}
}