Skip to content

Commit

Permalink
update tests
Browse files Browse the repository at this point in the history
  • Loading branch information
eendroroy committed Jan 16, 2025
1 parent 9df156a commit 58d0c0f
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 75 deletions.
6 changes: 3 additions & 3 deletions src/messages.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
pub const COUNTER_INVALID: &str = "Counter must be greater than or equal to 1";
pub const DRIFT_BEHIND_INVALID: &str = "Drift behind must be less than timestamp";
pub const INTERVAL_INVALID: &str = "Interval must be greater than or equal to 30";
pub const OTP_LENGTH_INVALID: &str = "OTP length must be greater than or equal to 4";
pub const OTP_LENGTH_INVALID: &str = "OTP length must be greater than or equal to 1";
pub const OTP_LENGTH_NOT_MATCHED: &str = "OTP length does not match the length of the configuration";
pub const PROV_OTP_LENGTH_INVALID: &str = "HOTP length must be 6";
pub const PROV_OTP_RADIX_INVALID: &str = "HOTP radix must be 10";
pub const PROV_OTP_LENGTH_INVALID: &str = "OTP length must be 6";
pub const PROV_OTP_RADIX_INVALID: &str = "Radix must be 10";
pub const RADIX_INVALID: &str = "Radix must be between 2 and 36 inclusive";
pub const SECRET_EMPTY: &str = "Secret must not be empty";
pub const TIMESTAMP_INVALID: &str = "Timestamp must be greater than or equal to 1";
Expand Down
1 change: 1 addition & 0 deletions src/otp/algorithm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub trait AlgorithmTrait {
fn hash(&self, secret: Vec<u8>, data: u64) -> Result<Vec<u8>, String>;
}

