d3xs_protocol/
chall.rs

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    // store this as sha256 so the attacker has less control over inputs of the compare
20    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}