Skip to main content

ftth_rsip/services/
digest_generator.rs

1use crate::{
2    common::{uri::Uri, Method},
3    headers::{
4        self,
5        auth::{Algorithm, AuthQop},
6    },
7};
8
9/// Simple helpful struct to generate & verify the `Digest` authentication strings.
10/// It can also be created from an [Authorization](crate::typed::Authorization),
11/// an `&str` for the password and a [Method](crate::Method) using the [from](From::from()) method.
12///
13/// Supports SIP versions of [RFC7616](https://datatracker.ietf.org/doc/html/rfc7616)
14/// and [RFC2617](https://datatracker.ietf.org/doc/html/rfc2617).
15#[derive(Debug, Clone)]
16pub struct DigestGenerator<'a> {
17    pub username: &'a str,
18    pub password: &'a str,
19    pub nonce: &'a str,
20    pub uri: &'a Uri,
21    pub realm: &'a str,
22    pub method: &'a Method,
23    pub qop: Option<&'a AuthQop>,
24    pub algorithm: Algorithm,
25}
26
27impl<'a> DigestGenerator<'a> {
28    //TODO: log if scheme is not digest
29    pub fn from(
30        auth: &'a headers::typed::Authorization,
31        password: &'a str,
32        method: &'a Method,
33    ) -> Self {
34        Self {
35            username: &auth.username,
36            password,
37            nonce: &auth.nonce,
38            uri: &auth.uri,
39            realm: &auth.realm,
40            method,
41            qop: auth.qop.as_ref(),
42            algorithm: auth.algorithm.unwrap_or(Algorithm::Md5),
43        }
44    }
45
46    pub fn verify(&self, response: &'a str) -> bool {
47        self.compute() == response
48    }
49
50    pub fn compute(&self) -> String {
51        let value = match self.qop {
52            Some(AuthQop::Auth { cnonce, nc }) => format!(
53                "{}:{}:{:08}:{}:{}:{}",
54                self.ha1(),
55                self.nonce,
56                nc,
57                cnonce,
58                "auth",
59                self.ha2()
60            ),
61            Some(AuthQop::AuthInt { cnonce, nc }) => format!(
62                "{}:{}:{:08}:{}:{}:{}",
63                self.ha1(),
64                self.nonce,
65                nc,
66                cnonce,
67                "auth-int",
68                self.ha2()
69            ),
70            None => format!("{}:{}:{}", self.ha1(), self.nonce, self.ha2()),
71        };
72
73        self.hash_value(value)
74    }
75
76    fn ha1(&self) -> String {
77        let value = format!("{}:{}:{}", self.username, self.realm, self.password);
78
79        self.hash_value(value)
80    }
81
82    fn ha2(&self) -> String {
83        let value = match self.qop {
84            None | Some(AuthQop::Auth { .. }) => format!("{}:{}", self.method, self.uri),
85            _ => format!(
86                "{}:{}:d41d8cd98f00b204e9800998ecf8427e",
87                self.method, self.uri
88            ),
89        };
90
91        self.hash_value(value)
92    }
93
94    fn hash_value(&self, value: String) -> String {
95        use md5::{Digest, Md5};
96        use sha2::{Sha256, Sha512};
97
98        match self.algorithm {
99            Algorithm::Md5 | Algorithm::Md5Sess => {
100                let mut hasher = Md5::new();
101                hasher.update(value);
102                format!("{:x}", hasher.finalize())
103            }
104            Algorithm::Sha256 | Algorithm::Sha256Sess => {
105                let mut hasher = Sha256::new();
106                hasher.update(value);
107                format!("{:x}", hasher.finalize())
108            }
109            Algorithm::Sha512 | Algorithm::Sha512Sess => {
110                let mut hasher = Sha512::new();
111                hasher.update(value);
112                format!("{:x}", hasher.finalize())
113            }
114        }
115    }
116}