1 use crate::error::*;
2 use crate::mac::Mac;
3 use base64::display::Base64Display;
4 use std::fmt;
5 use std::str::FromStr;
6 use std::time::{Duration, SystemTime, UNIX_EPOCH};
7 /// Representation of a Hawk `Authorization` header value (the part following "Hawk ").
8 ///
9 /// Headers can be derived from strings using the `FromStr` trait, and formatted into a
10 /// string using the `fmt_header` method.
11 ///
12 /// All fields are optional, although for specific purposes some fields must be present.
13 #[derive(Clone, PartialEq, Debug)]
14 pub struct Header {
15     pub id: Option<String>,
16     pub ts: Option<SystemTime>,
17     pub nonce: Option<String>,
18     pub mac: Option<Mac>,
19     pub ext: Option<String>,
20     pub hash: Option<Vec<u8>>,
21     pub app: Option<String>,
22     pub dlg: Option<String>,
23 }
24 
25 impl Header {
26     /// Create a new Header with the full set of Hawk fields.
27     ///
28     /// This is a low-level function. Headers are more often created from Requests or Responses.
29     ///
30     /// Note that none of the string-formatted header components can contain the character `\"`.
new<S>( id: Option<S>, ts: Option<SystemTime>, nonce: Option<S>, mac: Option<Mac>, ext: Option<S>, hash: Option<Vec<u8>>, app: Option<S>, dlg: Option<S>, ) -> Result<Header> where S: Into<String>,31     pub fn new<S>(
32         id: Option<S>,
33         ts: Option<SystemTime>,
34         nonce: Option<S>,
35         mac: Option<Mac>,
36         ext: Option<S>,
37         hash: Option<Vec<u8>>,
38         app: Option<S>,
39         dlg: Option<S>,
40     ) -> Result<Header>
41     where
42         S: Into<String>,
43     {
44         Ok(Header {
45             id: Header::check_component(id)?,
46             ts,
47             nonce: Header::check_component(nonce)?,
48             mac,
49             ext: Header::check_component(ext)?,
50             hash,
51             app: Header::check_component(app)?,
52             dlg: Header::check_component(dlg)?,
53         })
54     }
55 
56     /// Check a header component for validity.
check_component<S>(value: Option<S>) -> Result<Option<String>> where S: Into<String>,57     fn check_component<S>(value: Option<S>) -> Result<Option<String>>
58     where
59         S: Into<String>,
60     {
61         if let Some(value) = value {
62             let value = value.into();
63             if value.contains('\"') {
64                 return Err(Error::HeaderParseError(
65                     "Hawk headers cannot contain `\\`".into(),
66                 ));
67             }
68             Ok(Some(value))
69         } else {
70             Ok(None)
71         }
72     }
73 
74     /// Format the header for transmission in an Authorization header, omitting the `"Hawk "`
75     /// prefix.
fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result76     pub fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
77         let mut sep = "";
78         if let Some(ref id) = self.id {
79             write!(f, "{}id=\"{}\"", sep, id)?;
80             sep = ", ";
81         }
82         if let Some(ref ts) = self.ts {
83             write!(
84                 f,
85                 "{}ts=\"{}\"",
86                 sep,
87                 ts.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs()
88             )?;
89             sep = ", ";
90         }
91         if let Some(ref nonce) = self.nonce {
92             write!(f, "{}nonce=\"{}\"", sep, nonce)?;
93             sep = ", ";
94         }
95         if let Some(ref mac) = self.mac {
96             write!(
97                 f,
98                 "{}mac=\"{}\"",
99                 sep,
100                 Base64Display::with_config(mac, base64::STANDARD)
101             )?;
102             sep = ", ";
103         }
104         if let Some(ref ext) = self.ext {
105             write!(f, "{}ext=\"{}\"", sep, ext)?;
106             sep = ", ";
107         }
108         if let Some(ref hash) = self.hash {
109             write!(
110                 f,
111                 "{}hash=\"{}\"",
112                 sep,
113                 Base64Display::with_config(hash, base64::STANDARD)
114             )?;
115             sep = ", ";
116         }
117         if let Some(ref app) = self.app {
118             write!(f, "{}app=\"{}\"", sep, app)?;
119             sep = ", ";
120         }
121         if let Some(ref dlg) = self.dlg {
122             write!(f, "{}dlg=\"{}\"", sep, dlg)?;
123         }
124         Ok(())
125     }
126 }
127 
128 impl fmt::Display for Header {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result129     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130         self.fmt_header(f)
131     }
132 }
133 
134 impl FromStr for Header {
135     type Err = Error;
from_str(s: &str) -> Result<Header>136     fn from_str(s: &str) -> Result<Header> {
137         let mut p = &s[..];
138 
139         // Required attributes
140         let mut id: Option<&str> = None;
141         let mut ts: Option<SystemTime> = None;
142         let mut nonce: Option<&str> = None;
143         let mut mac: Option<Vec<u8>> = None;
144         // Optional attributes
145         let mut hash: Option<Vec<u8>> = None;
146         let mut ext: Option<&str> = None;
147         let mut app: Option<&str> = None;
148         let mut dlg: Option<&str> = None;
149 
150         while !p.is_empty() {
151             // Skip whitespace and commas used as separators
152             p = p.trim_start_matches(|c| c == ',' || char::is_whitespace(c));
153             // Find first '=' which delimits attribute name from value
154             let assign_end = p
155                 .find('=')
156                 .ok_or_else(|| Error::HeaderParseError("Expected '='".into()))?;
157             let attr = &p[..assign_end].trim();
158             if p.len() < assign_end + 1 {
159                 return Err(Error::HeaderParseError(
160                     "Missing right hand side of =".into(),
161                 ));
162             }
163             p = (&p[assign_end + 1..]).trim_start();
164             if !p.starts_with('\"') {
165                 return Err(Error::HeaderParseError("Expected opening quote".into()));
166             }
167             p = &p[1..];
168             // We have poor RFC 7235 compliance here as we ought to support backslash
169             // escaped characters, but hawk doesn't allow this we won't either.  All
170             // strings must be surrounded by ".." and contain no such characters.
171             let end = p.find('\"');
172             let val_end =
173                 end.ok_or_else(|| Error::HeaderParseError("Expected closing quote".into()))?;
174             let val = &p[..val_end];
175             match *attr {
176                 "id" => id = Some(val),
177                 "ts" => {
178                     let epoch = u64::from_str(val)
179                         .map_err(|_| Error::HeaderParseError("Error parsing `ts` field".into()))?;
180                     ts = Some(UNIX_EPOCH + Duration::new(epoch, 0));
181                 }
182                 "mac" => {
183                     mac = Some(base64::decode(val).map_err(|_| {
184                         Error::HeaderParseError("Error parsing `mac` field".into())
185                     })?);
186                 }
187                 "nonce" => nonce = Some(val),
188                 "ext" => ext = Some(val),
189                 "hash" => {
190                     hash = Some(base64::decode(val).map_err(|_| {
191                         Error::HeaderParseError("Error parsing `hash` field".into())
192                     })?);
193                 }
194                 "app" => app = Some(val),
195                 "dlg" => dlg = Some(val),
196                 _ => {
197                     return Err(Error::HeaderParseError(format!(
198                         "Invalid Hawk field {}",
199                         *attr
200                     )))
201                 }
202             };
203             // Break if we are at end of string, otherwise skip separator
204             if p.len() < val_end + 1 {
205                 break;
206             }
207             p = p[val_end + 1..].trim_start();
208         }
209 
210         Ok(Header {
211             id: match id {
212                 Some(id) => Some(id.to_string()),
213                 None => None,
214             },
215             ts,
216             nonce: match nonce {
217                 Some(nonce) => Some(nonce.to_string()),
218                 None => None,
219             },
220             mac: match mac {
221                 Some(mac) => Some(Mac::from(mac)),
222                 None => None,
223             },
224             ext: match ext {
225                 Some(ext) => Some(ext.to_string()),
226                 None => None,
227             },
228             hash,
229             app: match app {
230                 Some(app) => Some(app.to_string()),
231                 None => None,
232             },
233             dlg: match dlg {
234                 Some(dlg) => Some(dlg.to_string()),
235                 None => None,
236             },
237         })
238     }
239 }
240 
241 #[cfg(test)]
242 mod test {
243     use super::Header;
244     use crate::mac::Mac;
245     use std::str::FromStr;
246     use std::time::{Duration, UNIX_EPOCH};
247 
248     #[test]
illegal_id()249     fn illegal_id() {
250         assert!(Header::new(
251             Some("ab\"cdef"),
252             Some(UNIX_EPOCH + Duration::new(1234, 0)),
253             Some("nonce"),
254             Some(Mac::from(vec![])),
255             Some("ext"),
256             None,
257             None,
258             None
259         )
260         .is_err());
261     }
262 
263     #[test]
illegal_nonce()264     fn illegal_nonce() {
265         assert!(Header::new(
266             Some("abcdef"),
267             Some(UNIX_EPOCH + Duration::new(1234, 0)),
268             Some("no\"nce"),
269             Some(Mac::from(vec![])),
270             Some("ext"),
271             None,
272             None,
273             None
274         )
275         .is_err());
276     }
277 
278     #[test]
illegal_ext()279     fn illegal_ext() {
280         assert!(Header::new(
281             Some("abcdef"),
282             Some(UNIX_EPOCH + Duration::new(1234, 0)),
283             Some("nonce"),
284             Some(Mac::from(vec![])),
285             Some("ex\"t"),
286             None,
287             None,
288             None
289         )
290         .is_err());
291     }
292 
293     #[test]
illegal_app()294     fn illegal_app() {
295         assert!(Header::new(
296             Some("abcdef"),
297             Some(UNIX_EPOCH + Duration::new(1234, 0)),
298             Some("nonce"),
299             Some(Mac::from(vec![])),
300             None,
301             None,
302             Some("a\"pp"),
303             None
304         )
305         .is_err());
306     }
307 
308     #[test]
illegal_dlg()309     fn illegal_dlg() {
310         assert!(Header::new(
311             Some("abcdef"),
312             Some(UNIX_EPOCH + Duration::new(1234, 0)),
313             Some("nonce"),
314             Some(Mac::from(vec![])),
315             None,
316             None,
317             None,
318             Some("d\"lg")
319         )
320         .is_err());
321     }
322 
323     #[test]
from_str()324     fn from_str() {
325         let s = Header::from_str(
326             "id=\"dh37fgj492je\", ts=\"1353832234\", \
327              nonce=\"j4h3g2\", ext=\"some-app-ext-data\", \
328              mac=\"6R4rV5iE+NPoym+WwjeHzjAGXUtLNIxmo1vpMofpLAE=\", \
329              hash=\"6R4rV5iE+NPoym+WwjeHzjAGXUtLNIxmo1vpMofpLAE=\", \
330              app=\"my-app\", dlg=\"my-authority\"",
331         )
332         .unwrap();
333         assert!(s.id == Some("dh37fgj492je".to_string()));
334         assert!(s.ts == Some(UNIX_EPOCH + Duration::new(1353832234, 0)));
335         assert!(s.nonce == Some("j4h3g2".to_string()));
336         assert!(
337             s.mac
338                 == Some(Mac::from(vec![
339                     233, 30, 43, 87, 152, 132, 248, 211, 232, 202, 111, 150, 194, 55, 135, 206, 48,
340                     6, 93, 75, 75, 52, 140, 102, 163, 91, 233, 50, 135, 233, 44, 1
341                 ]))
342         );
343         assert!(s.ext == Some("some-app-ext-data".to_string()));
344         assert!(s.app == Some("my-app".to_string()));
345         assert!(s.dlg == Some("my-authority".to_string()));
346     }
347 
348     #[test]
from_str_invalid_mac()349     fn from_str_invalid_mac() {
350         let r = Header::from_str(
351             "id=\"dh37fgj492je\", ts=\"1353832234\", \
352              nonce=\"j4h3g2\", ext=\"some-app-ext-data\", \
353              mac=\"6!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!AE=\", \
354              app=\"my-app\", dlg=\"my-authority\"",
355         );
356         assert!(r.is_err());
357     }
358 
359     #[test]
from_str_no_field()360     fn from_str_no_field() {
361         let s = Header::from_str("").unwrap();
362         assert!(s.id == None);
363         assert!(s.ts == None);
364         assert!(s.nonce == None);
365         assert!(s.mac == None);
366         assert!(s.ext == None);
367         assert!(s.app == None);
368         assert!(s.dlg == None);
369     }
370 
371     #[test]
from_str_few_field()372     fn from_str_few_field() {
373         let s = Header::from_str(
374             "id=\"xyz\", ts=\"1353832234\", \
375              nonce=\"abc\", \
376              mac=\"6R4rV5iE+NPoym+WwjeHzjAGXUtLNIxmo1vpMofpLAE=\"",
377         )
378         .unwrap();
379         assert!(s.id == Some("xyz".to_string()));
380         assert!(s.ts == Some(UNIX_EPOCH + Duration::new(1353832234, 0)));
381         assert!(s.nonce == Some("abc".to_string()));
382         assert!(
383             s.mac
384                 == Some(Mac::from(vec![
385                     233, 30, 43, 87, 152, 132, 248, 211, 232, 202, 111, 150, 194, 55, 135, 206, 48,
386                     6, 93, 75, 75, 52, 140, 102, 163, 91, 233, 50, 135, 233, 44, 1
387                 ]))
388         );
389         assert!(s.ext == None);
390         assert!(s.app == None);
391         assert!(s.dlg == None);
392     }
393 
394     #[test]
from_str_messy()395     fn from_str_messy() {
396         let s = Header::from_str(
397             ", id  =  \"dh37fgj492je\", ts=\"1353832234\", \
398              nonce=\"j4h3g2\"  , , ext=\"some-app-ext-data\", \
399              mac=\"6R4rV5iE+NPoym+WwjeHzjAGXUtLNIxmo1vpMofpLAE=\"",
400         )
401         .unwrap();
402         assert!(s.id == Some("dh37fgj492je".to_string()));
403         assert!(s.ts == Some(UNIX_EPOCH + Duration::new(1353832234, 0)));
404         assert!(s.nonce == Some("j4h3g2".to_string()));
405         assert!(
406             s.mac
407                 == Some(Mac::from(vec![
408                     233, 30, 43, 87, 152, 132, 248, 211, 232, 202, 111, 150, 194, 55, 135, 206, 48,
409                     6, 93, 75, 75, 52, 140, 102, 163, 91, 233, 50, 135, 233, 44, 1
410                 ]))
411         );
412         assert!(s.ext == Some("some-app-ext-data".to_string()));
413         assert!(s.app == None);
414         assert!(s.dlg == None);
415     }
416 
417     #[test]
to_str_no_fields()418     fn to_str_no_fields() {
419         // must supply a type for S, since it is otherwise unused
420         let s = Header::new::<String>(None, None, None, None, None, None, None, None).unwrap();
421         let formatted = format!("{}", s);
422         println!("got: {}", formatted);
423         assert!(formatted == "")
424     }
425 
426     #[test]
to_str_few_fields()427     fn to_str_few_fields() {
428         let s = Header::new(
429             Some("dh37fgj492je"),
430             Some(UNIX_EPOCH + Duration::new(1353832234, 0)),
431             Some("j4h3g2"),
432             Some(Mac::from(vec![
433                 8, 35, 182, 149, 42, 111, 33, 192, 19, 22, 94, 43, 118, 176, 65, 69, 86, 4, 156,
434                 184, 85, 107, 249, 242, 172, 200, 66, 209, 57, 63, 38, 83,
435             ])),
436             None,
437             None,
438             None,
439             None,
440         )
441         .unwrap();
442         let formatted = format!("{}", s);
443         println!("got: {}", formatted);
444         assert!(
445             formatted
446                 == "id=\"dh37fgj492je\", ts=\"1353832234\", nonce=\"j4h3g2\", \
447                     mac=\"CCO2lSpvIcATFl4rdrBBRVYEnLhVa/nyrMhC0Tk/JlM=\""
448         )
449     }
450 
451     #[test]
to_str_maximal()452     fn to_str_maximal() {
453         let s = Header::new(
454             Some("dh37fgj492je"),
455             Some(UNIX_EPOCH + Duration::new(1353832234, 0)),
456             Some("j4h3g2"),
457             Some(Mac::from(vec![
458                 8, 35, 182, 149, 42, 111, 33, 192, 19, 22, 94, 43, 118, 176, 65, 69, 86, 4, 156,
459                 184, 85, 107, 249, 242, 172, 200, 66, 209, 57, 63, 38, 83,
460             ])),
461             Some("my-ext-value"),
462             Some(vec![1, 2, 3, 4]),
463             Some("my-app"),
464             Some("my-dlg"),
465         )
466         .unwrap();
467         let formatted = format!("{}", s);
468         println!("got: {}", formatted);
469         assert!(
470             formatted
471                 == "id=\"dh37fgj492je\", ts=\"1353832234\", nonce=\"j4h3g2\", \
472                     mac=\"CCO2lSpvIcATFl4rdrBBRVYEnLhVa/nyrMhC0Tk/JlM=\", ext=\"my-ext-value\", \
473                     hash=\"AQIDBA==\", app=\"my-app\", dlg=\"my-dlg\""
474         )
475     }
476 
477     #[test]
round_trip()478     fn round_trip() {
479         let s = Header::new(
480             Some("dh37fgj492je"),
481             Some(UNIX_EPOCH + Duration::new(1353832234, 0)),
482             Some("j4h3g2"),
483             Some(Mac::from(vec![
484                 8, 35, 182, 149, 42, 111, 33, 192, 19, 22, 94, 43, 118, 176, 65, 69, 86, 4, 156,
485                 184, 85, 107, 249, 242, 172, 200, 66, 209, 57, 63, 38, 83,
486             ])),
487             Some("my-ext-value"),
488             Some(vec![1, 2, 3, 4]),
489             Some("my-app"),
490             Some("my-dlg"),
491         )
492         .unwrap();
493         let formatted = format!("{}", s);
494         println!("got: {}", s);
495         let s2 = Header::from_str(&formatted).unwrap();
496         assert!(s2 == s);
497     }
498 }
499