1use crate::crypto;
2use crate::errors::*;
3use sha3::{Digest, Sha3_256};
4use std::collections::HashMap;
5
6const RING_BUFFER_SIZE: usize = 4;
7const CHALL_SIZE: usize = 32;
8const CHALL_ENCRYPTED_SIZE: usize =
9 CHALL_SIZE + crypto::CRYPTO_NONCE_SIZE + crypto::CRYPTO_TAG_SIZE;
10const SHA3_SIZE: usize = 32;
11
12fn hash(bytes: &[u8], dest: &mut [u8; SHA3_SIZE]) {
13 let mut hasher = Sha3_256::new();
14 hasher.update(bytes);
15 hasher.finalize_into(dest.into());
16}
17
18pub struct Challenge {
19 code: [u8; SHA3_SIZE],
21 pub encrypted: [u8; CHALL_ENCRYPTED_SIZE],
22}
23
24impl Challenge {
25 pub fn generate<R: crypto::Rng>(salsa: &crypto::SalsaBox) -> Result<Self> {
26 let mut chall = [0u8; CHALL_SIZE];
27 R::getrandom(&mut chall);
28
29 let mut encrypted = [0u8; CHALL_ENCRYPTED_SIZE];
30 crypto::encrypt::<R>(salsa, &chall, &mut encrypted)?;
31
32 let mut code = [0u8; SHA3_SIZE];
33 hash(&chall, &mut code);
34
35 Ok(Challenge { code, encrypted })
36 }
37
38 pub fn verify(&self, code: &[u8]) -> Result<&Self> {
39 let mut buf = [0u8; SHA3_SIZE];
40 hash(code, &mut buf);
41 if self.code == buf {
42 Ok(self)
43 } else {
44 Err(Error::InvalidChallengeReponse)
45 }
46 }
47}
48
49#[derive(Default)]
50pub struct RingBuffer {
51 challenges: [Option<Challenge>; RING_BUFFER_SIZE],
52 cursor: usize,
53}
54
55impl RingBuffer {
56 pub fn new<R: crypto::Rng>(salsa: &crypto::SalsaBox) -> RingBuffer {
57 let mut ring = RingBuffer::default();
58 ring.challenges[0] = Some(Challenge::generate::<R>(salsa).unwrap());
59 ring
60 }
61
62 pub fn current(&self) -> &Challenge {
63 self.challenges[self.cursor].as_ref().unwrap()
64 }
65
66 pub fn generate_next<R: crypto::Rng>(&mut self, salsa: &crypto::SalsaBox) -> &Challenge {
67 if self.challenges.len() - 1 == self.cursor {
68 self.cursor = 0;
69 } else {
70 self.cursor += 1;
71 }
72 self.challenges[self.cursor] = Some(Challenge::generate::<R>(salsa).unwrap());
73 self.challenges[self.cursor].as_ref().unwrap()
74 }
75
76 pub fn verify(&self, secret: &[u8]) -> Result<()> {
77 for chall in self.challenges.iter().flatten() {
78 if chall.verify(secret).is_ok() {
79 return Ok(());
80 }
81 }
82
83 Err(Error::AuthError)
84 }
85
86 pub fn reset<R: crypto::Rng>(&mut self, salsa: &crypto::SalsaBox) {
87 *self = RingBuffer::new::<R>(salsa)
88 }
89}
90
91#[derive(Default)]
92pub struct UserDoorMap {
93 map: HashMap<(String, String), RingBuffer>,
94}
95
96impl UserDoorMap {
97 pub fn generate_next<R: crypto::Rng>(
98 &mut self,
99 user: String,
100 door: String,
101 salsa: &crypto::SalsaBox,
102 ) -> &Challenge {
103 let ring = self
104 .map
105 .entry((user, door))
106 .or_insert_with(|| RingBuffer::new::<R>(salsa));
107 ring.generate_next::<R>(salsa)
108 }
109
110 pub fn verify(&self, user: String, door: String, secret: &[u8]) -> Result<String> {
111 if let Some(ring) = self.map.get(&(user, door.clone())) {
112 ring.verify(secret)?;
113 Ok(door)
114 } else {
115 Err(Error::AuthError)
116 }
117 }
118
119 pub fn reset<R: crypto::Rng>(&mut self, user: String, door: String, salsa: &crypto::SalsaBox) {
120 self.map.insert((user, door), RingBuffer::new::<R>(salsa));
121 }
122}