Hash refactor
This commit is contained in:
parent
052531694f
commit
b68dd87ce2
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "totp-rs"
|
name = "totp-rs"
|
||||||
version = "0.7.1"
|
version = "0.7.2"
|
||||||
authors = ["Cleo Rebert <cleo.rebert@gmail.com>"]
|
authors = ["Cleo Rebert <cleo.rebert@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
106
src/lib.rs
106
src/lib.rs
|
@ -40,18 +40,17 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use byteorder::{BigEndian, ReadBytesExt};
|
use byteorder::{BigEndian, ReadBytesExt};
|
||||||
|
use core::fmt;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
#[cfg(feature = "qr")]
|
#[cfg(feature = "qr")]
|
||||||
use {base64, image::Luma, qrcode::QrCode};
|
use {base64, image::Luma, qrcode::QrCode};
|
||||||
|
|
||||||
use hmac::{Hmac, Mac};
|
use hmac::Mac;
|
||||||
use sha1::Sha1;
|
|
||||||
use sha2::{Sha256, Sha512};
|
|
||||||
|
|
||||||
type HmacSha1 = Hmac<Sha1>;
|
type HmacSha1 = hmac::Hmac<sha1::Sha1>;
|
||||||
type HmacSha256 = Hmac<Sha256>;
|
type HmacSha256 = hmac::Hmac<sha2::Sha256>;
|
||||||
type HmacSha512 = Hmac<Sha512>;
|
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)
|
/// 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)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
@ -62,6 +61,40 @@ pub enum Algorithm {
|
||||||
SHA512,
|
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
|
/// 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)]
|
#[derive(Debug, Clone)]
|
||||||
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
|
||||||
|
@ -92,24 +125,10 @@ impl<T: AsRef<[u8]>> TOTP<T> {
|
||||||
|
|
||||||
/// Will sign the given timestamp
|
/// Will sign the given timestamp
|
||||||
pub fn sign(&self, time: u64) -> Vec<u8> {
|
pub fn sign(&self, time: u64) -> Vec<u8> {
|
||||||
let ctr = (time / self.step).to_be_bytes();
|
self.algorithm.sign(
|
||||||
match self.algorithm {
|
self.secret.as_ref(),
|
||||||
Algorithm::SHA1 => {
|
(time / self.step).to_be_bytes().as_ref(),
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Will generate a token according to the provided timestamp in seconds
|
/// 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
|
/// 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 {
|
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!(
|
format!(
|
||||||
"otpauth://totp/{}?secret={}&issuer={}&digits={}&algorithm={}",
|
"otpauth://totp/{}?secret={}&issuer={}&digits={}&algorithm={}",
|
||||||
label,
|
label.to_string(),
|
||||||
self.get_secret_base32(),
|
self.get_secret_base32(),
|
||||||
issuer,
|
issuer.to_string(),
|
||||||
self.digits,
|
self.digits.to_string(),
|
||||||
algorithm,
|
self.algorithm,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,12 +188,11 @@ impl<T: AsRef<[u8]>> TOTP<T> {
|
||||||
let url = self.get_url(label, issuer);
|
let url = self.get_url(label, issuer);
|
||||||
let code = QrCode::new(&url)?;
|
let code = QrCode::new(&url)?;
|
||||||
let mut vec = Vec::new();
|
let mut vec = Vec::new();
|
||||||
let size: u32 = ((code.width() + 8) * 8) as u32;
|
|
||||||
let encoder = image::png::PngEncoder::new(&mut vec);
|
let encoder = image::png::PngEncoder::new(&mut vec);
|
||||||
encoder.encode(
|
encoder.encode(
|
||||||
code.render::<Luma<u8>>().build().as_ref(),
|
code.render::<Luma<u8>>().build().as_ref(),
|
||||||
size,
|
((code.width() + 8) * 8) as u32,
|
||||||
size,
|
((code.width() + 8) * 8) as u32,
|
||||||
image::ColorType::L8,
|
image::ColorType::L8,
|
||||||
)?;
|
)?;
|
||||||
Ok(base64::encode(vec))
|
Ok(base64::encode(vec))
|
||||||
|
@ -191,12 +204,26 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn url_for_secret_matches() {
|
fn url_for_secret_matches_sha1() {
|
||||||
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret");
|
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret");
|
||||||
let url = totp.get_url("test_url", "totp-rs");
|
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");
|
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]
|
#[test]
|
||||||
fn returns_base32() {
|
fn returns_base32() {
|
||||||
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret");
|
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret");
|
||||||
|
@ -223,8 +250,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn checks_token() {
|
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("718996", 1000));
|
||||||
|
assert!(totp.check("712039", 2000));
|
||||||
|
assert!(!totp.check("527544", 2000));
|
||||||
|
assert!(!totp.check("714250", 2000));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in New Issue