1use std::{
4 fmt::{self, Display, Formatter},
5 str::FromStr,
6};
7
8use sha2::{Digest, Sha256};
9use str_reader::StringReader;
10use ttpkit::header::HeaderFieldValue;
11
12use crate::Error;
13
14#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
16pub enum DigestAlgorithm {
17 Md5,
18 Sha256,
19}
20
21impl DigestAlgorithm {
22 pub fn digest(&self, input: &[u8]) -> String {
24 match self {
25 Self::Md5 => format!("{:x}", md5::compute(input)),
26 Self::Sha256 => format!("{:x}", Sha256::digest(input)),
27 }
28 }
29}
30
31impl Display for DigestAlgorithm {
32 #[inline]
33 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
34 let s = match self {
35 Self::Md5 => "MD5",
36 Self::Sha256 => "SHA-256",
37 };
38
39 f.write_str(s)
40 }
41}
42
43impl FromStr for DigestAlgorithm {
44 type Err = Error;
45
46 fn from_str(s: &str) -> Result<Self, Self::Err> {
47 let res = match s {
48 "MD5" => Self::Md5,
49 "SHA-256" => Self::Sha256,
50 _ => {
51 return Err(Error::from_static_msg(
52 "unknown/unsupported digest algorithm",
53 ));
54 }
55 };
56
57 Ok(res)
58 }
59}
60
61#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
63pub enum QualityOfProtection {
64 Auth,
65 AuthInt,
66}
67
68impl Display for QualityOfProtection {
69 #[inline]
70 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
71 let s = match self {
72 Self::Auth => "auth",
73 Self::AuthInt => "auth-int",
74 };
75
76 f.write_str(s)
77 }
78}
79
80impl FromStr for QualityOfProtection {
81 type Err = Error;
82
83 fn from_str(s: &str) -> Result<Self, Self::Err> {
84 let res = match s {
85 "auth" => Self::Auth,
86 "auth-int" => Self::AuthInt,
87 _ => return Err(Error::from_static_msg("unknown qop")),
88 };
89
90 Ok(res)
91 }
92}
93
94#[derive(Clone)]
96pub struct DigestChallenge {
97 realm: String,
98 qops: Vec<QualityOfProtection>,
99 algorithm: DigestAlgorithm,
100 nonce: u64,
101}
102
103impl DigestChallenge {
104 pub fn new<R, Q>(realm: R, qops: Q, algorithm: DigestAlgorithm) -> Self
106 where
107 R: Into<String>,
108 Q: IntoIterator<Item = QualityOfProtection>,
109 {
110 let realm = realm.into();
111 let qops = qops.into_iter();
112
113 let nonce = rand::random();
114
115 Self {
116 realm,
117 qops: qops.collect(),
118 algorithm,
119 nonce,
120 }
121 }
122}
123
124impl Display for DigestChallenge {
125 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
126 write!(f, "Digest realm=\"{}\"", self.realm)?;
127
128 if !self.qops.is_empty() {
129 let mut qops = self.qops.iter();
130
131 write!(f, ",qop=\"{}", qops.next().unwrap())?;
132
133 for qop in qops {
134 write!(f, ", {qop}")?;
135 }
136
137 write!(f, "\"")?;
138 }
139
140 if self.algorithm != DigestAlgorithm::Md5 {
141 write!(f, ",algorithm={}", self.algorithm)?;
142 }
143
144 write!(f, ",nonce=\"{:016x}\"", self.nonce)
145 }
146}
147
148impl From<DigestChallenge> for HeaderFieldValue {
149 fn from(challenge: DigestChallenge) -> Self {
150 HeaderFieldValue::from(challenge.to_string())
151 }
152}
153
154pub struct DigestResponse {
156 realm: String,
157 uri: String,
158 username: String,
159 qop: Option<QualityOfProtection>,
160 nonce: String,
161 cnonce: Option<String>,
162 nc: Option<String>,
163 algorithm: DigestAlgorithm,
164 response: String,
165}
166
167impl DigestResponse {
168 #[inline]
170 pub fn realm(&self) -> &str {
171 &self.realm
172 }
173
174 #[inline]
176 pub fn username(&self) -> &str {
177 &self.username
178 }
179
180 #[inline]
182 pub fn algorithm(&self) -> DigestAlgorithm {
183 self.algorithm
184 }
185
186 pub fn verify<M>(&self, method: M, password_hash: &str) -> bool
193 where
194 M: Display,
195 {
196 self.verify_inner(&format!("{}:{}", method, self.uri), password_hash)
197 }
198
199 fn verify_inner(&self, a2: &str, password_hash: &str) -> bool {
206 if self.qop.unwrap_or(QualityOfProtection::Auth) == QualityOfProtection::AuthInt {
207 return false;
208 }
209
210 let a2_hash = self.algorithm.digest(a2.as_bytes());
211
212 let input = if let Some(qop) = self.qop {
213 let nc = self.nc.as_deref().unwrap_or("");
214 let cnonce = self.cnonce.as_deref().unwrap_or("");
215
216 format!(
217 "{}:{}:{}:{}:{}:{}",
218 password_hash, self.nonce, nc, cnonce, qop, a2_hash
219 )
220 } else {
221 format!("{}:{}:{}", password_hash, self.nonce, a2_hash)
222 };
223
224 let hash = self.algorithm.digest(input.as_bytes());
225
226 hash.eq_ignore_ascii_case(&self.response)
227 }
228}
229
230impl FromStr for DigestResponse {
231 type Err = Error;
232
233 fn from_str(s: &str) -> Result<Self, Self::Err> {
234 let mut reader = StringReader::new(s);
235
236 let auth_method = reader.read_word();
237
238 if !auth_method.eq_ignore_ascii_case("digest") {
239 return Err(Error::from_static_msg("not a Digest authorization"));
240 }
241
242 let mut realm = None;
243 let mut uri = None;
244 let mut username = None;
245 let mut qop = None;
246 let mut nonce = None;
247 let mut cnonce = None;
248 let mut nc = None;
249 let mut algorithm = None;
250 let mut response = None;
251
252 while let Some((name, value)) = parse_auth_param(&mut reader)? {
253 match &*name {
254 "realm" => realm = Some(value),
255 "uri" => uri = Some(value),
256 "username" => username = Some(value),
257 "qop" => qop = Some(value),
258 "nonce" => nonce = Some(value),
259 "cnonce" => cnonce = Some(value),
260 "nc" => nc = Some(value),
261 "algorithm" => algorithm = Some(value),
262 "response" => response = Some(value),
263 _ => (),
264 }
265 }
266
267 let res = Self {
268 realm: realm.ok_or_else(|| Error::from_static_msg("the realm parameter is missing"))?,
269 uri: uri.ok_or_else(|| Error::from_static_msg("the uri parameter is missing"))?,
270 username: username
271 .ok_or_else(|| Error::from_static_msg("the username parameter is missing"))?,
272 qop: qop.map(|qop| qop.parse()).transpose()?,
273 nonce: nonce.ok_or_else(|| Error::from_static_msg("the nonce parameter is missing"))?,
274 cnonce,
275 nc,
276 algorithm: algorithm.as_deref().unwrap_or("MD5").parse()?,
277 response: response
278 .ok_or_else(|| Error::from_static_msg("the response parameter is missing"))?,
279 };
280
281 if res.qop.is_some() {
282 if res.cnonce.is_none() {
283 return Err(Error::from_static_msg("the cnonce parameter is missing"));
284 } else if res.nc.is_none() {
285 return Err(Error::from_static_msg("the nc parameter is missing"));
286 }
287 } else if res.cnonce.is_some() {
288 return Err(Error::from_static_msg(
289 "the cnonce parameter is not expected",
290 ));
291 } else if res.nc.is_some() {
292 return Err(Error::from_static_msg("the nc parameter is not expected"));
293 }
294
295 Ok(res)
296 }
297}
298
299fn parse_auth_param(reader: &mut StringReader) -> Result<Option<(String, String)>, Error> {
301 let mut tmp = StringReader::new(reader.as_str());
302
303 while !tmp.is_empty() {
304 let name = tmp.read_until(|c| c == ',' || c == '=').trim();
305
306 if name.is_empty() {
307 match tmp.read_char() {
308 Ok(',') => continue,
309 Ok('=') => return Err(Error::from_static_msg("empty auth parameter name")),
310 Ok(_) => panic!("unexpected character"),
311 Err(_) => break,
312 }
313 }
314
315 tmp.match_char('=')
316 .map_err(|_| Error::from_static_msg("invalid auth parameter"))?;
317
318 tmp.skip_whitespace();
319
320 let value = if tmp.current_char() == Some('"') {
321 parse_quoted_string(&mut tmp)?
322 } else {
323 tmp.read_until(|c| c == ',').trim().into()
324 };
325
326 tmp.skip_whitespace();
327
328 if !tmp.is_empty() {
329 tmp.match_char(',')
330 .map_err(|_| Error::from_static_msg("invalid auth parameter"))?;
331 }
332
333 *reader = tmp;
334
335 return Ok(Some((name.to_ascii_lowercase(), value)));
336 }
337
338 *reader = tmp;
339
340 Ok(None)
341}
342
343fn parse_quoted_string(reader: &mut StringReader) -> Result<String, Error> {
345 reader.match_char('"').map_err(|_| {
346 Error::from_static_msg("quoted string does not start with the double quote sign")
347 })?;
348
349 let mut res = String::new();
350
351 while let Some(c) = reader.current_char() {
352 if c == '\\' {
353 reader.skip_char();
354
355 let c = reader
356 .current_char()
357 .ok_or_else(|| Error::from_static_msg("end of string within an escape sequence"))?;
358
359 res.push(c);
360 } else if c == '"' {
361 break;
362 } else {
363 res.push(c);
364 }
365
366 reader.skip_char();
367 }
368
369 reader.match_char('"').map_err(|_| {
370 Error::from_static_msg("quoted string does not end with the double quote sign")
371 })?;
372
373 Ok(res)
374}
375
376#[cfg(test)]
377mod tests {
378 use std::str::FromStr;
379
380 use str_reader::StringReader;
381
382 use super::{DigestResponse, parse_auth_param};
383
384 #[test]
385 fn test_parse_auth_params() {
386 let mut reader = StringReader::new(" foo = bar ");
387
388 match parse_auth_param(&mut reader) {
389 Ok(Some((name, value))) => {
390 assert_eq!(name, "foo");
391 assert_eq!(value, "bar");
392 }
393 v => panic!("unexpected result: {:?}", v),
394 }
395
396 assert!(matches!(parse_auth_param(&mut reader), Ok(None)));
397 assert!(reader.is_empty());
398
399 let mut reader = StringReader::new(" foo = bar, ");
400
401 match parse_auth_param(&mut reader) {
402 Ok(Some((name, value))) => {
403 assert_eq!(name, "foo");
404 assert_eq!(value, "bar");
405 }
406 v => panic!("unexpected result: {:?}", v),
407 }
408
409 assert!(matches!(parse_auth_param(&mut reader), Ok(None)));
410 assert!(reader.is_empty());
411
412 let mut reader = StringReader::new("foo=\" bar, barr \", aaa=bbb");
413
414 match parse_auth_param(&mut reader) {
415 Ok(Some((name, value))) => {
416 assert_eq!(name, "foo");
417 assert_eq!(value, " bar, barr ");
418 }
419 v => panic!("unexpected result: {:?}", v),
420 }
421
422 match parse_auth_param(&mut reader) {
423 Ok(Some((name, value))) => {
424 assert_eq!(name, "aaa");
425 assert_eq!(value, "bbb");
426 }
427 v => panic!("unexpected result: {:?}", v),
428 }
429
430 assert!(matches!(parse_auth_param(&mut reader), Ok(None)));
431 assert!(reader.is_empty());
432
433 let mut reader = StringReader::new("foo=bar,, , aaa=bbb");
434
435 match parse_auth_param(&mut reader) {
436 Ok(Some((name, value))) => {
437 assert_eq!(name, "foo");
438 assert_eq!(value, "bar");
439 }
440 v => panic!("unexpected result: {:?}", v),
441 }
442
443 match parse_auth_param(&mut reader) {
444 Ok(Some((name, value))) => {
445 assert_eq!(name, "aaa");
446 assert_eq!(value, "bbb");
447 }
448 v => panic!("unexpected result: {:?}", v),
449 }
450
451 assert!(matches!(parse_auth_param(&mut reader), Ok(None)));
452 assert!(reader.is_empty());
453
454 let mut reader = StringReader::new(" = bar ");
455
456 assert!(parse_auth_param(&mut reader).is_err());
457
458 let mut reader = StringReader::new(" foo ");
459
460 assert!(parse_auth_param(&mut reader).is_err());
461
462 let mut reader = StringReader::new(" foo, ");
463
464 assert!(parse_auth_param(&mut reader).is_err());
465
466 let mut reader = StringReader::new("foo=\"bar\\");
467
468 assert!(parse_auth_param(&mut reader).is_err());
469
470 let mut reader = StringReader::new("foo=\"bar");
471
472 assert!(parse_auth_param(&mut reader).is_err());
473
474 let mut reader = StringReader::new("foo=\"bar, barr\" aaa, bbb=ccc");
475
476 assert!(parse_auth_param(&mut reader).is_err());
477 }
478
479 #[test]
480 fn test_parse_digest_response() {
481 let response = "Digest realm=foo, username=user, \
482 uri=\"http://1.1.1.1/\", qop=auth, algorithm=MD5, \
483 nonce=1, cnonce=1, nc=1, response=foo";
484
485 let response = DigestResponse::from_str(response);
486
487 assert!(response.is_ok());
488 }
489}