Allow use without allocation, decouple issuer and account_name from otpauth feature

This commit is contained in:
Michael Pfaff 2023-04-29 13:39:17 -04:00
parent 81c773f471
commit 542ed7c97f
Signed by: michael
GPG Key ID: CF402C4A012AA9D4
8 changed files with 535 additions and 577 deletions

View File

@ -1,16 +1,18 @@
#[cfg(all(feature = "gen_secret", feature = "otpauth"))]
use totp_rs::{Algorithm, Secret, TOTP};
use totp_rs::{Algorithm, Secret, TOTP, LabeledTOTP};
#[cfg(all(feature = "gen_secret", feature = "otpauth"))]
fn main() {
let secret = Secret::generate_secret();
let totp = TOTP::new(
Algorithm::SHA1,
6,
1,
30,
secret.to_bytes().unwrap(),
let totp = LabeledTOTP::new(
TOTP::new(
Algorithm::SHA1,
6,
1,
30,
secret.to_bytes().unwrap(),
).unwrap(),
None,
"account".to_string(),
)

View File

@ -1,24 +1,8 @@
use totp_rs::{Rfc6238, TOTP};
#[cfg(feature = "otpauth")]
fn main() {
let mut rfc = Rfc6238::with_defaults("totp-sercret-123".as_bytes().to_vec()).unwrap();
// optional, set digits, issuer, account_name
rfc.digits(8).unwrap();
rfc.issuer("issuer".to_string());
rfc.account_name("user-account".to_string());
// create a TOTP from rfc
let totp = TOTP::from_rfc6238(rfc).unwrap();
let code = totp.generate_current().unwrap();
println!("code: {}", code);
}
#[cfg(not(feature = "otpauth"))]
fn main() {
let mut rfc = Rfc6238::with_defaults("totp-sercret-123".into()).unwrap();
// optional, set digits, issuer, account_name
rfc.digits(8).unwrap();

View File

@ -1,6 +1,5 @@
use totp_rs::{Algorithm, Secret, TOTP};
use totp_rs::{Algorithm, Secret, TOTP, LabeledTOTP};
#[cfg(feature = "otpauth")]
fn main() {
// create TOTP from base32 secret
let secret_b32 = Secret::Encoded(String::from("OBWGC2LOFVZXI4TJNZTS243FMNZGK5BNGEZDG"));
@ -10,9 +9,12 @@ fn main() {
1,
30,
secret_b32.to_bytes().unwrap(),
Some("issuer".to_string()),
"user-account".to_string(),
)
.and_then(|totp| LabeledTOTP::new(
totp,
"issuer".to_string(),
"user-account".to_string(),
))
.unwrap();
println!(
@ -37,9 +39,12 @@ fn main() {
1,
30,
secret_raw.to_bytes().unwrap(),
Some("issuer".to_string()),
"user-account".to_string(),
)
.and_then(|totp| LabeledTOTP::new(
totp,
"issuer".to_string(),
"user-account".to_string(),
))
.unwrap();
println!("raw {} ; base32 {}", secret_raw, secret_raw.to_encoded());
@ -48,34 +53,3 @@ fn main() {
totp_raw.generate_current().unwrap()
);
}
#[cfg(not(feature = "otpauth"))]
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();
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 = [
0x70, 0x6c, 0x61, 0x69, 0x6e, 0x2d, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x73, 0x65,
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();
println!("raw {} ; base32 {}", secret_raw, secret_raw.to_encoded());
println!(
"code from raw secret:\t{}",
totp_raw.generate_current().unwrap()
);
}

View File

@ -1,12 +1,12 @@
#[cfg(feature = "steam")]
use totp_rs::{Secret, TOTP};
use totp_rs::{Secret, LabeledTOTP};
#[cfg(feature = "steam")]
#[cfg(feature = "otpauth")]
fn main() {
// create TOTP from base32 secret
let secret_b32 = Secret::Encoded(String::from("OBWGC2LOFVZXI4TJNZTS243FMNZGK5BNGEZDG"));
let totp_b32 = TOTP::new_steam(secret_b32.to_bytes().unwrap(), "user-account".to_string());
let totp_b32 = LabeledTOTP::new_steam(secret_b32.to_bytes().unwrap(), "user-account".to_string());
println!(
"base32 {} ; raw {}",
@ -24,7 +24,7 @@ fn main() {
fn main() {
// create TOTP from base32 secret
let secret_b32 = Secret::Encoded(String::from("OBWGC2LOFVZXI4TJNZTS243FMNZGK5BNGEZDG"));
let totp_b32 = TOTP::new_steam(secret_b32.to_bytes().unwrap());
let totp_b32 = LabeledTOTP::new_steam(secret_b32.to_bytes().unwrap());
println!(
"base32 {} ; raw {}",

View File

@ -1,21 +1,5 @@
use totp_rs::{Algorithm, TOTP};
use totp_rs::{Algorithm, TOTP, LabeledTOTP};
#[cfg(not(feature = "otpauth"))]
fn main() {
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 30, "my-secret".as_bytes().to_vec()).unwrap();
loop {
println!(
"code {}\t ttl {}\t valid until: {}",
totp.generate_current().unwrap(),
totp.ttl().unwrap(),
totp.next_step_current().unwrap()
);
std::thread::sleep(std::time::Duration::from_secs(1));
}
}
#[cfg(feature = "otpauth")]
fn main() {
let totp = TOTP::new(
Algorithm::SHA1,
@ -23,9 +7,12 @@ fn main() {
1,
30,
"my-secret".as_bytes().to_vec(),
Some("Github".to_string()),
"constantoine@github.com".to_string(),
)
.and_then(|totp| LabeledTOTP::new(
totp,
"Github".to_string(),
"constantoine@github.com".to_string(),
))
.unwrap();
loop {

View File

@ -1,45 +1,31 @@
#[cfg(feature = "steam")]
use crate::{Algorithm, TOTP};
use crate::{Algorithm, TOTP, LabeledTOTP};
#[cfg(feature = "steam")]
impl TOTP {
#[cfg(feature = "otpauth")]
impl LabeledTOTP {
/// Will create a new instance of TOTP using the Steam algorithm with given parameters. See [the doc](struct.TOTP.html#fields) for reference as to how to choose those values
///
/// # Description
/// * `secret`: expect a non-encoded value, to pass in base32 string use `Secret::Encoded(String)`
///
/// ```rust
/// use totp_rs::{Secret, TOTP};
/// use totp_rs::{Secret, LabeledTOTP};
/// let secret = Secret::Encoded("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".into());
/// let totp = TOTP::new_steam(secret.to_bytes().unwrap(), "username".into());
/// let totp = LabeledTOTP::new_steam(secret.to_bytes().unwrap(), "username".into());
/// ```
pub fn new_steam(secret: Vec<u8>, account_name: String) -> TOTP {
pub fn new_steam(secret: Vec<u8>, account_name: String) -> Self {
Self::new_unchecked(
Algorithm::Steam,
5,
1,
30,
secret,
Some("Steam".into()),
TOTP::new_unchecked(
Algorithm::Steam,
5,
1,
30,
secret,
),
"Steam".to_owned(),
account_name,
)
}
#[cfg(not(feature = "otpauth"))]
/// Will create a new instance of TOTP using the Steam algorithm with given parameters. See [the doc](struct.TOTP.html#fields) for reference as to how to choose those values
///
/// # Description
/// * `secret`: expect a non-encoded value, to pass in base32 string use `Secret::Encoded(String)`
///
/// ```rust
/// use totp_rs::{Secret, TOTP};
/// let secret = Secret::Encoded("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".to_string());
/// let totp = TOTP::new_steam(secret.to_bytes().unwrap());
/// ```
pub fn new_steam(secret: Vec<u8>) -> TOTP {
Self::new_unchecked(Algorithm::Steam, 5, 1, 30, secret)
}
}
#[cfg(all(test, feature = "steam"))]
@ -50,7 +36,7 @@ mod test {
#[test]
#[cfg(feature = "otpauth")]
fn get_url_steam() {
let totp = TOTP::new_steam("TestSecretSuperSecret".into(), "constantoine".into());
let totp = LabeledTOTP::new_steam("TestSecretSuperSecret".into(), "constantoine".into());
let url = totp.get_url();
assert_eq!(url.as_str(), "otpauth://steam/Steam:constantoine?secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&digits=5&algorithm=SHA1&issuer=Steam");
}

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
use crate::Algorithm;
use crate::LabeledTOTP;
use crate::TotpUrlError;
use crate::TOTP;
@ -77,15 +78,6 @@ pub struct Rfc6238 {
step: u64,
/// As per [rfc-4226](https://tools.ietf.org/html/rfc4226#section-4) the secret should come from a strong source, most likely a CSPRNG. It should be at least 128 bits, but 160 are recommended
secret: Vec<u8>,
#[cfg(feature = "otpauth")]
/// The "Github" part of "Github:constantoine@github.com". Must not contain a colon `:`
/// For example, the name of your service/website.
/// Not mandatory, but strongly recommended!
issuer: Option<String>,
#[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.
account_name: String,
}
impl Rfc6238 {
@ -96,27 +88,6 @@ impl Rfc6238 {
/// will return a [Rfc6238Error](enum.Rfc6238Error.html) when
/// - `digits` is lower than 6 or higher than 8
/// - `secret` is smaller than 128 bits (16 characters)
#[cfg(feature = "otpauth")]
pub fn new(
digits: usize,
secret: Vec<u8>,
issuer: Option<String>,
account_name: String,
) -> Result<Rfc6238, Rfc6238Error> {
assert_digits(&digits)?;
assert_secret_length(secret.as_ref())?;
Ok(Rfc6238 {
algorithm: Algorithm::SHA1,
digits,
skew: 1,
step: 30,
secret,
issuer,
account_name,
})
}
#[cfg(not(feature = "otpauth"))]
pub fn new(digits: usize, secret: Vec<u8>) -> Result<Rfc6238, Rfc6238Error> {
assert_digits(&digits)?;
assert_secret_length(secret.as_ref())?;
@ -138,12 +109,6 @@ impl Rfc6238 {
/// will return a [Rfc6238Error](enum.Rfc6238Error.html) when
/// - `digits` is lower than 6 or higher than 8
/// - `secret` is smaller than 128 bits (16 characters)
#[cfg(feature = "otpauth")]
pub fn with_defaults(secret: Vec<u8>) -> Result<Rfc6238, Rfc6238Error> {
Rfc6238::new(6, secret, Some("".to_string()), "".to_string())
}
#[cfg(not(feature = "otpauth"))]
pub fn with_defaults(secret: Vec<u8>) -> Result<Rfc6238, Rfc6238Error> {
Rfc6238::new(6, secret)
}
@ -154,21 +119,8 @@ impl Rfc6238 {
self.digits = value;
Ok(())
}
#[cfg(feature = "otpauth")]
/// Set the `issuer`
pub fn issuer(&mut self, value: String) {
self.issuer = Some(value);
}
#[cfg(feature = "otpauth")]
/// Set the `account_name`
pub fn account_name(&mut self, value: String) {
self.account_name = value;
}
}
#[cfg(not(feature = "otpauth"))]
impl TryFrom<Rfc6238> for TOTP {
type Error = TotpUrlError;
@ -178,8 +130,7 @@ impl TryFrom<Rfc6238> for TOTP {
}
}
#[cfg(feature = "otpauth")]
impl TryFrom<Rfc6238> for TOTP {
impl TryFrom<Rfc6238> for LabeledTOTP {
type Error = TotpUrlError;
/// Try to create a [TOTP](struct.TOTP.html) from a [Rfc6238](struct.Rfc6238.html) config
@ -190,35 +141,21 @@ impl TryFrom<Rfc6238> for TOTP {
rfc.skew,
rfc.step,
rfc.secret,
rfc.issuer,
rfc.account_name,
)
).map(LabeledTOTP::from)
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "otpauth")]
use crate::TotpUrlError;
use super::{Rfc6238, TOTP};
#[cfg(not(feature = "otpauth"))]
use super::Rfc6238Error;
#[cfg(not(feature = "otpauth"))]
use crate::Secret;
const GOOD_SECRET: &str = "01234567890123456789";
#[cfg(feature = "otpauth")]
const ISSUER: Option<&str> = None;
#[cfg(feature = "otpauth")]
const ACCOUNT: &str = "valid-account";
#[cfg(feature = "otpauth")]
const INVALID_ACCOUNT: &str = ":invalid-account";
#[test]
#[cfg(not(feature = "otpauth"))]
fn new_rfc_digits() {
for x in 0..=20 {
let rfc = Rfc6238::new(x, GOOD_SECRET.into());
@ -232,7 +169,6 @@ mod tests {
}
#[test]
#[cfg(not(feature = "otpauth"))]
fn new_rfc_secret() {
let mut secret = String::from("");
for _ in 0..=20 {
@ -255,7 +191,6 @@ mod tests {
}
#[test]
#[cfg(not(feature = "otpauth"))]
fn rfc_to_totp_ok() {
let rfc = Rfc6238::new(8, GOOD_SECRET.into()).unwrap();
let totp = TOTP::try_from(rfc);
@ -269,7 +204,6 @@ mod tests {
}
#[test]
#[cfg(not(feature = "otpauth"))]
fn rfc_to_totp_ok_2() {
let rfc = Rfc6238::with_defaults(
Secret::Encoded("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ".to_string())
@ -287,51 +221,6 @@ mod tests {
}
#[test]
#[cfg(feature = "otpauth")]
fn rfc_to_totp_fail() {
let rfc = Rfc6238::new(
8,
GOOD_SECRET.as_bytes().to_vec(),
ISSUER.map(str::to_string),
INVALID_ACCOUNT.to_string(),
)
.unwrap();
let totp = TOTP::try_from(rfc);
assert!(totp.is_err());
assert!(matches!(totp.unwrap_err(), TotpUrlError::AccountName(_)))
}
#[test]
#[cfg(feature = "otpauth")]
fn rfc_to_totp_ok() {
let rfc = Rfc6238::new(
8,
GOOD_SECRET.as_bytes().to_vec(),
ISSUER.map(str::to_string),
ACCOUNT.to_string(),
)
.unwrap();
let totp = TOTP::try_from(rfc);
assert!(totp.is_ok());
}
#[test]
#[cfg(feature = "otpauth")]
fn rfc_with_default_set_values() {
let mut rfc = Rfc6238::with_defaults(GOOD_SECRET.as_bytes().to_vec()).unwrap();
let ok = rfc.digits(8);
assert!(ok.is_ok());
assert_eq!(rfc.account_name, "");
assert_eq!(rfc.issuer, Some("".to_string()));
rfc.issuer("Github".to_string());
rfc.account_name("constantoine".to_string());
assert_eq!(rfc.account_name, "constantoine");
assert_eq!(rfc.issuer, Some("Github".to_string()));
assert_eq!(rfc.digits, 8)
}
#[test]
#[cfg(not(feature = "otpauth"))]
fn rfc_with_default_set_values() {
let mut rfc = Rfc6238::with_defaults(GOOD_SECRET.as_bytes().to_vec()).unwrap();
let fail = rfc.digits(4);
@ -344,7 +233,6 @@ mod tests {
}
#[test]
#[cfg(not(feature = "otpauth"))]
fn digits_error() {
let error = crate::Rfc6238Error::InvalidDigits(9);
assert_eq!(
@ -354,7 +242,6 @@ mod tests {
}
#[test]
#[cfg(not(feature = "otpauth"))]
fn secret_length_error() {
let error = Rfc6238Error::SecretTooSmall(120);
assert_eq!(