1#[cfg(test)]
2mod auth_test;
3
4use std::net::SocketAddr;
5use std::time::{Duration, SystemTime, UNIX_EPOCH};
6
7#[cfg(feature = "async-auth")]
8use async_trait::async_trait;
9use base64::prelude::BASE64_STANDARD;
10use base64::Engine;
11use md5::{Digest, Md5};
12use ring::hmac;
13
14use crate::error::*;
15
16#[cfg(not(feature = "async-auth"))]
17pub trait AuthHandler {
18 fn auth_handle(&self, username: &str, realm: &str, src_addr: SocketAddr) -> Result<Vec<u8>>;
19}
20
21#[cfg(feature = "async-auth")]
22#[async_trait]
23pub trait AuthHandler {
24 async fn auth_handle(
25 &self,
26 username: &str,
27 realm: &str,
28 src_addr: SocketAddr,
29 ) -> Result<Vec<u8>>;
30}
31
32pub fn generate_long_term_credentials(
34 shared_secret: &str,
35 duration: Duration,
36) -> Result<(String, String)> {
37 let t = SystemTime::now().duration_since(UNIX_EPOCH)? + duration;
38 let username = format!("{}", t.as_secs());
39 let password = long_term_credentials(&username, shared_secret);
40 Ok((username, password))
41}
42
43fn long_term_credentials(username: &str, shared_secret: &str) -> String {
44 let mac = hmac::Key::new(
45 hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY,
46 shared_secret.as_bytes(),
47 );
48 let password = hmac::sign(&mac, username.as_bytes()).as_ref().to_vec();
49 BASE64_STANDARD.encode(password)
50}
51
52pub fn generate_auth_key(username: &str, realm: &str, password: &str) -> Vec<u8> {
54 let s = format!("{username}:{realm}:{password}");
55
56 let mut h = Md5::new();
57 h.update(s.as_bytes());
58 h.finalize().as_slice().to_vec()
59}
60
61pub struct LongTermAuthHandler {
62 shared_secret: String,
63}
64
65#[cfg(not(feature = "async-auth"))]
66impl AuthHandler for LongTermAuthHandler {
67 fn auth_handle(&self, username: &str, realm: &str, src_addr: SocketAddr) -> Result<Vec<u8>> {
68 log::trace!(
69 "Authentication username={} realm={} src_addr={}",
70 username,
71 realm,
72 src_addr
73 );
74
75 let t = Duration::from_secs(username.parse::<u64>()?);
76 if t < SystemTime::now().duration_since(UNIX_EPOCH)? {
77 return Err(Error::Other(format!(
78 "Expired time-windowed username {username}"
79 )));
80 }
81
82 let password = long_term_credentials(username, &self.shared_secret);
83 Ok(generate_auth_key(username, realm, &password))
84 }
85}
86#[cfg(feature = "async-auth")]
87#[async_trait]
88impl AuthHandler for LongTermAuthHandler {
89 async fn auth_handle(
90 &self,
91 username: &str,
92 realm: &str,
93 src_addr: SocketAddr,
94 ) -> Result<Vec<u8>> {
95 log::trace!(
96 "Authentication username={} realm={} src_addr={}",
97 username,
98 realm,
99 src_addr
100 );
101
102 let t = Duration::from_secs(username.parse::<u64>()?);
103 if t < SystemTime::now().duration_since(UNIX_EPOCH)? {
104 return Err(Error::Other(format!(
105 "Expired time-windowed username {username}"
106 )));
107 }
108
109 let password = long_term_credentials(username, &self.shared_secret);
110 Ok(generate_auth_key(username, realm, &password))
111 }
112}
113
114impl LongTermAuthHandler {
115 pub fn new(shared_secret: String) -> Self {
117 LongTermAuthHandler { shared_secret }
118 }
119}