Skip to main content

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        mut required_headers: HashSet<String>,
252    ) -> Result<Unvalidated, RequiredError> {
253        let date = headers.get("date").cloned();
254
255        required_headers.extend(self.headers.iter().cloned());
256
257        let signing_string = build_signing_string(
258            method,
259            path_and_query,
260            self.created,
261            self.expires,
262            &self.headers,
263            headers,
264            required_headers,
265        )?;
266
267        Ok(Unvalidated {
268            key_id: self.key_id,
269            signature: self.signature,
270            parsed_at: self.parsed_at,
271            algorithm: self.algorithm,
272            created: self.created,
273            expires: self.expires,
274            date,
275            signing_string,
276        })
277    }
278}
279
280impl FromStr for ParsedHeader {
281    type Err = ParseSignatureError;
282
283    fn from_str(s: &str) -> Result<Self, Self::Err> {
284        let s = s.trim_start_matches("Signature").trim();
285        let mut hm: HashMap<String, String> = s
286            .split(',')
287            .filter_map(|part| {
288                let mut i = part.splitn(2, '=');
289
290                if let Some(key) = i.next() {
291                    if let Some(value) = i.next() {
292                        return Some((key.to_owned(), value.trim_matches('"').to_owned()));
293                    }
294                }
295                None
296            })
297            .collect();
298
299        Ok(ParsedHeader {
300            signature: hm
301                .remove(SIGNATURE_FIELD)
302                .ok_or(ParseSignatureError(SIGNATURE_FIELD))?,
303            key_id: hm
304                .remove(KEY_ID_FIELD)
305                .ok_or(ParseSignatureError(KEY_ID_FIELD))?,
306            headers: hm
307                .remove(HEADERS_FIELD)
308                .map(|h| h.split_whitespace().map(|s| s.to_owned()).collect())
309                .unwrap_or_else(|| vec![CREATED.to_owned()]),
310            algorithm: hm.remove(ALGORITHM_FIELD).map(Algorithm::from),
311            created: parse_time(&mut hm, CREATED_FIELD)?,
312            expires: parse_time(&mut hm, EXPIRES_FIELD)?,
313            parsed_at: SystemTime::now(),
314        })
315    }
316}
317
318fn parse_time(
319    hm: &mut HashMap<String, String>,
320    key: &'static str,
321) -> Result<Option<SystemTime>, ParseSignatureError> {
322    let r = hm
323        .remove(key)
324        .map(|s| parse_unix_timestamp(&s).map_err(|_| ParseSignatureError(key)));
325
326    match r {
327        Some(Ok(t)) => Ok(Some(t)),
328        Some(Err(e)) => Err(e),
329        None => Ok(None),
330    }
331}
332
333impl From<DeprecatedAlgorithm> for Algorithm {
334    fn from(d: DeprecatedAlgorithm) -> Algorithm {
335        Algorithm::Deprecated(d)
336    }
337}
338
339impl From<String> for Algorithm {
340    fn from(s: String) -> Self {
341        Algorithm::from(s.as_str())
342    }
343}
344
345impl From<&str> for Algorithm {
346    fn from(s: &str) -> Self {
347        match s {
348            "hmac-sha1" => DeprecatedAlgorithm::HmacSha1.into(),
349            "hmac-sha256" => DeprecatedAlgorithm::HmacSha256.into(),
350            "hmac-sha384" => DeprecatedAlgorithm::HmacSha384.into(),
351            "hmac-sha512" => DeprecatedAlgorithm::HmacSha512.into(),
352            "rsa-sha1" => DeprecatedAlgorithm::RsaSha1.into(),
353            "rsa-sha256" => DeprecatedAlgorithm::RsaSha256.into(),
354            "rsa-sha384" => DeprecatedAlgorithm::RsaSha384.into(),
355            "rsa-sha512" => DeprecatedAlgorithm::RsaSha512.into(),
356            "ecdsa-sha1" => DeprecatedAlgorithm::EcdsaSha1.into(),
357            "ecdsa-sha256" => DeprecatedAlgorithm::EcdsaSha256.into(),
358            "ecdsa-sha384" => DeprecatedAlgorithm::EcdsaSha384.into(),
359            "ecdsa-sha512" => DeprecatedAlgorithm::EcdsaSha512.into(),
360            "hs2019" => Algorithm::Hs2019,
361            other => Algorithm::Unknown(other.into()),
362        }
363    }
364}
365
366impl fmt::Display for DeprecatedAlgorithm {
367    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
368        let s = match self {
369            DeprecatedAlgorithm::HmacSha1 => "hmac-sha1",
370            DeprecatedAlgorithm::HmacSha256 => "hmac-sha256",
371            DeprecatedAlgorithm::HmacSha384 => "hmac-sha384",
372            DeprecatedAlgorithm::HmacSha512 => "hmac-sha512",
373            DeprecatedAlgorithm::RsaSha1 => "rsa-sha1",
374            DeprecatedAlgorithm::RsaSha256 => "rsa-sha256",
375            DeprecatedAlgorithm::RsaSha384 => "rsa-sha384",
376            DeprecatedAlgorithm::RsaSha512 => "rsa-sha512",
377            DeprecatedAlgorithm::EcdsaSha1 => "ecdsa-sha1",
378            DeprecatedAlgorithm::EcdsaSha256 => "ecdsa-sha256",
379            DeprecatedAlgorithm::EcdsaSha384 => "ecdsa-sha384",
380            DeprecatedAlgorithm::EcdsaSha512 => "ecdsa-sha512",
381        };
382
383        write!(f, "{}", s)
384    }
385}
386
387impl fmt::Display for Algorithm {
388    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
389        match self {
390            Algorithm::Hs2019 => write!(f, "hs2019"),
391            Algorithm::Deprecated(d) => d.fmt(f),
392            Algorithm::Unknown(other) => write!(f, "{}", other),
393        }
394    }
395}
396
397impl fmt::Display for ParseSignatureError {
398    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
399        write!(f, "Error when parsing {} from Http Signature", self.0)
400    }
401}
402
403impl Error for ParseSignatureError {
404    fn description(&self) -> &'static str {
405        "There was an error parsing the Http Signature"
406    }
407}
408
409impl fmt::Display for ExpiredField {
410    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
411        match self {
412            Self::Expires => write!(f, "expires pseudo-header"),
413            Self::Created => write!(f, "created pseudo-header"),
414            Self::Date => write!(f, "Date header"),
415        }
416    }
417}
418
419impl fmt::Display for ValidateError {
420    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
421        match *self {
422            ValidateError::Missing => write!(f, "Http Signature is missing"),
423            ValidateError::Expired {
424                field,
425                expires,
426                checked,
427            } => write!(
428                f,
429                "Http Signature is expired, checked {}, checked at {}, expired at {}",
430                field,
431                httpdate::fmt_http_date(checked),
432                httpdate::fmt_http_date(expires)
433            ),
434        }
435    }
436}
437
438impl Error for ValidateError {}
439
440#[cfg(test)]
441mod tests {
442    use super::ParsedHeader;
443    use crate::unix_timestamp;
444    use std::time::SystemTime;
445
446    #[test]
447    fn parses_header_succesfully_1() {
448        let time1 = unix_timestamp(SystemTime::now());
449        let time2 = unix_timestamp(SystemTime::now());
450
451        let h = format!(
452            r#"Signature keyId="my-key-id",algorithm="hs2019",created="{}",expires="{}",headers="(request-target) (created) (expires) date content-type",signature="blah blah blah""#,
453            time1, time2
454        );
455
456        parse_signature(&h)
457    }
458
459    #[test]
460    fn parses_header_succesfully_2() {
461        let time1 = unix_timestamp(SystemTime::now());
462        let time2 = unix_timestamp(SystemTime::now());
463
464        let h = format!(
465            r#"Signature keyId="my-key-id",algorithm="rsa-sha256",created="{}",expires="{}",signature="blah blah blah""#,
466            time1, time2
467        );
468
469        parse_signature(&h)
470    }
471
472    #[test]
473    fn parses_header_succesfully_3() {
474        let time1 = unix_timestamp(SystemTime::now());
475
476        let h = format!(
477            r#"Signature keyId="my-key-id",algorithm="rsa-sha256",created="{}",headers="(request-target) (created) date content-type",signature="blah blah blah""#,
478            time1
479        );
480
481        parse_signature(&h)
482    }
483
484    #[test]
485    fn parses_header_succesfully_4() {
486        let h = r#"Signature keyId="my-key-id",algorithm="rsa-sha256",headers="(request-target) date content-type",signature="blah blah blah""#;
487
488        parse_signature(h)
489    }
490
491    fn parse_signature(s: &str) {
492        let ph: ParsedHeader = s.parse().unwrap();
493        println!("{:?}", ph);
494    }
495}