http_signature_normalization/
verify.rs

1//! Types and methods to verify a signature or authorization header
2use crate::{
3    build_signing_string, parse_unix_timestamp, RequiredError, ALGORITHM_FIELD, CREATED,
4    CREATED_FIELD, EXPIRES_FIELD, HEADERS_FIELD, KEY_ID_FIELD, SIGNATURE_FIELD,
5};
6use httpdate::HttpDate;
7use std::{
8    collections::{BTreeMap, HashMap, HashSet},
9    error::Error,
10    fmt,
11    str::FromStr,
12    time::{Duration, SystemTime},
13};
14
15#[derive(Debug)]
16/// The Unverified step of the verification process
17///
18/// This type is the result of performing some basic validation on the parsed header, and can be
19/// used to verify the header
20pub struct Unverified {
21    key_id: String,
22    signature: String,
23    algorithm: Option<Algorithm>,
24    signing_string: String,
25}
26
27#[derive(Debug)]
28/// The Unvalidated stage
29///
30/// This is created after generating the signing string from a parsed header, and transitions into
31/// the Unverified type by applying some basic validations
32pub struct Unvalidated {
33    pub(crate) key_id: String,
34    pub(crate) signature: String,
35    pub(crate) algorithm: Option<Algorithm>,
36    pub(crate) created: Option<SystemTime>,
37    pub(crate) expires: Option<SystemTime>,
38    pub(crate) parsed_at: SystemTime,
39    pub(crate) date: Option<String>,
40    pub(crate) signing_string: String,
41}
42
43#[derive(Debug)]
44/// The successful result of parsing a Signature or Authorization header
45pub struct ParsedHeader {
46    signature: String,
47    key_id: String,
48    headers: Vec<String>,
49    algorithm: Option<Algorithm>,
50    created: Option<SystemTime>,
51    expires: Option<SystemTime>,
52    parsed_at: SystemTime,
53}
54
55#[derive(Clone, Copy, Debug)]
56/// Algorithms that may be present in an HTTP Signature's `algorithm` field, but are considered
57/// deprecated due to security issues
58///
59/// Most of these are Deprecated solely because the presence of the algorithm's name in the request
60/// could be used to gain insight into ways to forge requests. This doesn't mean that using these
61/// algorithms to sign and verify requests is bad, it just means that stating which algorithm is in
62/// use is dangerous.  In the case of the SHA1 variants, they were deprecated for being weak
63/// hashes.
64///
65/// This library only produces HTTP Signatures with the "HS2019" algorithm type, and leaves
66/// deciding which algorithm to actually use to implementors
67pub enum DeprecatedAlgorithm {
68    /// HMAC SHA-1
69    HmacSha1,
70    /// HMAC SHA-256
71    HmacSha256,
72    /// HMAC SHA-384
73    HmacSha384,
74    /// HMAC SHA-512
75    HmacSha512,
76    /// RSA SHA-1
77    RsaSha1,
78    /// RSA SHA-256
79    RsaSha256,
80    /// RSA SHA-384
81    RsaSha384,
82    /// RSA SHA-512
83    RsaSha512,
84    /// ECDSA SHA-1
85    EcdsaSha1,
86    /// ECDSA SHA-256
87    EcdsaSha256,
88    /// ECDSA SHA-384
89    EcdsaSha384,
90    /// ECDSA SHA-512
91    EcdsaSha512,
92}
93
94#[derive(Clone, Debug)]
95/// Kinds of algorithms
96///
97/// This library knows about HS2019 as a supported algorithm, and any other algorithms are either
98/// unknown at the time of writing, or deprecated
99pub enum Algorithm {
100    /// The only officially supported algorithm from the current HTTP Signatures specification
101    Hs2019,
102    /// Algorithms that have been used historically, but are deprecated
103    Deprecated(DeprecatedAlgorithm),
104    /// Algorithms that may be used by custom implementations and are unknown to the spec
105    Unknown(String),
106}
107
108#[derive(Clone, Copy, Debug)]
109/// Indicator of which date field was checked for an expired error
110pub enum ExpiredField {
111    /// (expires) pseudo-header
112    Expires,
113
114    /// (created) pseudo-header
115    Created,
116
117    /// Date header
118    Date,
119}
120
121#[derive(Clone, Debug)]
122/// Kinds of errors for validating a request
123pub enum ValidateError {
124    /// The Authorization or Signature header is not present
125    Missing,
126    /// The request's `created` or `expires` field indicates it is too old to be valid
127    Expired {
128        /// Which field did the date come from
129        field: ExpiredField,
130
131        /// When did the signature expire
132        expires: SystemTime,
133
134        /// When was the signature checked
135        checked: SystemTime,
136    },
137}
138
139#[derive(Clone, Debug)]
140/// The error produced when parsing the HTTPT Signature fails, including the name of the field that
141/// was invalid.
142pub struct ParseSignatureError(&'static str);
143
144impl ParseSignatureError {
145    /// Get the name of the missing field
146    pub fn missing_field(&self) -> &'static str {
147        self.0
148    }
149}
150
151impl Unverified {
152    /// Get the Key ID from an Unverified type
153    ///
154    /// This is useful for looking up the proper verification key to verify the request
155    pub fn key_id(&self) -> &str {
156        &self.key_id
157    }
158
159    /// Get the Algorithm used in the request, if one is present
160    ///
161    /// If the algorithm is present and is not what an implementor expected, they should not
162    /// attempt to verify the signature
163    pub fn algorithm(&self) -> Option<&Algorithm> {
164        self.algorithm.as_ref()
165    }
166
167    /// Get the signing string used to create the signature
168    pub fn signing_string(&self) -> &str {
169        &self.signing_string
170    }
171
172    /// Get the signature itself
173    pub fn signature(&self) -> &str {
174        &self.signature
175    }
176
177    /// Verify the signature with the signature and the signing string
178    ///
179    /// ```rust,ignore
180    /// unverified.verify(|signature, signing_string| {
181    ///     let bytes = match base64::decode(signature) {
182    ///         Ok(bytes) => bytes,
183    ///         Err(_) => return false,
184    ///     };
185    ///
186    ///     public_key
187    ///         .verify(bytes, signing_string)
188    ///         .unwrap_or(false)
189    /// })
190    /// ```
191    pub fn verify<F, T>(&self, f: F) -> T
192    where
193        F: FnOnce(&str, &str) -> T,
194    {
195        (f)(&self.signature, &self.signing_string)
196    }
197}
198
199impl Unvalidated {
200    /// Validate parts of the header, ensuring that the provided dates don't indicate that it is
201    /// expired.
202    pub fn validate(self, expires_after: Duration) -> Result<Unverified, ValidateError> {
203        if let Some(expires) = self.expires {
204            if expires < self.parsed_at {
205                return Err(ValidateError::Expired {
206                    field: ExpiredField::Expires,
207                    expires,
208                    checked: self.parsed_at,
209                });
210            }
211        }
212        if let Some(created) = self.created {
213            if created + expires_after < self.parsed_at {
214                return Err(ValidateError::Expired {
215                    field: ExpiredField::Created,
216                    expires: created + expires_after,
217                    checked: self.parsed_at,
218                });
219            }
220        }
221
222        if let Some(date) = self.date {
223            if let Ok(datetime) = date.parse::<HttpDate>() {
224                let date = SystemTime::from(datetime);
225                if date + expires_after < self.parsed_at {
226                    return Err(ValidateError::Expired {
227                        field: ExpiredField::Date,
228                        expires: date + expires_after,
229                        checked: self.parsed_at,
230                    });
231                }
232            }
233        }
234
235        Ok(Unverified {
236            key_id: self.key_id,
237            algorithm: self.algorithm,
238            signing_string: self.signing_string,
239            signature: self.signature,
240        })
241    }
242}
243
244impl ParsedHeader {
245    /// Generate a Signing String from the header
246    pub fn into_unvalidated(
247        self,
248        method: &str,
249        path_and_query: &str,
250        headers: &mut BTreeMap<String, String>,
251        required_headers: HashSet<String>,
252    ) -> Result<Unvalidated, RequiredError> {
253        let date = headers.get("date").cloned();
254
255        let signing_string = build_signing_string(
256            method,
257            path_and_query,
258            self.created,
259            self.expires,
260            &self.headers,
261            headers,
262            required_headers,
263        )?;
264
265        Ok(Unvalidated {
266            key_id: self.key_id,
267            signature: self.signature,
268            parsed_at: self.parsed_at,
269            algorithm: self.algorithm,
270            created: self.created,
271            expires: self.expires,
272            date,
273            signing_string,
274        })
275    }
276}
277
278impl FromStr for ParsedHeader {
279    type Err = ParseSignatureError;
280
281    fn from_str(s: &str) -> Result<Self, Self::Err> {
282        let s = s.trim_start_matches("Signature").trim();
283        let mut hm: HashMap<String, String> = s
284            .split(',')
285            .filter_map(|part| {
286                let mut i = part.splitn(2, '=');
287
288                if let Some(key) = i.next() {
289                    if let Some(value) = i.next() {
290                        return Some((key.to_owned(), value.trim_matches('"').to_owned()));
291                    }
292                }
293                None
294            })
295            .collect();
296
297        Ok(ParsedHeader {
298            signature: hm
299                .remove(SIGNATURE_FIELD)
300                .ok_or(ParseSignatureError(SIGNATURE_FIELD))?,
301            key_id: hm
302                .remove(KEY_ID_FIELD)
303                .ok_or(ParseSignatureError(KEY_ID_FIELD))?,
304            headers: hm
305                .remove(HEADERS_FIELD)
306                .map(|h| h.split_whitespace().map(|s| s.to_owned()).collect())
307                .unwrap_or_else(|| vec![CREATED.to_owned()]),
308            algorithm: hm.remove(ALGORITHM_FIELD).map(Algorithm::from),
309            created: parse_time(&mut hm, CREATED_FIELD)?,
310            expires: parse_time(&mut hm, EXPIRES_FIELD)?,
311            parsed_at: SystemTime::now(),
312        })
313    }
314}
315
316fn parse_time(
317    hm: &mut HashMap<String, String>,
318    key: &'static str,
319) -> Result<Option<SystemTime>, ParseSignatureError> {
320    let r = hm
321        .remove(key)
322        .map(|s| parse_unix_timestamp(&s).map_err(|_| ParseSignatureError(key)));
323
324    match r {
325        Some(Ok(t)) => Ok(Some(t)),
326        Some(Err(e)) => Err(e),
327        None => Ok(None),
328    }
329}
330
331impl From<DeprecatedAlgorithm> for Algorithm {
332    fn from(d: DeprecatedAlgorithm) -> Algorithm {
333        Algorithm::Deprecated(d)
334    }
335}
336
337impl From<String> for Algorithm {
338    fn from(s: String) -> Self {
339        Algorithm::from(s.as_str())
340    }
341}
342
343impl From<&str> for Algorithm {
344    fn from(s: &str) -> Self {
345        match s {
346            "hmac-sha1" => DeprecatedAlgorithm::HmacSha1.into(),
347            "hmac-sha256" => DeprecatedAlgorithm::HmacSha256.into(),
348            "hmac-sha384" => DeprecatedAlgorithm::HmacSha384.into(),
349            "hmac-sha512" => DeprecatedAlgorithm::HmacSha512.into(),
350            "rsa-sha1" => DeprecatedAlgorithm::RsaSha1.into(),
351            "rsa-sha256" => DeprecatedAlgorithm::RsaSha256.into(),
352            "rsa-sha384" => DeprecatedAlgorithm::RsaSha384.into(),
353            "rsa-sha512" => DeprecatedAlgorithm::RsaSha512.into(),
354            "ecdsa-sha1" => DeprecatedAlgorithm::EcdsaSha1.into(),
355            "ecdsa-sha256" => DeprecatedAlgorithm::EcdsaSha256.into(),
356            "ecdsa-sha384" => DeprecatedAlgorithm::EcdsaSha384.into(),
357            "ecdsa-sha512" => DeprecatedAlgorithm::EcdsaSha512.into(),
358            "hs2019" => Algorithm::Hs2019,
359            other => Algorithm::Unknown(other.into()),
360        }
361    }
362}
363
364impl fmt::Display for DeprecatedAlgorithm {
365    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
366        let s = match self {
367            DeprecatedAlgorithm::HmacSha1 => "hmac-sha1",
368            DeprecatedAlgorithm::HmacSha256 => "hmac-sha256",
369            DeprecatedAlgorithm::HmacSha384 => "hmac-sha384",
370            DeprecatedAlgorithm::HmacSha512 => "hmac-sha512",
371            DeprecatedAlgorithm::RsaSha1 => "rsa-sha1",
372            DeprecatedAlgorithm::RsaSha256 => "rsa-sha256",
373            DeprecatedAlgorithm::RsaSha384 => "rsa-sha384",
374            DeprecatedAlgorithm::RsaSha512 => "rsa-sha512",
375            DeprecatedAlgorithm::EcdsaSha1 => "ecdsa-sha1",
376            DeprecatedAlgorithm::EcdsaSha256 => "ecdsa-sha256",
377            DeprecatedAlgorithm::EcdsaSha384 => "ecdsa-sha384",
378            DeprecatedAlgorithm::EcdsaSha512 => "ecdsa-sha512",
379        };
380
381        write!(f, "{}", s)
382    }
383}
384
385impl fmt::Display for Algorithm {
386    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
387        match self {
388            Algorithm::Hs2019 => write!(f, "hs2019"),
389            Algorithm::Deprecated(d) => d.fmt(f),
390            Algorithm::Unknown(other) => write!(f, "{}", other),
391        }
392    }
393}
394
395impl fmt::Display for ParseSignatureError {
396    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
397        write!(f, "Error when parsing {} from Http Signature", self.0)
398    }
399}
400
401impl Error for ParseSignatureError {
402    fn description(&self) -> &'static str {
403        "There was an error parsing the Http Signature"
404    }
405}
406
407impl fmt::Display for ExpiredField {
408    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
409        match self {
410            Self::Expires => write!(f, "expires pseudo-header"),
411            Self::Created => write!(f, "created pseudo-header"),
412            Self::Date => write!(f, "Date header"),
413        }
414    }
415}
416
417impl fmt::Display for ValidateError {
418    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
419        match *self {
420            ValidateError::Missing => write!(f, "Http Signature is missing"),
421            ValidateError::Expired {
422                field,
423                expires,
424                checked,
425            } => write!(
426                f,
427                "Http Signature is expired, checked {}, checked at {}, expired at {}",
428                field,
429                httpdate::fmt_http_date(checked),
430                httpdate::fmt_http_date(expires)
431            ),
432        }
433    }
434}
435
436impl Error for ValidateError {}
437
438#[cfg(test)]
439mod tests {
440    use super::ParsedHeader;
441    use crate::unix_timestamp;
442    use std::time::SystemTime;
443
444    #[test]
445    fn parses_header_succesfully_1() {
446        let time1 = unix_timestamp(SystemTime::now());
447        let time2 = unix_timestamp(SystemTime::now());
448
449        let h = format!(
450            r#"Signature keyId="my-key-id",algorithm="hs2019",created="{}",expires="{}",headers="(request-target) (created) (expires) date content-type",signature="blah blah blah""#,
451            time1, time2
452        );
453
454        parse_signature(&h)
455    }
456
457    #[test]
458    fn parses_header_succesfully_2() {
459        let time1 = unix_timestamp(SystemTime::now());
460        let time2 = unix_timestamp(SystemTime::now());
461
462        let h = format!(
463            r#"Signature keyId="my-key-id",algorithm="rsa-sha256",created="{}",expires="{}",signature="blah blah blah""#,
464            time1, time2
465        );
466
467        parse_signature(&h)
468    }
469
470    #[test]
471    fn parses_header_succesfully_3() {
472        let time1 = unix_timestamp(SystemTime::now());
473
474        let h = format!(
475            r#"Signature keyId="my-key-id",algorithm="rsa-sha256",created="{}",headers="(request-target) (created) date content-type",signature="blah blah blah""#,
476            time1
477        );
478
479        parse_signature(&h)
480    }
481
482    #[test]
483    fn parses_header_succesfully_4() {
484        let h = r#"Signature keyId="my-key-id",algorithm="rsa-sha256",headers="(request-target) date content-type",signature="blah blah blah""#;
485
486        parse_signature(h)
487    }
488
489    fn parse_signature(s: &str) {
490        let ph: ParsedHeader = s.parse().unwrap();
491        println!("{:?}", ph);
492    }
493}