http-signature-normalization/src/verify.rs

494 lines
15 KiB
Rust
Raw Normal View History

2019-09-21 16:26:11 +00:00
//! Types and methods to verify a signature or authorization header
2022-01-17 22:49:01 +00:00
use crate::{
build_signing_string, parse_unix_timestamp, RequiredError, ALGORITHM_FIELD, CREATED,
CREATED_FIELD, EXPIRES_FIELD, HEADERS_FIELD, KEY_ID_FIELD, SIGNATURE_FIELD,
};
use httpdate::HttpDate;
2019-09-11 05:24:04 +00:00
use std::{
collections::{BTreeMap, HashMap, HashSet},
2019-09-11 05:24:04 +00:00
error::Error,
fmt,
str::FromStr,
2022-01-17 22:49:01 +00:00
time::{Duration, SystemTime},
2019-09-11 05:17:30 +00:00
};
2019-09-11 06:24:51 +00:00
#[derive(Debug)]
2019-09-21 16:26:11 +00:00
/// The Unverified step of the verification process
///
/// This type is the result of performing some basic validation on the parsed header, and can be
/// used to verify the header
2019-09-11 06:24:51 +00:00
pub struct Unverified {
key_id: String,
signature: String,
2019-09-11 06:24:51 +00:00
algorithm: Option<Algorithm>,
signing_string: String,
}
#[derive(Debug)]
2019-09-21 16:26:11 +00:00
/// The Unvalidated stage
///
/// This is created after generating the signing string from a parsed header, and transitions into
/// the Unverified type by applying some basic validations
2019-09-11 05:17:30 +00:00
pub struct Unvalidated {
pub(crate) key_id: String,
pub(crate) signature: String,
pub(crate) algorithm: Option<Algorithm>,
2022-01-17 22:49:01 +00:00
pub(crate) created: Option<SystemTime>,
pub(crate) expires: Option<SystemTime>,
pub(crate) parsed_at: SystemTime,
pub(crate) date: Option<String>,
2019-09-11 05:17:30 +00:00
pub(crate) signing_string: String,
}
2019-09-11 06:24:51 +00:00
#[derive(Debug)]
2019-09-21 16:26:11 +00:00
/// The successful result of parsing a Signature or Authorization header
2019-09-11 05:17:30 +00:00
pub struct ParsedHeader {
signature: String,
key_id: String,
headers: Vec<String>,
algorithm: Option<Algorithm>,
2022-01-17 22:49:01 +00:00
created: Option<SystemTime>,
expires: Option<SystemTime>,
parsed_at: SystemTime,
2019-09-11 05:17:30 +00:00
}
#[derive(Clone, Copy, Debug)]
2019-09-21 16:26:11 +00:00
/// Algorithms that may be present in an HTTP Signature's `algorithm` field, but are considered
/// deprecated due to security issues
///
/// Most of these are Deprecated solely because the presence of the algorithm's name in the request
/// could be used to gain insight into ways to forge requests. This doesn't mean that using these
/// algorithms to sign and verify requests is bad, it just means that stating which algorithm is in
/// use is dangerous. In the case of the SHA1 variants, they were deprecated for being weak
/// hashes.
///
/// This library only produces HTTP Signatures with the "HS2019" algorithm type, and leaves
/// deciding which algorithm to actually use to implementors
2019-09-11 05:17:30 +00:00
pub enum DeprecatedAlgorithm {
2019-09-21 16:26:11 +00:00
/// HMAC SHA-1
2019-09-11 05:17:30 +00:00
HmacSha1,
2019-09-21 16:26:11 +00:00
/// HMAC SHA-256
2019-09-11 05:17:30 +00:00
HmacSha256,
2019-09-21 16:26:11 +00:00
/// HMAC SHA-384
2019-09-11 05:17:30 +00:00
HmacSha384,
2019-09-21 16:26:11 +00:00
/// HMAC SHA-512
2019-09-11 05:17:30 +00:00
HmacSha512,
2019-09-21 16:26:11 +00:00
/// RSA SHA-1
2019-09-11 05:17:30 +00:00
RsaSha1,
2019-09-21 16:26:11 +00:00
/// RSA SHA-256
2019-09-11 05:17:30 +00:00
RsaSha256,
2019-09-21 16:26:11 +00:00
/// RSA SHA-384
2019-09-11 05:17:30 +00:00
RsaSha384,
2019-09-21 16:26:11 +00:00
/// RSA SHA-512
2019-09-11 05:17:30 +00:00
RsaSha512,
2019-09-21 16:26:11 +00:00
/// ECDSA SHA-1
2019-09-11 05:17:30 +00:00
EcdsaSha1,
2019-09-21 16:26:11 +00:00
/// ECDSA SHA-256
2019-09-11 05:17:30 +00:00
EcdsaSha256,
2019-09-21 16:26:11 +00:00
/// ECDSA SHA-384
2019-09-11 05:17:30 +00:00
EcdsaSha384,
2019-09-21 16:26:11 +00:00
/// ECDSA SHA-512
2019-09-11 05:17:30 +00:00
EcdsaSha512,
}
#[derive(Clone, Debug)]
2019-09-21 16:26:11 +00:00
/// Kinds of algorithms
///
/// This library knows about HS2019 as a supported algorithm, and any other algorithms are either
/// unknown at the time of writing, or deprecated
2019-09-11 05:17:30 +00:00
pub enum Algorithm {
2019-09-21 16:26:11 +00:00
/// The only officially supported algorithm from the current HTTP Signatures specification
2019-09-11 05:17:30 +00:00
Hs2019,
2019-09-21 16:26:11 +00:00
/// Algorithms that have been used historically, but are deprecated
2019-09-11 05:17:30 +00:00
Deprecated(DeprecatedAlgorithm),
2019-09-21 16:26:11 +00:00
/// Algorithms that may be used by custom implementations and are unknown to the spec
2019-09-11 05:17:30 +00:00
Unknown(String),
}
#[derive(Clone, Copy, Debug)]
/// Indicator of which date field was checked for an expired error
pub enum ExpiredField {
/// (expires) pseudo-header
Expires,
/// (created) pseudo-header
Created,
/// Date header
Date,
}
2019-09-11 05:17:30 +00:00
#[derive(Clone, Debug)]
2019-09-21 16:26:11 +00:00
/// Kinds of errors for validating a request
2019-09-11 05:17:30 +00:00
pub enum ValidateError {
2019-09-21 16:26:11 +00:00
/// The Authorization or Signature header is not present
2019-09-11 06:24:51 +00:00
Missing,
2019-09-21 16:26:11 +00:00
/// The request's `created` or `expires` field indicates it is too old to be valid
Expired {
/// Which field did the date come from
field: ExpiredField,
/// When did the signature expire
expires: SystemTime,
/// When was the signature checked
checked: SystemTime,
},
2019-09-11 05:17:30 +00:00
}
#[derive(Clone, Debug)]
2019-09-21 16:26:11 +00:00
/// The error produced when parsing the HTTPT Signature fails, including the name of the field that
/// was invalid.
2019-09-11 05:17:30 +00:00
pub struct ParseSignatureError(&'static str);
2020-03-20 02:36:10 +00:00
impl ParseSignatureError {
/// Get the name of the missing field
pub fn missing_field(&self) -> &'static str {
self.0
}
}
2019-09-11 06:24:51 +00:00
impl Unverified {
2019-09-21 16:26:11 +00:00
/// Get the Key ID from an Unverified type
///
/// This is useful for looking up the proper verification key to verify the request
2019-09-11 05:17:30 +00:00
pub fn key_id(&self) -> &str {
&self.key_id
}
2019-09-21 16:26:11 +00:00
/// Get the Algorithm used in the request, if one is present
///
/// If the algorithm is present and is not what an implementor expected, they should not
/// attempt to verify the signature
2019-09-11 05:17:30 +00:00
pub fn algorithm(&self) -> Option<&Algorithm> {
self.algorithm.as_ref()
}
2019-09-11 06:24:51 +00:00
2020-01-01 00:10:42 +00:00
/// Get the signing string used to create the signature
pub fn signing_string(&self) -> &str {
&self.signing_string
}
/// Get the signature itself
pub fn signature(&self) -> &str {
&self.signature
}
2019-09-21 16:26:11 +00:00
/// Verify the signature with the signature and the signing string
///
/// ```rust,ignore
/// unverified.verify(|signature, signing_string| {
/// let bytes = match base64::decode(signature) {
/// Ok(bytes) => bytes,
/// Err(_) => return false,
/// };
///
/// public_key
/// .verify(bytes, signing_string)
/// .unwrap_or(false)
/// })
/// ```
2019-09-11 06:24:51 +00:00
pub fn verify<F, T>(&self, f: F) -> T
where
F: FnOnce(&str, &str) -> T,
2019-09-11 06:24:51 +00:00
{
(f)(&self.signature, &self.signing_string)
}
}
impl Unvalidated {
2019-09-21 16:26:11 +00:00
/// Validate parts of the header, ensuring that the provided dates don't indicate that it is
/// expired.
2019-09-11 06:24:51 +00:00
pub fn validate(self, expires_after: Duration) -> Result<Unverified, ValidateError> {
if let Some(expires) = self.expires {
if expires < self.parsed_at {
return Err(ValidateError::Expired {
field: ExpiredField::Expires,
expires,
checked: self.parsed_at,
});
2019-09-11 06:24:51 +00:00
}
}
if let Some(created) = self.created {
if created + expires_after < self.parsed_at {
return Err(ValidateError::Expired {
field: ExpiredField::Created,
expires: created + expires_after,
checked: self.parsed_at,
});
2019-09-11 06:24:51 +00:00
}
}
if let Some(date) = self.date {
2022-01-17 22:49:01 +00:00
if let Ok(datetime) = date.parse::<HttpDate>() {
let date = SystemTime::from(datetime);
if date + expires_after < self.parsed_at {
return Err(ValidateError::Expired {
field: ExpiredField::Date,
expires: date + expires_after,
checked: self.parsed_at,
});
}
}
}
2019-09-11 06:24:51 +00:00
Ok(Unverified {
key_id: self.key_id,
algorithm: self.algorithm,
signing_string: self.signing_string,
signature: self.signature,
2019-09-11 06:24:51 +00:00
})
}
2019-09-11 05:17:30 +00:00
}
impl ParsedHeader {
2019-09-21 16:26:11 +00:00
/// Generate a Signing String from the header
2019-09-11 06:24:51 +00:00
pub fn into_unvalidated(
2019-09-11 05:17:30 +00:00
self,
2019-09-11 05:24:04 +00:00
method: &str,
path_and_query: &str,
headers: &mut BTreeMap<String, String>,
required_headers: HashSet<String>,
) -> Result<Unvalidated, RequiredError> {
let date = headers.get("date").cloned();
2019-09-11 05:17:30 +00:00
let signing_string = build_signing_string(
method,
path_and_query,
self.created,
self.expires,
&self.headers,
2019-09-11 05:24:04 +00:00
headers,
required_headers,
)?;
2019-09-11 05:17:30 +00:00
Ok(Unvalidated {
2019-09-11 05:17:30 +00:00
key_id: self.key_id,
signature: self.signature,
parsed_at: self.parsed_at,
algorithm: self.algorithm,
created: self.created,
expires: self.expires,
date,
2019-09-11 05:17:30 +00:00
signing_string,
})
2019-09-11 05:17:30 +00:00
}
}
impl FromStr for ParsedHeader {
type Err = ParseSignatureError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim_start_matches("Signature").trim();
let mut hm: HashMap<String, String> = s
.split(',')
.filter_map(|part| {
2020-04-26 01:31:38 +00:00
let mut i = part.splitn(2, '=');
2019-09-11 05:17:30 +00:00
if let Some(key) = i.next() {
if let Some(value) = i.next() {
2019-09-11 06:24:51 +00:00
return Some((key.to_owned(), value.trim_matches('"').to_owned()));
2019-09-11 05:17:30 +00:00
}
}
None
})
.collect();
Ok(ParsedHeader {
signature: hm
.remove(SIGNATURE_FIELD)
.ok_or(ParseSignatureError(SIGNATURE_FIELD))?,
key_id: hm
.remove(KEY_ID_FIELD)
.ok_or(ParseSignatureError(KEY_ID_FIELD))?,
headers: hm
.remove(HEADERS_FIELD)
.map(|h| h.split_whitespace().map(|s| s.to_owned()).collect())
.unwrap_or_else(|| vec![CREATED.to_owned()]),
2020-04-26 01:31:38 +00:00
algorithm: hm.remove(ALGORITHM_FIELD).map(Algorithm::from),
2019-09-11 05:17:30 +00:00
created: parse_time(&mut hm, CREATED_FIELD)?,
expires: parse_time(&mut hm, EXPIRES_FIELD)?,
2022-01-17 22:49:01 +00:00
parsed_at: SystemTime::now(),
2019-09-11 05:17:30 +00:00
})
}
}
fn parse_time(
hm: &mut HashMap<String, String>,
key: &'static str,
2022-01-17 22:49:01 +00:00
) -> Result<Option<SystemTime>, ParseSignatureError> {
let r = hm
.remove(key)
.map(|s| parse_unix_timestamp(&s).map_err(|_| ParseSignatureError(key)));
2019-09-11 05:17:30 +00:00
match r {
Some(Ok(t)) => Ok(Some(t)),
Some(Err(e)) => Err(e),
None => Ok(None),
}
}
impl From<DeprecatedAlgorithm> for Algorithm {
fn from(d: DeprecatedAlgorithm) -> Algorithm {
Algorithm::Deprecated(d)
}
}
impl From<String> for Algorithm {
fn from(s: String) -> Self {
Algorithm::from(s.as_str())
}
}
impl From<&str> for Algorithm {
fn from(s: &str) -> Self {
match s {
"hmac-sha1" => DeprecatedAlgorithm::HmacSha1.into(),
"hmac-sha256" => DeprecatedAlgorithm::HmacSha256.into(),
"hmac-sha384" => DeprecatedAlgorithm::HmacSha384.into(),
"hmac-sha512" => DeprecatedAlgorithm::HmacSha512.into(),
"rsa-sha1" => DeprecatedAlgorithm::RsaSha1.into(),
"rsa-sha256" => DeprecatedAlgorithm::RsaSha256.into(),
"rsa-sha384" => DeprecatedAlgorithm::RsaSha384.into(),
"rsa-sha512" => DeprecatedAlgorithm::RsaSha512.into(),
"ecdsa-sha1" => DeprecatedAlgorithm::EcdsaSha1.into(),
"ecdsa-sha256" => DeprecatedAlgorithm::EcdsaSha256.into(),
"ecdsa-sha384" => DeprecatedAlgorithm::EcdsaSha384.into(),
"ecdsa-sha512" => DeprecatedAlgorithm::EcdsaSha512.into(),
"hs2019" => Algorithm::Hs2019,
other => Algorithm::Unknown(other.into()),
}
}
}
impl fmt::Display for DeprecatedAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
DeprecatedAlgorithm::HmacSha1 => "hmac-sha1",
DeprecatedAlgorithm::HmacSha256 => "hmac-sha256",
DeprecatedAlgorithm::HmacSha384 => "hmac-sha384",
DeprecatedAlgorithm::HmacSha512 => "hmac-sha512",
DeprecatedAlgorithm::RsaSha1 => "rsa-sha1",
DeprecatedAlgorithm::RsaSha256 => "rsa-sha256",
DeprecatedAlgorithm::RsaSha384 => "rsa-sha384",
DeprecatedAlgorithm::RsaSha512 => "rsa-sha512",
DeprecatedAlgorithm::EcdsaSha1 => "ecdsa-sha1",
DeprecatedAlgorithm::EcdsaSha256 => "ecdsa-sha256",
DeprecatedAlgorithm::EcdsaSha384 => "ecdsa-sha384",
DeprecatedAlgorithm::EcdsaSha512 => "ecdsa-sha512",
};
write!(f, "{}", s)
}
}
impl fmt::Display for Algorithm {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
2020-04-26 01:31:38 +00:00
Algorithm::Hs2019 => write!(f, "hs2019"),
Algorithm::Deprecated(d) => d.fmt(f),
Algorithm::Unknown(other) => write!(f, "{}", other),
}
}
}
2019-09-11 06:24:51 +00:00
impl fmt::Display for ParseSignatureError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Error when parsing {} from Http Signature", self.0)
}
}
impl Error for ParseSignatureError {
fn description(&self) -> &'static str {
"There was an error parsing the Http Signature"
}
}
impl fmt::Display for ExpiredField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Expires => write!(f, "expires pseudo-header"),
Self::Created => write!(f, "created pseudo-header"),
Self::Date => write!(f, "Date header"),
2019-09-11 05:17:30 +00:00
}
}
}
impl fmt::Display for ValidateError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2019-09-11 05:17:30 +00:00
match *self {
ValidateError::Missing => write!(f, "Http Signature is missing"),
ValidateError::Expired {
field,
expires,
checked,
} => write!(
f,
"Http Signature is expired, checked {}, checked at {}, expired at {}",
field,
httpdate::fmt_http_date(checked),
httpdate::fmt_http_date(expires)
),
2019-09-11 05:17:30 +00:00
}
}
}
impl Error for ValidateError {}
2019-09-11 06:24:51 +00:00
#[cfg(test)]
mod tests {
use super::ParsedHeader;
2022-01-17 22:49:01 +00:00
use crate::unix_timestamp;
use std::time::SystemTime;
2019-09-11 06:24:51 +00:00
#[test]
fn parses_header_succesfully_1() {
2022-01-17 22:49:01 +00:00
let time1 = unix_timestamp(SystemTime::now());
let time2 = unix_timestamp(SystemTime::now());
2019-09-11 06:24:51 +00:00
let h = format!(
r#"Signature keyId="my-key-id",algorithm="hs2019",created="{}",expires="{}",headers="(request-target) (created) (expires) date content-type",signature="blah blah blah""#,
time1, time2
);
2019-09-11 06:24:51 +00:00
parse_signature(&h)
}
#[test]
fn parses_header_succesfully_2() {
2022-01-17 22:49:01 +00:00
let time1 = unix_timestamp(SystemTime::now());
let time2 = unix_timestamp(SystemTime::now());
2019-09-11 06:24:51 +00:00
let h = format!(
r#"Signature keyId="my-key-id",algorithm="rsa-sha256",created="{}",expires="{}",signature="blah blah blah""#,
time1, time2
);
2019-09-11 06:24:51 +00:00
parse_signature(&h)
}
#[test]
fn parses_header_succesfully_3() {
2022-01-17 22:49:01 +00:00
let time1 = unix_timestamp(SystemTime::now());
2019-09-11 06:24:51 +00:00
let h = format!(
r#"Signature keyId="my-key-id",algorithm="rsa-sha256",created="{}",headers="(request-target) (created) date content-type",signature="blah blah blah""#,
time1
);
2019-09-11 06:24:51 +00:00
parse_signature(&h)
}
#[test]
fn parses_header_succesfully_4() {
let h = r#"Signature keyId="my-key-id",algorithm="rsa-sha256",headers="(request-target) date content-type",signature="blah blah blah""#;
parse_signature(h)
}
fn parse_signature(s: &str) {
let ph: ParsedHeader = s.parse().unwrap();
println!("{:?}", ph);
2019-09-11 05:17:30 +00:00
}
}