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