Cargo fmt

Signed-off-by: constantoine <cleo.rebert-ext@treezor.com>
This commit is contained in:
constantoine 2022-10-05 11:55:37 +02:00
parent 4d9e41ee68
commit 0f0e7d6032
No known key found for this signature in database
GPG Key ID: 0FA097951CF65367
8 changed files with 369 additions and 159 deletions

View File

@ -1,9 +1,8 @@
#[cfg(all(feature = "gen_secret", feature = "otpauth"))]
use totp_rs::{Secret, TOTP, Algorithm};
use totp_rs::{Algorithm, Secret, TOTP};
#[cfg(all(feature = "gen_secret", feature = "otpauth"))]
fn main () {
fn main() {
let secret = Secret::generate_secret();
let totp = TOTP::new(
@ -14,7 +13,8 @@ fn main () {
secret.to_bytes().unwrap(),
None,
"account".to_string(),
).unwrap();
)
.unwrap();
println!(
"secret raw: {} ; secret base32 {} ; code: {}",
@ -25,4 +25,4 @@ fn main () {
}
#[cfg(not(all(feature = "gen_secret", feature = "otpauth")))]
fn main () {}
fn main() {}

View File

@ -1,10 +1,8 @@
use totp_rs::{Rfc6238, TOTP};
#[cfg(feature = "otpauth")]
fn main () {
let mut rfc = Rfc6238::with_defaults(
"totp-sercret-123"
).unwrap();
fn main() {
let mut rfc = Rfc6238::with_defaults("totp-sercret-123").unwrap();
// optional, set digits, issuer, account_name
rfc.digits(8).unwrap();
@ -18,10 +16,8 @@ fn main () {
}
#[cfg(not(feature = "otpauth"))]
fn main () {
let mut rfc = Rfc6238::with_defaults(
"totp-sercret-123"
).unwrap();
fn main() {
let mut rfc = Rfc6238::with_defaults("totp-sercret-123").unwrap();
// optional, set digits, issuer, account_name
rfc.digits(8).unwrap();
@ -30,4 +26,4 @@ fn main () {
let totp = TOTP::from_rfc6238(rfc).unwrap();
let code = totp.generate_current().unwrap();
println!("code: {}", code);
}
}

View File

@ -1,7 +1,7 @@
use totp_rs::{Secret, TOTP, Algorithm};
use totp_rs::{Algorithm, Secret, TOTP};
#[cfg(feature = "otpauth")]
fn main () {
fn main() {
// create TOTP from base32 secret
let secret_b32 = Secret::Encoded(String::from("OBWGC2LOFVZXI4TJNZTS243FMNZGK5BNGEZDG"));
let totp_b32 = TOTP::new(
@ -12,10 +12,18 @@ fn main () {
secret_b32.to_bytes().unwrap(),
Some("issuer".to_string()),
"user-account".to_string(),
).unwrap();
)
.unwrap();
println!("base32 {} ; raw {}", secret_b32, secret_b32.to_raw().unwrap());
println!("code from base32:\t{}", totp_b32.generate_current().unwrap());
println!(
"base32 {} ; raw {}",
secret_b32,
secret_b32.to_raw().unwrap()
);
println!(
"code from base32:\t{}",
totp_b32.generate_current().unwrap()
);
// create TOTP from raw binary value
let secret = [
@ -31,26 +39,31 @@ fn main () {
secret_raw.to_bytes().unwrap(),
Some("issuer".to_string()),
"user-account".to_string(),
).unwrap();
)
.unwrap();
println!("raw {} ; base32 {}", secret_raw, secret_raw.to_encoded());
println!("code from raw secret:\t{}", totp_raw.generate_current().unwrap());
println!(
"code from raw secret:\t{}",
totp_raw.generate_current().unwrap()
);
}
#[cfg(not(feature = "otpauth"))]
fn main () {
fn main() {
// create TOTP from base32 secret
let secret_b32 = Secret::Encoded(String::from("OBWGC2LOFVZXI4TJNZTS243FMNZGK5BNGEZDG"));
let totp_b32 = TOTP::new(
Algorithm::SHA1,
6,
1,
30,
secret_b32.to_bytes().unwrap(),
).unwrap();
let totp_b32 = TOTP::new(Algorithm::SHA1, 6, 1, 30, secret_b32.to_bytes().unwrap()).unwrap();
println!("base32 {} ; raw {}", secret_b32, secret_b32.to_raw().unwrap());
println!("code from base32:\t{}", totp_b32.generate_current().unwrap());
println!(
"base32 {} ; raw {}",
secret_b32,
secret_b32.to_raw().unwrap()
);
println!(
"code from base32:\t{}",
totp_b32.generate_current().unwrap()
);
// create TOTP from raw binary value
let secret = [
@ -58,14 +71,11 @@ fn main () {
0x63, 0x72, 0x65, 0x74, 0x2d, 0x31, 0x32, 0x33,
];
let secret_raw = Secret::Raw(secret.to_vec());
let totp_raw = TOTP::new(
Algorithm::SHA1,
6,
1,
30,
secret_raw.to_bytes().unwrap(),
).unwrap();
let totp_raw = TOTP::new(Algorithm::SHA1, 6, 1, 30, secret_raw.to_bytes().unwrap()).unwrap();
println!("raw {} ; base32 {}", secret_raw, secret_raw.to_encoded());
println!("code from raw secret:\t{}", totp_raw.generate_current().unwrap());
}
println!(
"code from raw secret:\t{}",
totp_raw.generate_current().unwrap()
);
}

View File

@ -2,13 +2,7 @@ use totp_rs::{Algorithm, TOTP};
#[cfg(not(feature = "otpauth"))]
fn main() {
let totp = TOTP::new(
Algorithm::SHA1,
6,
1,
30,
"my-secret".to_string(),
).unwrap();
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 30, "my-secret".to_string()).unwrap();
loop {
println!(
@ -21,7 +15,6 @@ fn main() {
}
}
#[cfg(feature = "otpauth")]
fn main() {
let totp = TOTP::new(
@ -31,8 +24,9 @@ fn main() {
30,
"my-secret".to_string(),
Some("Github".to_string()),
"constantoine@github.com".to_string()
).unwrap();
"constantoine@github.com".to_string(),
)
.unwrap();
loop {
println!(

View File

@ -47,13 +47,13 @@
//! # }
//! ```
mod secret;
mod rfc;
mod secret;
mod url_error;
pub use rfc::{Rfc6238, Rfc6238Error};
pub use secret::{Secret, SecretParseError};
pub use url_error::TotpUrlError;
pub use rfc::{Rfc6238, Rfc6238Error};
use constant_time_eq::constant_time_eq;
@ -119,9 +119,7 @@ impl Algorithm {
}
fn system_time() -> Result<u64, SystemTimeError> {
let t = SystemTime::now()
.duration_since(UNIX_EPOCH)?
.as_secs();
let t = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
Ok(t)
}
@ -149,10 +147,10 @@ pub struct TOTP<T = Vec<u8>> {
#[cfg(feature = "otpauth")]
/// The "constantoine@github.com" part of "Github:constantoine@github.com". Must not contain a colon `:`
/// For example, the name of your user's account.
pub account_name: String
pub account_name: String,
}
impl <T: AsRef<[u8]>> PartialEq for TOTP<T> {
impl<T: AsRef<[u8]>> PartialEq for TOTP<T> {
/// Will not check for issuer and account_name equality
/// As they aren't taken in account for token generation/token checking
fn eq(&self, other: &Self) -> bool {
@ -175,19 +173,34 @@ impl <T: AsRef<[u8]>> PartialEq for TOTP<T> {
#[cfg(all(feature = "gen_secret", not(feature = "otpauth")))]
impl Default for TOTP {
fn default() -> Self {
return TOTP::new(Algorithm::SHA1, 6, 1, 30, Secret::generate_secret().to_bytes().unwrap()).unwrap()
return TOTP::new(
Algorithm::SHA1,
6,
1,
30,
Secret::generate_secret().to_bytes().unwrap(),
)
.unwrap();
}
}
#[cfg(all(feature = "gen_secret", feature = "otpauth"))]
impl Default for TOTP {
fn default() -> Self {
TOTP::new(Algorithm::SHA1, 6, 1, 30, Secret::generate_secret().to_bytes().unwrap(), None, "".to_string()).unwrap()
TOTP::new(
Algorithm::SHA1,
6,
1,
30,
Secret::generate_secret().to_bytes().unwrap(),
None,
"".to_string(),
)
.unwrap()
}
}
impl<T: AsRef<[u8]>> TOTP<T> {
#[cfg(feature = "otpauth")]
/// Will create a new instance of TOTP with given parameters. See [the doc](struct.TOTP.html#fields) for reference as to how to choose those values
///
@ -207,7 +220,15 @@ impl<T: AsRef<[u8]>> TOTP<T> {
/// # Errors
///
/// Will return an error in case issuer or label contain the character ':'
pub fn new(algorithm: Algorithm, digits: usize, skew: u8, step: u64, secret: T, issuer: Option<String>, account_name: String) -> Result<TOTP<T>, TotpUrlError> {
pub fn new(
algorithm: Algorithm,
digits: usize,
skew: u8,
step: u64,
secret: T,
issuer: Option<String>,
account_name: String,
) -> Result<TOTP<T>, TotpUrlError> {
crate::rfc::assert_digits(&digits)?;
crate::rfc::assert_secret_length(secret.as_ref())?;
if issuer.is_some() && issuer.as_ref().unwrap().contains(':') {
@ -244,7 +265,13 @@ impl<T: AsRef<[u8]>> TOTP<T> {
/// # Errors
///
/// Will return an error in case issuer or label contain the character ':'
pub fn new(algorithm: Algorithm, digits: usize,skew: u8, step: u64, secret: T) -> Result<TOTP<T>, TotpUrlError> {
pub fn new(
algorithm: Algorithm,
digits: usize,
skew: u8,
step: u64,
secret: T,
) -> Result<TOTP<T>, TotpUrlError> {
crate::rfc::assert_digits(&digits)?;
crate::rfc::assert_secret_length(secret.as_ref())?;
Ok(TOTP {
@ -277,7 +304,8 @@ impl<T: AsRef<[u8]>> TOTP<T> {
pub fn generate(&self, time: u64) -> String {
let result: &[u8] = &self.sign(time);
let offset = (result.last().unwrap() & 15) as usize;
let result = u32::from_be_bytes(result[offset..offset + 4].try_into().unwrap()) & 0x7fff_ffff;
let result =
u32::from_be_bytes(result[offset..offset + 4].try_into().unwrap()) & 0x7fff_ffff;
format!(
"{1:00$}",
self.digits,
@ -295,7 +323,7 @@ impl<T: AsRef<[u8]>> TOTP<T> {
/// Returns the timestamp of the first second of the next step
/// According to system time
pub fn next_step_current(&self)-> Result<u64, SystemTimeError> {
pub fn next_step_current(&self) -> Result<u64, SystemTimeError> {
let t = system_time()?;
Ok(self.next_step(t))
}
@ -360,13 +388,19 @@ impl<T: AsRef<[u8]>> TOTP<T> {
let path = url.path().trim_start_matches('/');
if path.contains(':') {
let parts = path.split_once(':').unwrap();
issuer = Some(urlencoding::decode(parts.0.to_owned().as_str()).map_err(|_| TotpUrlError::IssuerDecoding(parts.0.to_owned()))?.to_string());
issuer = Some(
urlencoding::decode(parts.0.to_owned().as_str())
.map_err(|_| TotpUrlError::IssuerDecoding(parts.0.to_owned()))?
.to_string(),
);
account_name = parts.1.trim_start_matches(':').to_owned();
} else {
account_name = path.to_owned();
}
account_name = urlencoding::decode(account_name.as_str()).map_err(|_| TotpUrlError::AccountName(account_name.to_string()))?.to_string();
account_name = urlencoding::decode(account_name.as_str())
.map_err(|_| TotpUrlError::AccountName(account_name.to_string()))?
.to_string();
for (key, value) in url.query_pairs() {
match key.as_ref() {
@ -379,20 +413,31 @@ impl<T: AsRef<[u8]>> TOTP<T> {
}
}
"digits" => {
digits = value.parse::<usize>().map_err(|_| TotpUrlError::Digits(value.to_string()))?;
digits = value
.parse::<usize>()
.map_err(|_| TotpUrlError::Digits(value.to_string()))?;
}
"period" => {
step = value.parse::<u64>().map_err(|_| TotpUrlError::Step(value.to_string()))?;
step = value
.parse::<u64>()
.map_err(|_| TotpUrlError::Step(value.to_string()))?;
}
"secret" => {
secret =
base32::decode(base32::Alphabet::RFC4648 { padding: false }, value.as_ref())
.ok_or_else(|| TotpUrlError::Secret(value.to_string()))?;
secret = base32::decode(
base32::Alphabet::RFC4648 { padding: false },
value.as_ref(),
)
.ok_or_else(|| TotpUrlError::Secret(value.to_string()))?;
}
"issuer" => {
let param_issuer = value.parse::<String>().map_err(|_| TotpUrlError::Issuer(value.to_string()))?;
let param_issuer = value
.parse::<String>()
.map_err(|_| TotpUrlError::Issuer(value.to_string()))?;
if issuer.is_some() && param_issuer.as_str() != issuer.as_ref().unwrap() {
return Err(TotpUrlError::IssuerMistmatch(issuer.as_ref().unwrap().to_string(), param_issuer));
return Err(TotpUrlError::IssuerMistmatch(
issuer.as_ref().unwrap().to_string(),
param_issuer,
));
}
issuer = Some(param_issuer);
}
@ -413,11 +458,11 @@ impl<T: AsRef<[u8]>> TOTP<T> {
/// Secret will be base 32'd without padding, as per RFC.
#[cfg(feature = "otpauth")]
pub fn get_url(&self) -> String {
let account_name: String = urlencoding::encode(self.account_name.as_str()).to_string();
let mut label: String = format!("{}?", account_name);
if self.issuer.is_some() {
let issuer: String = urlencoding::encode(self.issuer.as_ref().unwrap().as_str()).to_string();
let issuer: String =
urlencoding::encode(self.issuer.as_ref().unwrap().as_str()).to_string();
label = format!("{0}:{1}?issuer={0}&", issuer, account_name);
}
@ -441,7 +486,8 @@ impl<T: AsRef<[u8]>> TOTP<T> {
// Draw the border
for x in 0..image_size {
for y in 0..image_size {
if (y < 8*4 || y >= image_size - 8*4) || (x < 8*4 || x >= image_size - 8*4) {
if (y < 8 * 4 || y >= image_size - 8 * 4) || (x < 8 * 4 || x >= image_size - 8 * 4)
{
canvas.put_pixel(x, y, Luma([255]));
}
}
@ -457,17 +503,13 @@ impl<T: AsRef<[u8]>> TOTP<T> {
// Multiply coordinates by width of pixels
// And take into account the 8*4 padding on top and left side
let x_start = x_qr * 8 + 8*4;
let y_start = y_qr * 8 + 8*4;
let x_start = x_qr * 8 + 8 * 4;
let y_start = y_qr * 8 + 8 * 4;
// Draw a 8-pixels-wide square
for x_img in x_start..x_start + 8 {
for y_img in y_start..y_start + 8 {
canvas.put_pixel(
x_img,
y_img,
Luma([val]),
);
canvas.put_pixel(x_img, y_img, Luma([val]));
}
}
}
@ -529,7 +571,15 @@ mod tests {
#[test]
#[cfg(feature = "otpauth")]
fn new_wrong_issuer() {
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github:".to_string()), "constantoine@github.com".to_string());
let totp = TOTP::new(
Algorithm::SHA1,
6,
1,
1,
"TestSecretSuperSecret",
Some("Github:".to_string()),
"constantoine@github.com".to_string(),
);
assert!(totp.is_err());
assert!(matches!(totp.unwrap_err(), TotpUrlError::Issuer(_)));
}
@ -537,7 +587,15 @@ mod tests {
#[test]
#[cfg(feature = "otpauth")]
fn new_wrong_account_name() {
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine:github.com".to_string());
let totp = TOTP::new(
Algorithm::SHA1,
6,
1,
1,
"TestSecretSuperSecret",
Some("Github".to_string()),
"constantoine:github.com".to_string(),
);
assert!(totp.is_err());
assert!(matches!(totp.unwrap_err(), TotpUrlError::AccountName(_)));
}
@ -545,7 +603,15 @@ mod tests {
#[test]
#[cfg(feature = "otpauth")]
fn new_wrong_account_name_no_issuer() {
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", None, "constantoine:github.com".to_string());
let totp = TOTP::new(
Algorithm::SHA1,
6,
1,
1,
"TestSecretSuperSecret",
None,
"constantoine:github.com".to_string(),
);
assert!(totp.is_err());
assert!(matches!(totp.unwrap_err(), TotpUrlError::AccountName(_)));
}
@ -553,8 +619,26 @@ mod tests {
#[test]
#[cfg(feature = "otpauth")]
fn comparison_ok() {
let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
let test = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
let reference = TOTP::new(
Algorithm::SHA1,
6,
1,
1,
"TestSecretSuperSecret",
Some("Github".to_string()),
"constantoine@github.com".to_string(),
)
.unwrap();
let test = TOTP::new(
Algorithm::SHA1,
6,
1,
1,
"TestSecretSuperSecret",
Some("Github".to_string()),
"constantoine@github.com".to_string(),
)
.unwrap();
assert_eq!(reference, test);
}
@ -601,7 +685,16 @@ mod tests {
#[test]
#[cfg(feature = "otpauth")]
fn url_for_secret_matches_sha1_without_issuer() {
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", None, "constantoine@github.com".to_string()).unwrap();
let totp = TOTP::new(
Algorithm::SHA1,
6,
1,
1,
"TestSecretSuperSecret",
None,
"constantoine@github.com".to_string(),
)
.unwrap();
let url = totp.get_url();
assert_eq!(url.as_str(), "otpauth://totp/constantoine%40github.com?secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&digits=6&algorithm=SHA1");
}
@ -609,7 +702,16 @@ mod tests {
#[test]
#[cfg(feature = "otpauth")]
fn url_for_secret_matches_sha1() {
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
let totp = TOTP::new(
Algorithm::SHA1,
6,
1,
1,
"TestSecretSuperSecret",
Some("Github".to_string()),
"constantoine@github.com".to_string(),
)
.unwrap();
let url = totp.get_url();
assert_eq!(url.as_str(), "otpauth://totp/Github:constantoine%40github.com?issuer=Github&secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&digits=6&algorithm=SHA1");
}
@ -617,7 +719,16 @@ mod tests {
#[test]
#[cfg(feature = "otpauth")]
fn url_for_secret_matches_sha256() {
let totp = TOTP::new(Algorithm::SHA256, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
let totp = TOTP::new(
Algorithm::SHA256,
6,
1,
1,
"TestSecretSuperSecret",
Some("Github".to_string()),
"constantoine@github.com".to_string(),
)
.unwrap();
let url = totp.get_url();
assert_eq!(url.as_str(), "otpauth://totp/Github:constantoine%40github.com?issuer=Github&secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&digits=6&algorithm=SHA256");
}
@ -625,7 +736,16 @@ mod tests {
#[test]
#[cfg(feature = "otpauth")]
fn url_for_secret_matches_sha512() {
let totp = TOTP::new(Algorithm::SHA512, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
let totp = TOTP::new(
Algorithm::SHA512,
6,
1,
1,
"TestSecretSuperSecret",
Some("Github".to_string()),
"constantoine@github.com".to_string(),
)
.unwrap();
let url = totp.get_url();
assert_eq!(url.as_str(), "otpauth://totp/Github:constantoine%40github.com?issuer=Github&secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&digits=6&algorithm=SHA512");
}
@ -634,7 +754,10 @@ mod tests {
#[cfg(not(feature = "otpauth"))]
fn returns_base32() {
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret").unwrap();
assert_eq!(totp.get_secret_base32().as_str(), "KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ");
assert_eq!(
totp.get_secret_base32().as_str(),
"KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ"
);
}
#[test]
@ -649,9 +772,13 @@ mod tests {
fn generate_token_current() {
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret").unwrap();
let time = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH).unwrap()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
assert_eq!(totp.generate(time).as_str(), totp.generate_current().unwrap());
assert_eq!(
totp.generate(time).as_str(),
totp.generate_current().unwrap()
);
}
#[test]
@ -679,7 +806,9 @@ mod tests {
#[cfg(not(feature = "otpauth"))]
fn checks_token_current() {
let totp = TOTP::new(Algorithm::SHA1, 6, 0, 1, "TestSecretSuperSecret").unwrap();
assert!(totp.check_current(&totp.generate_current().unwrap()).unwrap());
assert!(totp
.check_current(&totp.generate_current().unwrap())
.unwrap());
assert!(!totp.check_current("bogus").unwrap());
}
@ -714,16 +843,28 @@ mod tests {
fn from_url_err() {
assert!(TOTP::<Vec<u8>>::from_url("otpauth://hotp/123").is_err());
assert!(TOTP::<Vec<u8>>::from_url("otpauth://totp/GitHub:test").is_err());
assert!(TOTP::<Vec<u8>>::from_url("otpauth://totp/GitHub:test:?secret=ABC&digits=8&period=60&algorithm=SHA256").is_err());
assert!(TOTP::<Vec<u8>>::from_url(
"otpauth://totp/GitHub:test:?secret=ABC&digits=8&period=60&algorithm=SHA256"
)
.is_err());
assert!(TOTP::<Vec<u8>>::from_url("otpauth://totp/Github:constantoine%40github.com?issuer=GitHub&secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&digits=6&algorithm=SHA1").is_err())
}
#[test]
#[cfg(feature = "otpauth")]
fn from_url_default() {
let totp = TOTP::<Vec<u8>>::from_url("otpauth://totp/GitHub:test?secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ").unwrap();
assert_eq!(totp.secret, base32::decode(base32::Alphabet::RFC4648 { padding: false }, "KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ").unwrap());
let totp = TOTP::<Vec<u8>>::from_url(
"otpauth://totp/GitHub:test?secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ",
)
.unwrap();
assert_eq!(
totp.secret,
base32::decode(
base32::Alphabet::RFC4648 { padding: false },
"KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ"
)
.unwrap()
);
assert_eq!(totp.algorithm, Algorithm::SHA1);
assert_eq!(totp.digits, 6);
assert_eq!(totp.skew, 1);
@ -734,7 +875,14 @@ mod tests {
#[cfg(feature = "otpauth")]
fn from_url_query() {
let totp = TOTP::<Vec<u8>>::from_url("otpauth://totp/GitHub:test?secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&digits=8&period=60&algorithm=SHA256").unwrap();
assert_eq!(totp.secret, base32::decode(base32::Alphabet::RFC4648 { padding: false }, "KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ").unwrap());
assert_eq!(
totp.secret,
base32::decode(
base32::Alphabet::RFC4648 { padding: false },
"KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ"
)
.unwrap()
);
assert_eq!(totp.algorithm, Algorithm::SHA256);
assert_eq!(totp.digits, 8);
assert_eq!(totp.skew, 1);
@ -745,7 +893,16 @@ mod tests {
#[cfg(feature = "otpauth")]
fn from_url_to_url() {
let totp = TOTP::<Vec<u8>>::from_url("otpauth://totp/Github:constantoine%40github.com?issuer=Github&secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&digits=6&algorithm=SHA1").unwrap();
let totp_bis = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
let totp_bis = TOTP::new(
Algorithm::SHA1,
6,
1,
1,
"TestSecretSuperSecret",
Some("Github".to_string()),
"constantoine@github.com".to_string(),
)
.unwrap();
assert_eq!(totp.get_url(), totp_bis.get_url());
}
@ -753,7 +910,16 @@ mod tests {
#[cfg(feature = "otpauth")]
fn from_url_issuer_special() {
let totp = TOTP::<Vec<u8>>::from_url("otpauth://totp/Github%40:constantoine%40github.com?issuer=Github%40&secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&digits=6&algorithm=SHA1").unwrap();
let totp_bis = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github@".to_string()), "constantoine@github.com".to_string()).unwrap();
let totp_bis = TOTP::new(
Algorithm::SHA1,
6,
1,
1,
"TestSecretSuperSecret",
Some("Github@".to_string()),
"constantoine@github.com".to_string(),
)
.unwrap();
assert_eq!(totp.get_url(), totp_bis.get_url());
assert_eq!(totp.issuer.unwrap(), "Github@");
}
@ -762,7 +928,14 @@ mod tests {
#[cfg(feature = "otpauth")]
fn from_url_query_issuer() {
let totp = TOTP::<Vec<u8>>::from_url("otpauth://totp/GitHub:test?issuer=GitHub&secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&digits=8&period=60&algorithm=SHA256").unwrap();
assert_eq!(totp.secret, base32::decode(base32::Alphabet::RFC4648 { padding: false }, "KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ").unwrap());
assert_eq!(
totp.secret,
base32::decode(
base32::Alphabet::RFC4648 { padding: false },
"KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ"
)
.unwrap()
);
assert_eq!(totp.algorithm, Algorithm::SHA256);
assert_eq!(totp.digits, 8);
assert_eq!(totp.skew, 1);
@ -775,7 +948,10 @@ mod tests {
fn from_url_query_different_issuers() {
let totp = TOTP::<Vec<u8>>::from_url("otpauth://totp/GitHub:test?issuer=Gitlab&secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&digits=8&period=60&algorithm=SHA256");
assert!(totp.is_err());
assert!(matches!(totp.unwrap_err(), TotpUrlError::IssuerMistmatch(_, _)));
assert!(matches!(
totp.unwrap_err(),
TotpUrlError::IssuerMistmatch(_, _)
));
}
#[test]
@ -783,9 +959,19 @@ mod tests {
fn generates_qr() {
use sha2::{Digest, Sha512};
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretSuperSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
let totp = TOTP::new(
Algorithm::SHA1,
6,
1,
1,
"TestSecretSuperSecret",
Some("Github".to_string()),
"constantoine@github.com".to_string(),
)
.unwrap();
let url = totp.get_url();
let qr = qrcodegen::QrCode::encode_text(&url, qrcodegen::QrCodeEcc::Medium).expect("could not generate qr");
let qr = qrcodegen::QrCode::encode_text(&url, qrcodegen::QrCodeEcc::Medium)
.expect("could not generate qr");
let data = totp.get_qr_draw_canvas(qr).into_raw();
// Create hash from image

View File

@ -103,7 +103,7 @@ impl<T: AsRef<[u8]>> Rfc6238<T> {
) -> Result<Rfc6238<T>, Rfc6238Error> {
assert_digits(&digits)?;
assert_secret_length(secret.as_ref())?;
Ok(Rfc6238 {
algorithm: Algorithm::SHA1,
digits,
@ -115,13 +115,10 @@ impl<T: AsRef<[u8]>> Rfc6238<T> {
})
}
#[cfg(not(feature = "otpauth"))]
pub fn new(
digits: usize,
secret: T,
) -> Result<Rfc6238<T>, Rfc6238Error> {
pub fn new(digits: usize, secret: T) -> Result<Rfc6238<T>, Rfc6238Error> {
assert_digits(&digits)?;
assert_secret_length(secret.as_ref())?;
Ok(Rfc6238 {
algorithm: Algorithm::SHA1,
digits,
@ -150,7 +147,7 @@ impl<T: AsRef<[u8]>> Rfc6238<T> {
}
/// Set the `digits`
pub fn digits(&mut self, value:usize) -> Result<(), Rfc6238Error> {
pub fn digits(&mut self, value: usize) -> Result<(), Rfc6238Error> {
assert_digits(&value)?;
self.digits = value;
Ok(())
@ -175,13 +172,7 @@ impl<T: AsRef<[u8]>> TryFrom<Rfc6238<T>> for TOTP<T> {
/// Try to create a [TOTP](struct.TOTP.html) from a [Rfc6238](struct.Rfc6238.html) config
fn try_from(rfc: Rfc6238<T>) -> Result<Self, Self::Error> {
TOTP::new(
rfc.algorithm,
rfc.digits,
rfc.skew,
rfc.step,
rfc.secret,
)
TOTP::new(rfc.algorithm, rfc.digits, rfc.skew, rfc.step, rfc.secret)
}
}
@ -198,7 +189,7 @@ impl<T: AsRef<[u8]>> TryFrom<Rfc6238<T>> for TOTP<T> {
rfc.step,
rfc.secret,
rfc.issuer,
rfc.account_name
rfc.account_name,
)
}
}
@ -228,10 +219,7 @@ mod tests {
#[cfg(not(feature = "otpauth"))]
fn new_rfc_digits() {
for x in 0..=20 {
let rfc = Rfc6238::new(
x,
GOOD_SECRET.to_string(),
);
let rfc = Rfc6238::new(x, GOOD_SECRET.to_string());
if !(6..=8).contains(&x) {
assert!(rfc.is_err());
assert!(matches!(rfc.unwrap_err(), Rfc6238Error::InvalidDigits(_)));
@ -247,16 +235,16 @@ mod tests {
let mut secret = String::from("");
for _ in 0..=20 {
secret = format!("{}{}", secret, "0");
let rfc = Rfc6238::new(
6,
secret.clone(),
);
let rfc = Rfc6238::new(6, secret.clone());
let rfc_default = Rfc6238::with_defaults(secret.clone());
if secret.len() < 16 {
assert!(rfc.is_err());
assert!(matches!(rfc.unwrap_err(), Rfc6238Error::SecretTooSmall(_)));
assert!(rfc_default.is_err());
assert!(matches!(rfc_default.unwrap_err(), Rfc6238Error::SecretTooSmall(_)));
assert!(matches!(
rfc_default.unwrap_err(),
Rfc6238Error::SecretTooSmall(_)
));
} else {
assert!(rfc.is_ok());
assert!(rfc_default.is_ok());
@ -267,11 +255,7 @@ mod tests {
#[test]
#[cfg(not(feature = "otpauth"))]
fn rfc_to_totp_ok() {
let rfc = Rfc6238::new(
8,
GOOD_SECRET.to_string(),
)
.unwrap();
let rfc = Rfc6238::new(8, GOOD_SECRET.to_string()).unwrap();
let totp = TOTP::try_from(rfc);
assert!(totp.is_ok());
let otp = totp.unwrap();
@ -286,7 +270,9 @@ mod tests {
#[cfg(not(feature = "otpauth"))]
fn rfc_to_totp_ok_2() {
let rfc = Rfc6238::with_defaults(
Secret::Encoded("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ".to_string()).to_bytes().unwrap(),
Secret::Encoded("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ".to_string())
.to_bytes()
.unwrap(),
)
.unwrap();
let totp = TOTP::try_from(rfc);

View File

@ -40,7 +40,7 @@
//!
//! println!("code from base32:\t{}", totp_b32.generate_current().unwrap());
//! # }
//!
//!
//! ```
//! - Create a TOTP from a Generated Secret
//! ```
@ -77,8 +77,8 @@
//! # }
//! ```
use std::string::FromUtf8Error;
use base32::{self, Alphabet};
use std::string::FromUtf8Error;
use constant_time_eq::constant_time_eq;
@ -112,7 +112,6 @@ impl Default for Secret {
}
impl Secret {
/// Get the inner String value as a Vec of bytes
pub fn to_bytes(&self) -> Result<Vec<u8>, SecretParseError> {
match self {
@ -138,10 +137,9 @@ impl Secret {
/// Try to transforms a `Secret::Raw` into a `Secret::Encoded`
pub fn to_encoded(&self) -> Self {
match self {
Secret::Raw(s) => Secret::Encoded(base32::encode(
Alphabet::RFC4648 { padding: false },
s,
)),
Secret::Raw(s) => {
Secret::Encoded(base32::encode(Alphabet::RFC4648 { padding: false }, s))
}
Secret::Encoded(_) => self.clone(),
}
}
@ -175,7 +173,7 @@ impl std::fmt::Display for Secret {
s = format!("{}{:02x}", &s, &b);
}
write!(f, "{}", s)
},
}
Secret::Encoded(s) => write!(f, "{}", s),
}
}
@ -218,8 +216,14 @@ mod tests {
#[test]
fn secret_as_bytes() {
let base32_str = String::from(BASE32);
assert_eq!(Secret::Raw(BYTES.to_vec()).to_bytes().unwrap(), BYTES.to_vec());
assert_eq!(Secret::Encoded(base32_str).to_bytes().unwrap(), BYTES.to_vec());
assert_eq!(
Secret::Raw(BYTES.to_vec()).to_bytes().unwrap(),
BYTES.to_vec()
);
assert_eq!(
Secret::Encoded(base32_str).to_bytes().unwrap(),
BYTES.to_vec()
);
}
#[test]

View File

@ -117,25 +117,37 @@ mod tests {
#[test]
fn account_name() {
let error = TotpUrlError::AccountName("Laziz:".to_string());
assert_eq!(error.to_string(), "Account Name can't contain a colon. \"Laziz:\" contains a colon")
assert_eq!(
error.to_string(),
"Account Name can't contain a colon. \"Laziz:\" contains a colon"
)
}
#[test]
fn account_name_decoding() {
let error = TotpUrlError::AccountNameDecoding("Laz&iz".to_string());
assert_eq!(error.to_string(), "Couldn't URL decode \"Laz&iz\"".to_string())
assert_eq!(
error.to_string(),
"Couldn't URL decode \"Laz&iz\"".to_string()
)
}
#[test]
fn algorithm() {
let error = TotpUrlError::Algorithm("SIKE".to_string());
assert_eq!(error.to_string(), "Algorithm can only be SHA1, SHA256 or SHA512, not \"SIKE\"".to_string())
assert_eq!(
error.to_string(),
"Algorithm can only be SHA1, SHA256 or SHA512, not \"SIKE\"".to_string()
)
}
#[test]
fn digits() {
let error = TotpUrlError::Digits("six".to_string());
assert_eq!(error.to_string(), "Could not parse \"six\" as a number.".to_string())
assert_eq!(
error.to_string(),
"Could not parse \"six\" as a number.".to_string()
)
}
#[test]
@ -147,19 +159,28 @@ mod tests {
#[test]
fn host() {
let error = TotpUrlError::Host("hotp".to_string());
assert_eq!(error.to_string(), "Host should be totp, not \"hotp\"".to_string())
assert_eq!(
error.to_string(),
"Host should be totp, not \"hotp\"".to_string()
)
}
#[test]
fn issuer() {
let error = TotpUrlError::Issuer("Iss:uer".to_string());
assert_eq!(error.to_string(), "Issuer can't contain a colon. \"Iss:uer\" contains a colon".to_string())
assert_eq!(
error.to_string(),
"Issuer can't contain a colon. \"Iss:uer\" contains a colon".to_string()
)
}
#[test]
fn issuer_decoding() {
let error = TotpUrlError::IssuerDecoding("iss&uer".to_string());
assert_eq!(error.to_string(), "Couldn't URL decode \"iss&uer\"".to_string())
assert_eq!(
error.to_string(),
"Couldn't URL decode \"iss&uer\"".to_string()
)
}
#[test]
@ -171,25 +192,38 @@ mod tests {
#[test]
fn scheme() {
let error = TotpUrlError::Scheme("https".to_string());
assert_eq!(error.to_string(), "Scheme should be otpauth, not \"https\"".to_string())
assert_eq!(
error.to_string(),
"Scheme should be otpauth, not \"https\"".to_string()
)
}
#[test]
fn secret() {
let error = TotpUrlError::Secret("YoLo".to_string());
assert_eq!(error.to_string(), "Secret \"YoLo\" is not a valid non-padded base32 string".to_string())
assert_eq!(
error.to_string(),
"Secret \"YoLo\" is not a valid non-padded base32 string".to_string()
)
}
#[test]
fn secret_size() {
let error = TotpUrlError::SecretSize(112);
assert_eq!(error.to_string(), "The length of the shared secret MUST be at least 128 bits. 112 bits is not enough".to_string())
assert_eq!(
error.to_string(),
"The length of the shared secret MUST be at least 128 bits. 112 bits is not enough"
.to_string()
)
}
#[test]
#[cfg(feature = "otpauth")]
fn step() {
let error = TotpUrlError::Url(url::ParseError::EmptyHost);
assert_eq!(error.to_string(), "Error parsing URL: empty host".to_string())
assert_eq!(
error.to_string(),
"Error parsing URL: empty host".to_string()
)
}
}
}