Skip to content

Commit ee63ad3

Browse files
authored
Merge pull request #2 from orion78fr/otpauth-uri-parser
Otpauth URI parser
2 parents 3bc8ad6 + 5cb7652 commit ee63ad3

File tree

8 files changed

+508
-151
lines changed

8 files changed

+508
-151
lines changed

Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ description = "An easy-to-use library for HOTP and TOTP authentication"
44
repository = "https://github.com/tmthecoder/xotp"
55
readme = "README.md"
66
license = "MIT"
7-
keywords = ["otp", "hotp", "totp"]
7+
keywords = ["otp", "hotp", "totp", "otpauth"]
88
categories = ["cryptography"]
99
authors = ["Tejas Mehta <[email protected]>"]
10-
version = "0.1.0"
10+
version = "0.2.0"
1111
edition = "2021"
1212

1313
[dependencies]
1414
hmac = "0.12.0"
1515
sha-1 = "0.10.0"
1616
sha2 = "0.10.1"
17-
base32 = "0.4.0"
17+
base32 = "0.4.0"
18+
url = "2.2.2"

src/hotp.rs

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
// Implementation of the HOTP standard according to RFC4226 by Tejas Mehta
22

3-
use crate::util::{get_code, hash_generic, MacDigest};
4-
use base32::Alphabet;
3+
use crate::util::{base32_decode, get_code, hash_generic, MacDigest};
54

65
/// A HOTP Generator
76
///
8-
/// Follows the specification listed in [RFC4226]. Needs a secret on
9-
/// initialization, with other single generation-specific items being
10-
/// provided when [`HOTP::get_otp`] is called.
7+
/// Follows the specification listed in [RFC4226]. Needs a secret and a number of digits on initialization.
8+
/// The HOTP can then be generated using [`HOTP::get_otp`].
119
///
1210
/// # Example
1311
/// See the top-level README for an example of HOTP usage
@@ -22,61 +20,85 @@ pub struct HOTP {
2220
/// The secret key used in the HMAC process.
2321
///
2422
/// Often given as a Base32 key, which can be conveniently initialize using
25-
/// the [`HOTP::from_base32`] initializers
23+
/// the [`HOTP::from_base32`] constructors.
2624
secret: Vec<u8>,
25+
26+
/// The number of digits of the code generated.
27+
///
28+
/// This value defaults to 6 if not specified in a constructor.
29+
digits: u32,
2730
}
2831

