1use anyhow::{bail, ensure, Result};
4use hmac::{Hmac, Mac};
5use sha2::{Digest, Sha256};
6use tokio::io::{AsyncRead, AsyncWrite};
7use uuid::Uuid;
8
9use crate::shared::{ClientMessage, Delimited, ServerMessage};
10
11pub struct Authenticator(Hmac<Sha256>);
13
14impl Authenticator {
15 pub fn new(secret: &str) -> Self {
17 let hashed_secret = Sha256::new().chain_update(secret).finalize();
18 Self(Hmac::new_from_slice(&hashed_secret).expect("HMAC can take key of any size"))
19 }
20
21 pub fn answer(&self, challenge: &Uuid) -> String {
23 let mut hmac = self.0.clone();
24 hmac.update(challenge.as_bytes());
25 hex::encode(hmac.finalize().into_bytes())
26 }
27
28 pub fn validate(&self, challenge: &Uuid, tag: &str) -> bool {
41 if let Ok(tag) = hex::decode(tag) {
42 let mut hmac = self.0.clone();
43 hmac.update(challenge.as_bytes());
44 hmac.verify_slice(&tag).is_ok()
45 } else {
46 false
47 }
48 }
49
50 pub async fn server_handshake<T: AsyncRead + AsyncWrite + Unpin>(
52 &self,
53 stream: &mut Delimited<T>,
54 ) -> Result<()> {
55 let challenge = Uuid::new_v4();
56 stream.send(ServerMessage::Challenge(challenge)).await?;
57 match stream.recv_timeout().await? {
58 Some(ClientMessage::Authenticate(tag)) => {
59 ensure!(self.validate(&challenge, &tag), "invalid secret");
60 Ok(())
61 }
62 _ => bail!("server requires secret, but no secret was provided"),
63 }
64 }
65
66 pub async fn client_handshake<T: AsyncRead + AsyncWrite + Unpin>(
68 &self,
69 stream: &mut Delimited<T>,
70 ) -> Result<()> {
71 let challenge = match stream.recv_timeout().await? {
72 Some(ServerMessage::Challenge(challenge)) => challenge,
73 _ => bail!("expected authentication challenge, but no secret was required"),
74 };
75 let tag = self.answer(&challenge);
76 stream.send(ClientMessage::Authenticate(tag)).await?;
77 Ok(())
78 }
79}