1use 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)]
16pub struct Unverified {
21 key_id: String,
22 signature: String,
23 algorithm: Option<Algorithm>,
24 signing_string: String,
25}
26
27#[derive(Debug)]
28pub 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)]
44pub 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)]
56pub enum DeprecatedAlgorithm {
68 HmacSha1,
70 HmacSha256,
72 HmacSha384,
74 HmacSha512,
76 RsaSha1,
78 RsaSha256,
80 RsaSha384,
82 RsaSha512,
84 EcdsaSha1,
86 EcdsaSha256,
88 EcdsaSha384,
90 EcdsaSha512,
92}
93
94#[derive(Clone, Debug)]
95pub enum Algorithm {
100 Hs2019,
102 Deprecated(DeprecatedAlgorithm),
104 Unknown(String),
106}
107
108#[derive(Clone, Copy, Debug)]
109pub enum ExpiredField {
111 Expires,
113
114 Created,
116
117 Date,
119}
120
121#[derive(Clone, Debug)]
122pub enum ValidateError {
124 Missing,
126 Expired {
128 field: ExpiredField,
130
131 expires: SystemTime,
133
134 checked: SystemTime,
136 },
137}
138
139#[derive(Clone, Debug)]
140pub struct ParseSignatureError(&'static str);
143
144impl ParseSignatureError {
145 pub fn missing_field(&self) -> &'static str {
147 self.0
148 }
149}
150
151impl Unverified {
152 pub fn key_id(&self) -> &str {
156 &self.key_id
157 }
158
159 pub fn algorithm(&self) -> Option<&Algorithm> {
164 self.algorithm.as_ref()
165 }
166
167 pub fn signing_string(&self) -> &str {
169 &self.signing_string
170 }
171
172 pub fn signature(&self) -> &str {
174 &self.signature
175 }
176
177 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 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 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}