2932
/// All initializer implementations for the [`HOTP`] struct.
30-
3133
impl HOTP {
3234
/// Creates a new HOTP instance with a byte-array representation
33-
/// of the secret
35+
/// of the secret and the number of digits.
3436
///
3537
/// Since only SHA1 was specified in the reference implementation and
36-
/// RFC specification, there's no need to initialize with a digest object
37-
pub fn new(secret: &[u8]) -> Self {
38+
/// RFC specification, there's no need to initialize with a digest object.
39+
pub fn new(secret: &[u8], digits: u32) -> Self {
3840
HOTP {
3941
secret: secret.to_vec(),
42+
digits,
4043
}
4144
}
4245

43-
/// Creates a new HOTP instance from a utf8-encoded string secret
44-
///
45-
/// Internally calls [`HOTP::new`] with the string's byte representation
46-
pub fn from_utf8(secret: &str) -> Self {
47-
HOTP::new(secret.as_bytes())
46+
/// Creates a new HOTP instance from an utf8-encoded string secret and the number of digits.
47+
pub fn new_from_utf8(secret: &str, digits: u32) -> Self {
48+
HOTP::new(secret.as_bytes(), digits)
4849
}
4950

50-
/// Creates a new HOTP instance from a base32-encoded string secret
51+
/// Creates a new HOTP instance from a base32-encoded string secret and the number of digits.
5152
///
52-
/// Internally calls [`HOTP::new`] after decoding the string
53+
/// # Panics
54+
/// This method panics if the provided string is not correctly base32 encoded.
55+
pub fn new_from_base32(secret: &str, digits: u32) -> Self {
56+
let decoded = base32_decode(secret).expect("Failed to decode base32 string");
57+
HOTP::new(&decoded, digits)
58+
}
59+
60+
/// Creates a new HOTP instance from a byte-array representation of the secret and
61+
/// a default number of 6 digits.
62+
pub fn default_from_secret(secret: &[u8]) -> Self {
63+
HOTP::new(secret, 6)
64+
}
65+
66+
/// Creates a new HOTP instance from an utf8-encoded string secret and a default number of 6 digits.
67+
pub fn default_from_utf8(secret: &str) -> Self {
68+
HOTP::new_from_utf8(secret, 6)
69+
}
70+
71+
/// Creates a new HOTP instance from a base32-encoded string secret and a default number of 6 digits.
5372
///
5473
/// # Panics
55-
/// This method panics if the provided string is not correctly base32
56-
/// encoded.
57-
pub fn from_base32(secret: &str) -> Self {
58-
let decoded = base32::decode(Alphabet::RFC4648 { padding: false }, secret)
59-
.expect("Failed to decode base32 string");
60-
HOTP::new(&decoded)
74+
/// This method panics if the provided string is not correctly base32 encoded.
75+
pub fn default_from_base32(secret: &str) -> Self {
76+
HOTP::new_from_base32(secret, 6)
6177
}
6278
}
6379

64-
/// All otp generation methods for the [`HOTP`] struct.
80+
impl HOTP {
81+
/// Gets the number of digits of the code.
82+
pub fn get_digits(&self) -> u32 {
83+
self.digits
84+
}
85+
}
6586

87+
/// All otp generation methods for the [`HOTP`] struct.
6688
impl HOTP {
67-
/// Generates and returns the HOTP value
89+
/// Generates and returns the HOTP value.
6890
///
69-
/// Uses the given counter value with the specified digit count
91+
/// Uses the given counter value.
7092
///
7193
/// # Panics
7294
/// This method panics if the hash's secret is incorrectly given.
73-
pub fn get_otp(&self, counter: u64, digits: u32) -> u32 {
95+
pub fn get_otp(&self, counter: u64) -> u32 {
7496
let hash = hash_generic(&counter.to_be_bytes(), &self.secret, &MacDigest::SHA1);
7597
let offset = (hash[hash.len() - 1] & 0xf) as usize;
7698
let bytes: [u8; 4] = hash[offset..offset + 4]
7799
.try_into()
78100
.expect("Failed byte get");
79101

80-
get_code(bytes, digits)
102+
get_code(bytes, self.digits)
81103
}
82104
}

src/lib.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414
//! let secret = "secret";
1515
//! let counter = 0;
1616
//! // Get a HOTP instance with a '&str' secret
17-
//! let hotp_str = HOTP::from_utf8(secret);
18-
//! // Get an otp with the given counter and digit count
19-
//! let otp_from_str = hotp_str.get_otp(counter, 6);
17+
//! let hotp_str = HOTP::default_from_utf8(secret);
18+
//! // Get an otp with the given counter
19+
//! let otp_from_str = hotp_str.get_otp(counter);
2020
//! println!("The otp from hotp_str: {}", otp_from_str);
2121
//!
2222
//! // Alternatively, get a HOTP instance with a '&[u8]' secret
23-
//! let hotp_bytes = HOTP::new(secret.as_bytes());
24-
//! // Get an otp with the given counter and digit count
25-
//! let otp_from_bytes = hotp_bytes.get_otp(counter, 6);
23+
//! let hotp_bytes = HOTP::new(secret.as_bytes(), 6);
24+
//! // Get an otp with the given counter
25+
//! let otp_from_bytes = hotp_bytes.get_otp(counter);
2626
//! println!("The otp from hotp_bytes: {}", otp_from_bytes);
2727
//! }
2828
//! ```
@@ -41,23 +41,23 @@
4141
//! .expect("Error getting time")
4242
//! .as_secs();
4343
//! // Get a TOTP instance with an '&str' secret and default SHA1 Digest
44-
//! let totp_sha1_str = TOTP::from_utf8(secret);
44+
//! let totp_sha1_str = TOTP::default_from_utf8(secret);
4545
//! // Get an otp with the given counter and elapsed seconds
46-
//! let otp_sha1 = totp_sha1_str.get_otp(elapsed_seconds, 8);
46+
//! let otp_sha1 = totp_sha1_str.get_otp(elapsed_seconds);
4747
//! println!("The otp from totp_sha1_str: {}", otp_sha1);
4848
//!
4949
//! // Alternatively get a TOTP instance with an '&[u8]' secret
5050
//! // and different digest (Sha256 or Sha512)
51-
//! let totp_sha256_bytes = TOTP::new_with_digest(
51+
//! let totp_sha256_bytes = TOTP::new(
5252
//! secret.as_bytes(),
53-
//! MacDigest::SHA256
53+
//! MacDigest::SHA256, // SHA256 algorithm
54+
//! 8, // 8 digits
55+
//! 60 // 60-second interval
5456
//! );
5557
//! // Get an otp with the given counter, time and other custom params
56-
//! let otp_sha256 = totp_sha256_bytes.get_otp_with_custom(
58+
//! let otp_sha256 = totp_sha256_bytes.get_otp_with_custom_time_start(
5759
//! elapsed_seconds,
58-
//! 30, // A 60-second time step
5960
//! 0, // Start time at unix epoch
60-
//! 6 // 8-digit code
6161
//! );
6262
//! println!("The otp from totp_sha256_bytes: {}", otp_sha256);
6363
//! }

0 commit comments

Comments
 (0)