From b68dd87ce2b70f813ae36193ace4e7275b19b0ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9o=20Rebert?= Date: Thu, 13 Jan 2022 21:52:55 +0100 Subject: [PATCH] Hash refactor --- Cargo.toml | 2 +- src/lib.rs | 106 ++++++++++++++++++++++++++++++++++------------------- 2 files changed, 69 insertions(+), 39 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d022f7b..3a93482 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "totp-rs" -version = "0.7.1" +version = "0.7.2" authors = ["Cleo Rebert "] edition = "2021" readme = "README.md" diff --git a/src/lib.rs b/src/lib.rs index 5f50f61..93e7582 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; -type HmacSha256 = Hmac; -type HmacSha512 = Hmac; +type HmacSha1 = hmac::Hmac; +type HmacSha256 = hmac::Hmac; +type HmacSha512 = hmac::Hmac; /// 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(mut digest: D, data: &[u8]) -> Vec + where + D: hmac::Mac, + { + digest.update(data); + digest.finalize().into_bytes().to_vec() + } + + fn sign(&self, key: &[u8], data: &[u8]) -> Vec { + 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> TOTP { /// Will sign the given timestamp pub fn sign(&self, time: u64) -> Vec { - 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> TOTP { /// 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> TOTP { 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::>().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]