#[derive(Copy, Clone)]
pub enum Algorithm {
SHA1,
SHA256,
Expand Down
13 changes: 4 additions & 9 deletions src/otp/hotp.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::messages::{
OTP_LENGTH_INVALID, OTP_LENGTH_NOT_MATCHED, PROV_OTP_LENGTH_INVALID,
PROV_OTP_RADIX_INVALID, RADIX_INVALID, SECRET_EMPTY, UNSUPPORTED_ALGORITHM,
OTP_LENGTH_INVALID, OTP_LENGTH_NOT_MATCHED, PROV_OTP_LENGTH_INVALID, PROV_OTP_RADIX_INVALID,
RADIX_INVALID, SECRET_EMPTY, UNSUPPORTED_ALGORITHM,
};
use crate::otp::algorithm::Algorithm;
use crate::otp::otp::otp;
Expand All @@ -21,7 +21,7 @@ impl HOTP {
) -> Result<HOTP, &'static str> {
if secret.len() < 1 {
Err(SECRET_EMPTY)
} else if length < 4 {
} else if length < 1 {
Err(OTP_LENGTH_INVALID)
} else if radix < 2 || radix > 36 {
Err(RADIX_INVALID)
Expand All @@ -45,12 +45,7 @@ impl HOTP {
)
}

pub fn verify(
&self,
otp: &str,
counter: u64,
retries: u64,
) -> Result<Option<u64>, String> {
pub fn verify(&self, otp: &str, counter: u64, retries: u64) -> Result<Option<u64>, String> {
if self.length != otp.len() as u8 {
Err(OTP_LENGTH_NOT_MATCHED.to_string())
} else {
Expand Down
10 changes: 2 additions & 8 deletions src/otp/otp.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::messages::{COUNTER_INVALID, OTP_LENGTH_INVALID, RADIX_INVALID, SECRET_EMPTY};
use crate::messages::COUNTER_INVALID;

use crate::otp::algorithm::{Algorithm, AlgorithmTrait};
use num_bigint::BigUint;
Expand All @@ -11,13 +11,7 @@ pub(crate) fn otp(
radix: u8,
counter: u64,
) -> Result<String, String> {
if secret.len() < 1 {
Err(SECRET_EMPTY.to_string())
} else if length < 4 {
Err(OTP_LENGTH_INVALID.to_string())
} else if radix < 2 || radix > 36 {
Err(RADIX_INVALID.to_string())
} else if counter < 1 {
if counter < 1 {
Err(COUNTER_INVALID.to_string())
} else {
match otp_bin_code(algorithm, secret, counter) {
Expand Down
263 changes: 208 additions & 55 deletions tests/hotp.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,70 @@
use rusotp::Algorithm;
use rusotp::{generate_hotp, Algorithm, HOTP};

const SECRET: &str = "12345678901234567890";

#[test]
fn fail_with_empty_secret() {
let result = rusotp::HOTP::new(Algorithm::SHA256, "", 1, 1);
let result = HOTP::new(Algorithm::SHA256, "", 1, 1);
assert!(result.is_err(), "Expected an error");
assert_eq!(result.err().unwrap(), "Secret must not be empty");
}

#[test]
fn fail_with_invalid_otp_length() {
let result = rusotp::HOTP::new(Algorithm::SHA256, "12312341234", 1, 10);
#[should_panic(expected = "Secret must not be empty")]
fn generate_hotp_should_fail_with_empty_secret() {
generate_hotp(Algorithm::SHA256, "", 1, 1, 0);
}

#[test]
fn fail_with_otp_length_less_than_1() {
let result = HOTP::new(Algorithm::SHA256, SECRET, 0, 10);
assert!(result.is_err(), "Expected an error");
assert_eq!(
result.err().unwrap(),
"OTP length must be greater than or equal to 4"
"OTP length must be greater than or equal to 1"
);
}

#[test]
fn fail_with_invalid_radix() {
let result = rusotp::HOTP::new(Algorithm::SHA256, "12312341234", 4, 1);
assert!(result.is_err(), "Expected an error");
#[should_panic(expected = "OTP length must be greater than or equal to 1")]
fn generate_hotp_should_fail_otp_length_less_than_1() {
generate_hotp(Algorithm::SHA256, SECRET, 0, 10, 0);
}

#[test]
fn fail_with_radix_less_than_2() {
let lesser_radix = HOTP::new(Algorithm::SHA256, SECRET, 4, 1);
assert!(lesser_radix.is_err(), "Expected an error");
assert_eq!(
result.err().unwrap(),
lesser_radix.err().unwrap(),
"Radix must be between 2 and 36 inclusive"
);
}

#[test]
#[should_panic(expected = "Radix must be between 2 and 36 inclusive")]
fn generate_hotp_should_fail_radix_less_than_2() {
generate_hotp(Algorithm::SHA256, SECRET, 4, 1, 0);
}
#[test]
fn fail_with_radix_greater_than_36() {
let greater_radix = HOTP::new(Algorithm::SHA256, SECRET, 4, 37);
assert!(greater_radix.is_err(), "Expected an error");
assert_eq!(
greater_radix.err().unwrap(),
"Radix must be between 2 and 36 inclusive"
);
}

#[test]
fn fail_with_invalid_counter() {
let hotp = match rusotp::HOTP::new(Algorithm::SHA256, "12312341234", 4, 10) {
#[should_panic(expected = "Radix must be between 2 and 36 inclusive")]
fn generate_hotp_should_fail_radix_more_than_36() {
generate_hotp(Algorithm::SHA256, SECRET, 4, 37, 0);
}

#[test]
fn fail_with_counter_less_than_1() {
let hotp = match HOTP::new(Algorithm::SHA256, SECRET, 4, 10) {
Ok(hotp) => hotp,
Err(e) => panic!("{}", e),
};
Expand All @@ -44,12 +79,14 @@ fn fail_with_invalid_counter() {
}

#[test]
fn fail_with_otp_length_not_matched() {
let hotp = match rusotp::HOTP::new(Algorithm::SHA256, "12312341234", 4, 10) {
Ok(hotp) => hotp,
Err(e) => panic!("{}", e),
};
#[should_panic(expected = "Counter must be greater than or equal to 1")]
fn generate_hotp_should_fail_with_counter_less_than_1() {
generate_hotp(Algorithm::SHA256, SECRET, 4, 10, 0);
}

#[test]
fn fail_with_otp_length_not_matched() {
let hotp = HOTP::new(Algorithm::SHA256, SECRET, 4, 10).unwrap();
let result = hotp.verify("12345", 10, 0);

assert!(result.is_err(), "Expected an error");
Expand All @@ -61,8 +98,34 @@ fn fail_with_otp_length_not_matched() {

#[test]
fn generated_otp_is_correct() {
let secret = "12345678901234567890";
let data = vec![
(6, 10, 1, "247374"),
(6, 10, 2, "254785"),
(6, 10, 3, "496144"),
(6, 16, 1, "687B4E"),
(6, 24, 1, "N7C1B6"),
(6, 36, 1, "M16ONI"),
(8, 10, 100, "93583477"),
(8, 16, 100, "23615D75"),
(8, 24, 100, "032D2EKL"),
(8, 36, 100, "009TEJXX"),
(4, 36, 1, "6ONI"),
(4, 36, 2, "KYWX"),
(4, 36, 3, "ERBK"),
(4, 36, 4, "ROTO"),
];

data.iter().for_each(|(length, radix, counter, otp)| {
let hotp = HOTP::new(Algorithm::SHA256, SECRET, *length, *radix).unwrap();

let result = hotp.generate(*counter);
assert!(result.is_ok(), "Expected a result");
assert_eq!(result.unwrap(), otp.to_string());
});
}

#[test]
fn wrong_otp_does_not_get_verified() {
let data = vec![
(6, 10, 1, "247374"),
(6, 10, 2, "254785"),
Expand All @@ -81,53 +144,143 @@ fn generated_otp_is_correct() {
];

data.iter().for_each(|(length, radix, counter, otp)| {
match rusotp::HOTP::new(Algorithm::SHA256, secret, *length, *radix) {
Ok(hotp) => {
let result = hotp.generate(*counter);
assert!(result.is_ok(), "Expected a result");
assert_eq!(result.unwrap(), otp.to_string());
}
Err(e) => panic!("{}", e),
};
let hotp = HOTP::new(Algorithm::SHA256, SECRET, *length, *radix).unwrap();

let result = hotp.verify(otp, *counter + 1, 0);
assert!(result.is_ok(), "Expected a result");
assert!(result.unwrap().is_none(), "Expected a failed verification");
});
}

#[test]
fn otp_get_verified_with_retries() {
let data = vec![
(6, 10, 2, "254785", 1),
(6, 10, 3, "496144", 1),
(8, 10, 100, "93583477", 5),
(8, 16, 100, "23615D75", 1),
(8, 24, 100, "032D2EKL", 1),
(8, 36, 100, "009TEJXX", 1),
(4, 36, 2, "KYWX", 1),
(4, 36, 3, "ERBK", 1),
(4, 36, 4, "ROTO", 1),
];

data.iter()
.for_each(|(length, radix, counter, otp, retries)| {
let hotp = HOTP::new(Algorithm::SHA256, SECRET, *length, *radix).unwrap();

let result = hotp.verify(otp, *counter - *retries, *retries);
assert!(result.is_ok(), "Expected a result");
assert!(
result.unwrap().is_some(),
"Expected a successful verification"
);
});
}

#[test]
fn generated_otp_gets_verified() {
let secret = "12345678901234567890";

let data = vec![
(6, 10, 1),
(6, 10, 2),
(6, 10, 3),
(6, 16, 1),
(6, 24, 1),
(6, 36, 1),
(8, 10, 100),
(8, 16, 100),
(8, 24, 100),
(8, 36, 100),
(4, 36, 1),
(4, 36, 2),
(4, 36, 3),
(Algorithm::SHA256, 6, 10, 1),
(Algorithm::SHA256, 6, 10, 2),
(Algorithm::SHA256, 6, 10, 3),
(Algorithm::SHA256, 6, 16, 1),
(Algorithm::SHA256, 6, 24, 1),
(Algorithm::SHA256, 6, 36, 1),
(Algorithm::SHA256, 8, 10, 100),
(Algorithm::SHA256, 8, 16, 100),
(Algorithm::SHA256, 8, 24, 100),
(Algorithm::SHA256, 8, 36, 100),
(Algorithm::SHA256, 4, 36, 1),
(Algorithm::SHA256, 4, 36, 2),
(Algorithm::SHA256, 4, 36, 3),
];

data.iter().for_each(|(length, radix, counter)| {
match rusotp::HOTP::new(Algorithm::SHA256, secret, *length, *radix) {
Ok(hotp) => {
match hotp.generate(*counter) {
Ok(otp) => {
let result = hotp.verify(&otp, *counter, 0);
assert!(result.is_ok(), "Expected a result");
assert!(
result.unwrap().is_some(),
"Expected a successful verification"
);
}
Err(e) => panic!("{}", e),
};
}
Err(e) => panic!("{}", e),
};
data.iter().for_each(|(algorithm, length, radix, counter)| {
let hotp = HOTP::new(*algorithm, secret, *length, *radix).unwrap();
let otp = hotp.generate(*counter).unwrap();

let result = hotp.verify(&otp, *counter, 0);
assert!(result.is_ok(), "Expected a result");
assert!(
result.unwrap().is_some(),
"Expected a successful verification"
);
});
}

#[test]
fn provisioning_uri_is_correct() {
let hotp_tool = HOTP::new(Algorithm::SHA1, SECRET, 6, 10).unwrap();

let result = hotp_tool.provisioning_uri("test", 0);

assert!(result.is_ok(), "Expected a result");
assert_eq!(
result.unwrap(),
"otpauth://hotp/test?secret=12345678901234567890&counter=0"
);
}

#[test]
fn fail_provisioning_uri_with_sha256() {
let hotp_tool = HOTP::new(Algorithm::SHA256, SECRET, 6, 10).unwrap();

let result = hotp_tool.provisioning_uri("test", 0);

assert!(result.is_err(), "Expected an error");
assert_eq!(result.err().unwrap(), "Unsupported algorithm");
}

#[test]
fn fail_provisioning_uri_with_sha512() {
let hotp_tool = HOTP::new(Algorithm::SHA512, SECRET, 6, 10).unwrap();

let result = hotp_tool.provisioning_uri("test", 0);

assert!(result.is_err(), "Expected an error");
assert_eq!(result.err().unwrap(), "Unsupported algorithm");
}

#[test]
fn fail_provisioning_uri_with_otp_length_less_than_6() {
let hotp_tool = HOTP::new(Algorithm::SHA512, SECRET, 4, 10).unwrap();

let result = hotp_tool.provisioning_uri("test", 0);

assert!(result.is_err(), "Expected an error");
assert_eq!(result.err().unwrap(), "OTP length must be 6");
}

#[test]
fn fail_provisioning_uri_with_otp_length_more_than_6() {
let hotp_tool = HOTP::new(Algorithm::SHA512, SECRET, 8, 10).unwrap();

let result = hotp_tool.provisioning_uri("test", 0);

assert!(result.is_err(), "Expected an error");
assert_eq!(result.err().unwrap(), "OTP length must be 6");
}

#[test]
fn fail_provisioning_uri_with_radix_less_than_10() {
let hotp_tool = HOTP::new(Algorithm::SHA512, SECRET, 6, 9).unwrap();

let result = hotp_tool.provisioning_uri("test", 0);

assert!(result.is_err(), "Expected an error");
assert_eq!(result.err().unwrap(), "Radix must be 10");
}

#[test]
fn fail_provisioning_uri_with_radix_more_than_10() {
let hotp_tool = HOTP::new(Algorithm::SHA512, SECRET, 6, 11).unwrap();

let result = hotp_tool.provisioning_uri("test", 0);

assert!(result.is_err(), "Expected an error");
assert_eq!(result.err().unwrap(), "Radix must be 10");
}

0 comments on commit 58d0c0f

Please sign in to comment.