digest_auth/
lib.rs

1//! This crate implements Digest Auth headers as specified by IETF RFCs 2069, 2617, and 7616.
2//! It can be used in conjunction with libraries like reqwest to access e.g. IP cameras
3//! that use this authentication scheme.
4//!
5//! This library was written for the http client, but since the algorithm is symmetrical,
6//! it can be used by the server side as well. Server-side nonce management (generation, timed
7//! expiry) and authorization checking is left to user's implementation.
8//!
9//! The `AuthorizationHeader::digest()` method can be used server-side to replicate the
10//! password/body hash; then just check if the computed digest matches what the user sent.
11//!
12//! # Examples
13//!
14//! Basic usage:
15//!
16//! ```
17//! use digest_auth::AuthContext;
18//!
19//! // Value from the WWW-Authenticate HTTP header (usually in a HTTP 401 response)
20//! let www_authenticate = r#"Digest realm="http-auth@example.org", qop="auth, auth-int", algorithm=MD5, nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS""#;
21//!
22//! // Prepare an authorization context. Note that this is a GET request. There are different
23//! // constructors available for POST or other request types. You can re-use it, but
24//! // it's cheap to create a fresh one each time, as the struct uses references only.
25//! let mut context = AuthContext::new("Mufasa", "Circle of Life", "/dir/index.html");
26//! // For this test, we inject a custom cnonce. It's generated for you otherwise
27//! // - you don't need `mut` in that case and needn't worry about this at all.
28//! context.set_custom_cnonce("f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ");
29//!
30//! // Parse the prompt header. You can inspect the parsed object, its fields are public.
31//! let mut prompt = digest_auth::parse(www_authenticate).unwrap();
32//!
33//! // Compute a value for the Authorization header that we'll send back to the server
34//! let answer = prompt.respond(&context).unwrap().to_string();
35//! assert_eq!(answer, r#"Digest username="Mufasa", realm="http-auth@example.org", nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", uri="/dir/index.html", qop=auth, nc=00000001, cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ", response="8ca523f5e9506fed4657c9700eebdbec", opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS", algorithm=MD5"#);
36//!
37//! // The `prompt` variable is mutable, because the 'nc' counter (nonce reuse count)
38//! // is inside the struct and updated automatically.
39//!
40//! // You can re-use it for subsequent requests, assuming the server allows nonce re-use.
41//! // Some poorly implemented servers will reject it and give you 401 again, in which case
42//! // you should parse the new "WWW-Authenticate" header and use that instead.
43//!
44//! let answer2 = prompt.respond(&context).unwrap().to_string();
45//! // notice how the 'response' field changed - the 'nc' counter is included in the hash
46//! assert_eq!(answer2, r#"Digest username="Mufasa", realm="http-auth@example.org", nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", uri="/dir/index.html", qop=auth, nc=00000002, cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ", response="4b5d595ecf2db9df612ea5b45cd97101", opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS", algorithm=MD5"#);
47//! ```
48
49mod digest;
50mod enums;
51mod error;
52
53pub use error::{Error, Result};
54
55pub use crate::digest::{AuthContext, AuthorizationHeader, WwwAuthenticateHeader};
56
57pub use crate::enums::*;
58
59/// Parse the WWW-Authorization header value.
60/// It's just a convenience method to call [`WwwAuthenticateHeader::parse()`](struct.WwwAuthenticateHeader.html#method.parse).
61pub fn parse(www_authorize: &str) -> Result<WwwAuthenticateHeader> {
62    WwwAuthenticateHeader::parse(www_authorize)
63}
64
65#[cfg(test)]
66mod test {
67    use crate::{AuthContext, Error};
68
69    #[test]
70    fn test_parse_respond() {
71        let src = r#"
72    Digest
73       realm="http-auth@example.org",
74       qop="auth, auth-int",
75       algorithm=MD5,
76       nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",
77       opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"
78    "#;
79
80        let mut context = AuthContext::new("Mufasa", "Circle of Life", "/dir/index.html");
81        context.set_custom_cnonce("f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ");
82
83        let mut prompt = crate::parse(src).unwrap();
84        let answer = prompt.respond(&context).unwrap();
85
86        let str = answer.to_string().replace(", ", ",\n  "); // This is only for easier reading
87
88        assert_eq!(
89            str,
90            r#"
91Digest username="Mufasa",
92  realm="http-auth@example.org",
93  nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",
94  uri="/dir/index.html",
95  qop=auth,
96  nc=00000001,
97  cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ",
98  response="8ca523f5e9506fed4657c9700eebdbec",
99  opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS",
100  algorithm=MD5
101"#
102            .trim()
103        );
104    }
105
106    #[test]
107    fn test_cast_error() {
108        let _m: Box<dyn std::error::Error> = Error::UnknownAlgorithm("Uhhh".into()).into();
109    }
110}