ykoath_protocol/
calculate.rs

1use crate::escape_ascii::EscapeAscii;
2use crate::{Error, YubiKey};
3use std::fmt::{self, Display};
4
5#[derive(Clone, Copy, Debug)]
6pub struct Response<'a> {
7    pub digits: u8,
8    pub response: &'a [u8],
9}
10
11impl Response<'_> {
12    #[must_use]
13    pub fn code(&self) -> Code {
14        let mut response = 0_u32.to_be_bytes();
15        let len = response.len();
16        response[len.saturating_sub(self.response.len())..]
17            .copy_from_slice(&self.response[self.response.len().saturating_sub(len)..]);
18        Code {
19            digits: self.digits,
20            response: u32::from_be_bytes(response),
21        }
22    }
23}
24
25#[derive(Clone, Copy)]
26pub struct Code {
27    digits: u8,
28    response: u32,
29}
30
31impl Display for Code {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        // https://github.com/Yubico/yubikey-manager/blob/4.0.9/yubikit/oath.py#L240
34        write!(
35            f,
36            "{:01$}",
37            (self.response & 0x7fff_ffff) % 10_u32.pow(u32::from(self.digits)),
38            usize::from(self.digits),
39        )
40    }
41}
42
43impl YubiKey {
44    #[tracing::instrument(err, fields(name = ?EscapeAscii(name)), ret, skip(name, buf))]
45    pub fn calculate<'a>(
46        &self,
47        truncate: bool,
48        name: &[u8],
49        challenge: &[u8],
50        buf: &'a mut Vec<u8>,
51    ) -> Result<Response<'a>, Error> {
52        // https://github.com/tokio-rs/tracing/issues/2796
53        #[allow(clippy::redundant_locals)]
54        let buf = buf;
55        buf.clear();
56        buf.extend_from_slice(&[
57            0x00,
58            0xa2,
59            0x00,
60            #[allow(clippy::bool_to_int_with_if)]
61            if truncate { 0x01 } else { 0x00 },
62        ]);
63        buf.push(0x00);
64        Self::push(buf, 0x71, name)?;
65        Self::push(buf, 0x74, challenge)?;
66        let mut response = self.transmit(buf)?;
67        let (_, response) = Self::pop(&mut response, &[if truncate { 0x76 } else { 0x75 }])?;
68        Ok(Response {
69            digits: *response.first().ok_or(Error::InsufficientData)?,
70            response: &response[1..],
71        })
72    }
73}