Hash refactor

This commit is contained in:
Cléo Rebert 2022-01-13 21:52:55 +01:00
parent 052531694f
commit b68dd87ce2
2 changed files with 69 additions and 39 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "totp-rs"
version = "0.7.1"
version = "0.7.2"
authors = ["Cleo Rebert <cleo.rebert@gmail.com>"]
edition = "2021"
readme = "README.md"

View File

@ -40,18 +40,17 @@
use serde::{Deserialize, Serialize};
use byteorder::{BigEndian, ReadBytesExt};
use core::fmt;
use std::io::Cursor;
#[cfg(feature = "qr")]
use {base64, image::Luma, qrcode::QrCode};
use hmac::{Hmac, Mac};
use sha1::Sha1;
use sha2::{Sha256, Sha512};
use hmac::Mac;
type HmacSha1 = Hmac<Sha1>;
type HmacSha256 = Hmac<Sha256>;
type HmacSha512 = Hmac<Sha512>;
type HmacSha1 = hmac::Hmac<sha1::Sha1>;
type HmacSha256 = hmac::Hmac<sha2::Sha256>;
type HmacSha512 = hmac::Hmac<sha2::Sha512>;
/// Algorithm enum holds the three standards algorithms for TOTP as per the [reference implementation](https://tools.ietf.org/html/rfc6238#appendix-A)
#[derive(Debug, Copy, Clone)]
@ -62,6 +61,40 @@ pub enum Algorithm {
SHA512,
}
impl fmt::Display for Algorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Algorithm::SHA1 => {
return f.write_str("SHA1");
}
Algorithm::SHA256 => {
return f.write_str("SHA256");
}
Algorithm::SHA512 => {
return f.write_str("SHA512");
}
}
}
}
impl Algorithm {
fn hash<D>(mut digest: D, data: &[u8]) -> Vec<u8>
where
D: hmac::Mac,
{
digest.update(data);
digest.finalize().into_bytes().to_vec()
}
fn sign(&self, key: &[u8], data: &[u8]) -> Vec<u8> {
match *self {
Algorithm::SHA1 => Algorithm::hash(HmacSha1::new_from_slice(key).unwrap(), data),
Algorithm::SHA256 => Algorithm::hash(HmacSha256::new_from_slice(key).unwrap(), data),
Algorithm::SHA512 => Algorithm::hash(HmacSha512::new_from_slice(key).unwrap(), data),
}
}
}
/// TOTP holds informations as to how to generate an auth code and validate it. Its [secret](struct.TOTP.html#structfield.secret) field is sensitive data, treat it accordingly
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
@ -92,24 +125,10 @@ impl<T: AsRef<[u8]>> TOTP<T> {
/// Will sign the given timestamp
pub fn sign(&self, time: u64) -> Vec<u8> {
let ctr = (time / self.step).to_be_bytes();
match self.algorithm {
Algorithm::SHA1 => {
let mut mac = HmacSha1::new_from_slice(self.secret.as_ref()).expect("no key");
mac.update(&ctr);
mac.finalize().into_bytes().to_vec()
}
Algorithm::SHA256 => {
let mut mac = HmacSha256::new_from_slice(self.secret.as_ref()).expect("no key");
mac.update(&ctr);
mac.finalize().into_bytes().to_vec()
}
Algorithm::SHA512 => {
let mut mac = HmacSha512::new_from_slice(self.secret.as_ref()).expect("no key");
mac.update(&ctr);
mac.finalize().into_bytes().to_vec()
}
}
self.algorithm.sign(
self.secret.as_ref(),
(time / self.step).to_be_bytes().as_ref(),
)
}
/// Will generate a token according to the provided timestamp in seconds
@ -147,18 +166,13 @@ impl<T: AsRef<[u8]>> TOTP<T> {
/// Will generate a standard URL used to automatically add TOTP auths. Usually used with qr codes
pub fn get_url(&self, label: &str, issuer: &str) -> String {
let algorithm = match self.algorithm {
Algorithm::SHA1 => "SHA1",
Algorithm::SHA256 => "SHA256",
Algorithm::SHA512 => "SHA512",
};
format!(
"otpauth://totp/{}?secret={}&issuer={}&digits={}&algorithm={}",
label,
label.to_string(),
self.get_secret_base32(),
issuer,
self.digits,
algorithm,
issuer.to_string(),
self.digits.to_string(),
self.algorithm,
)
}
@ -174,12 +188,11 @@ impl<T: AsRef<[u8]>> TOTP<T> {
let url = self.get_url(label, issuer);
let code = QrCode::new(&url)?;
let mut vec = Vec::new();
let size: u32 = ((code.width() + 8) * 8) as u32;
let encoder = image::png::PngEncoder::new(&mut vec);
encoder.encode(
code.render::<Luma<u8>>().build().as_ref(),
size,
size,
((code.width() + 8) * 8) as u32,
((code.width() + 8) * 8) as u32,
image::ColorType::L8,
)?;
Ok(base64::encode(vec))
@ -191,12 +204,26 @@ mod tests {
use super::*;
#[test]
fn url_for_secret_matches() {
fn url_for_secret_matches_sha1() {
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret");
let url = totp.get_url("test_url", "totp-rs");
assert_eq!(url.as_str(), "otpauth://totp/test_url?secret=KRSXG5CTMVRXEZLU&issuer=totp-rs&digits=6&algorithm=SHA1");
}
#[test]
fn url_for_secret_matches_sha256() {
let totp = TOTP::new(Algorithm::SHA256, 6, 1, 1, "TestSecret");
let url = totp.get_url("test_url", "totp-rs");
assert_eq!(url.as_str(), "otpauth://totp/test_url?secret=KRSXG5CTMVRXEZLU&issuer=totp-rs&digits=6&algorithm=SHA256");
}
#[test]
fn url_for_secret_matches_sha512() {
let totp = TOTP::new(Algorithm::SHA512, 6, 1, 1, "TestSecret");
let url = totp.get_url("test_url", "totp-rs");
assert_eq!(url.as_str(), "otpauth://totp/test_url?secret=KRSXG5CTMVRXEZLU&issuer=totp-rs&digits=6&algorithm=SHA512");
}
#[test]
fn returns_base32() {
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret");
@ -223,8 +250,11 @@ mod tests {
#[test]
fn checks_token() {
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret");
let totp = TOTP::new(Algorithm::SHA1, 6, 0, 1, "TestSecret");
assert!(totp.check("718996", 1000));
assert!(totp.check("712039", 2000));
assert!(!totp.check("527544", 2000));
assert!(!totp.check("714250", 2000));
}
#[test]