use crate::api::*;
use crate::error::Error;
use crate::service::*;
const DIGITS: u32 = 6;
#[inline(never)]
fn hotp_raw(key: &[u8], counter: u64, digits: u32) -> u64 {
hmac_and_truncate(key, &counter.to_be_bytes(), digits)
}
#[inline(never)]
fn hmac_and_truncate(key: &[u8], message: &[u8], digits: u32) -> u64 {
use hmac::{Hmac, Mac, NewMac};
let mut hmac = Hmac::<sha1::Sha1>::new_from_slice(key).unwrap();
hmac.update(message);
let result = hmac.finalize();
let hs = result.into_bytes();
dynamic_truncation(&hs) % 10_u64.pow(digits)
}
#[inline]
fn dynamic_truncation(hs: &[u8]) -> u64 {
let offset_bits = (*hs.last().unwrap() & 0xf) as usize;
let p = u32::from_be_bytes(hs[offset_bits..][..4].try_into().unwrap()) as u64;
p & 0x7fff_ffff
}
#[cfg(feature = "totp")]
impl Sign for super::Totp
{
#[inline(never)]
fn sign(keystore: &mut impl Keystore, request: &request::Sign)
-> Result<reply::Sign, Error>
{
let key_id = request.key;
let secret = keystore
.load_key(key::Secrecy::Secret, None, &key_id)?
.material;
if request.message.len() != 8 {
return Err(Error::InternalError);
}
let timestamp_as_le_bytes = request.message[..].try_into().unwrap();
let timestamp = u64::from_le_bytes(timestamp_as_le_bytes);
let totp_material: u64 = hotp_raw(&secret, timestamp, DIGITS);
Ok(reply::Sign { signature: crate::Bytes::from_slice(totp_material.to_le_bytes().as_ref()).unwrap() })
}
}
#[cfg(feature = "totp")]
impl Exists for super::Totp
{
#[inline(never)]
fn exists(keystore: &mut impl Keystore, request: &request::Exists)
-> Result<reply::Exists, Error>
{
let key_id = request.key;
let exists = keystore.exists_key(key::Secrecy::Secret, Some(key::Kind::Symmetric(20)), &key_id);
Ok(reply::Exists { exists })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hotp() {
assert_eq!(hotp_raw(b"\xff", 23, 6), 330795);
assert_eq!(hotp_raw(b"12345678901234567890", 0, 6), 755224);
assert_eq!(hotp_raw(b"12345678901234567890", 1, 6), 287082);
assert_ne!(hotp_raw(b"12345678901234567890", 1, 6), 287081);
}
}