1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
//! Types and methods to verify a signature or authorization header
use chrono::{DateTime, Duration, TimeZone, Utc};
use std::{
    collections::{BTreeMap, HashMap},
    error::Error,
    fmt,
    str::FromStr,
};

use crate::{
    build_signing_string, ALGORITHM_FIELD, CREATED, CREATED_FIELD, EXPIRES_FIELD, HEADERS_FIELD,
    KEY_ID_FIELD, SIGNATURE_FIELD,
};

#[derive(Debug)]
/// 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
pub struct Unverified {
    key_id: String,
    signature: String,
    algorithm: Option<Algorithm>,
    signing_string: String,
}

#[derive(Debug)]
/// 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
pub struct Unvalidated {
    pub(crate) key_id: String,
    pub(crate) signature: String,
    pub(crate) algorithm: Option<Algorithm>,
    pub(crate) created: Option<DateTime<Utc>>,
    pub(crate) expires: Option<DateTime<Utc>>,
    pub(crate) parsed_at: DateTime<Utc>,
    pub(crate) signing_string: String,
}

#[derive(Debug)]
/// The successful result of parsing a Signature or Authorization header
pub struct ParsedHeader {
    signature: String,
    key_id: String,
    headers: Vec<String>,
    algorithm: Option<Algorithm>,
    created: Option<DateTime<Utc>>,
    expires: Option<DateTime<Utc>>,
    parsed_at: DateTime<Utc>,
}

#[derive(Clone, Copy, Debug)]
/// 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
pub enum DeprecatedAlgorithm {
    /// HMAC SHA-1
    HmacSha1,
    /// HMAC SHA-256
    HmacSha256,
    /// HMAC SHA-384
    HmacSha384,
    /// HMAC SHA-512
    HmacSha512,
    /// RSA SHA-1
    RsaSha1,
    /// RSA SHA-256
    RsaSha256,
    /// RSA SHA-384
    RsaSha384,
    /// RSA SHA-512
    RsaSha512,
    /// ECDSA SHA-1
    EcdsaSha1,
    /// ECDSA SHA-256
    EcdsaSha256,
    /// ECDSA SHA-384
    EcdsaSha384,
    /// ECDSA SHA-512
    EcdsaSha512,
}

#[derive(Clone, Debug)]
/// 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
pub enum Algorithm {
    /// The only officially supported algorithm from the current HTTP Signatures specification
    Hs2019,
    /// Algorithms that have been used historically, but are deprecated
    Deprecated(DeprecatedAlgorithm),
    /// Algorithms that may be used by custom implementations and are unknown to the spec
    Unknown(String),
}

#[derive(Clone, Debug)]
/// Kinds of errors for validating a request
pub enum ValidateError {
    /// The Authorization or Signature header is not present
    Missing,
    /// The request's `created` or `expires` field indicates it is too old to be valid
    Expired,
}

#[derive(Clone, Debug)]
/// The error produced when parsing the HTTPT Signature fails, including the name of the field that
/// was invalid.
pub struct ParseSignatureError(&'static str);

impl Unverified {
    /// Get the Key ID from an Unverified type
    ///
    /// This is useful for looking up the proper verification key to verify the request
    pub fn key_id(&self) -> &str {
        &self.key_id
    }

    /// 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
    pub fn algorithm(&self) -> Option<&Algorithm> {
        self.algorithm.as_ref()
    }

    /// 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)
    /// })
    /// ```
    pub fn verify<F, T>(&self, f: F) -> T
    where
        F: FnOnce(&str, &str) -> T,
    {
        (f)(&self.signature, &self.signing_string)
    }
}

impl Unvalidated {
    /// Validate parts of the header, ensuring that the provided dates don't indicate that it is
    /// expired.
    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);
            }
        }
        if let Some(created) = self.created {
            if created + expires_after < self.parsed_at {
                return Err(ValidateError::Expired);
            }
        }

        Ok(Unverified {
            key_id: self.key_id,
            algorithm: self.algorithm,
            signing_string: self.signing_string,
            signature: self.signature,
        })
    }
}

impl ParsedHeader {
    /// Generate a Signing String from the header
    pub fn into_unvalidated(
        self,
        method: &str,
        path_and_query: &str,
        headers: &mut BTreeMap<String, String>,
    ) -> Unvalidated {
        let signing_string = build_signing_string(
            method,
            path_and_query,
            self.created,
            self.expires,
            &self.headers,
            headers,
        );

        Unvalidated {
            key_id: self.key_id,
            signature: self.signature,
            parsed_at: self.parsed_at,
            algorithm: self.algorithm,
            created: self.created,
            expires: self.expires,
            signing_string,
        }
    }
}

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| {
                let mut i = part.splitn(2, "=");

                if let Some(key) = i.next() {
                    if let Some(value) = i.next() {
                        return Some((key.to_owned(), value.trim_matches('"').to_owned()));
                    }
                }
                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()]),
            algorithm: hm.remove(ALGORITHM_FIELD).map(|s| Algorithm::from(s)),
            created: parse_time(&mut hm, CREATED_FIELD)?,
            expires: parse_time(&mut hm, EXPIRES_FIELD)?,
            parsed_at: Utc::now(),
        })
    }
}

fn parse_time(
    hm: &mut HashMap<String, String>,
    key: &'static str,
) -> Result<Option<DateTime<Utc>>, ParseSignatureError> {
    let r = hm.remove(key).map(|s| {
        Utc.datetime_from_str(&s, "%s")
            .map_err(|_| ParseSignatureError(key))
    });

    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 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 ValidateError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            ValidateError::Missing => write!(f, "Http Signature is missing"),
            ValidateError::Expired => write!(f, "Http Signature is expired"),
        }
    }
}

impl Error for ValidateError {
    fn description(&self) -> &'static str {
        match *self {
            ValidateError::Missing => "Http Signature is missing",
            ValidateError::Expired => "Http Signature is expired",
        }
    }
}

#[cfg(test)]
mod tests {
    use chrono::Utc;

    use super::ParsedHeader;

    #[test]
    fn parses_header_succesfully_1() {
        let time1 = Utc::now().timestamp();
        let time2 = Utc::now().timestamp();

        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);

        parse_signature(&h)
    }

    #[test]
    fn parses_header_succesfully_2() {
        let time1 = Utc::now().timestamp();
        let time2 = Utc::now().timestamp();

        let h = format!(r#"Signature keyId="my-key-id",algorithm="rsa-sha256",created="{}",expires="{}",signature="blah blah blah""#, time1, time2);

        parse_signature(&h)
    }

    #[test]
    fn parses_header_succesfully_3() {
        let time1 = Utc::now().timestamp();

        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);

        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);
    }
}