-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathhotp.rs
122 lines (108 loc) · 3.94 KB
/
hotp.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// Implementation of the HOTP standard according to RFC4226 by Tejas Mehta
use crate::otp_result::OTPResult;
use crate::util::{base32_decode, get_code, hash_generic, MacDigest};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
/// A HOTP Generator
///
/// Follows the specification listed in [RFC4226]. Needs a secret and
/// digit count on initialization.
///
/// The HOTP can then be generated using [`HOTP::get_otp`].
///
/// # Example
/// See the top-level README for an example of HOTP usage
///
/// In addition to the example, all other initialization methods can be
/// utilized in a similar manner.
///
/// [RFC4226]: https://datatracker.ietf.org/doc/html/rfc4226
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
#[derive(Debug, Clone, Hash)]
pub struct HOTP {
/// The secret key used in the HMAC process.
///
/// Often given as a Base32 key, which can be conveniently initialized
/// using the [`HOTP::default_from_base32`] constructor.
secret: Vec<u8>,
/// The number of digits of the code generated.
///
/// This value defaults to 6 if not specified in a constructor.
digits: u32,
}
/// All initializer implementations for the [`HOTP`] struct.
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
impl HOTP {
/// Creates a new HOTP instance with a byte-array representation
/// of the secret and specified digit count.
///
/// Since only SHA1 was specified in the reference implementation and
/// RFC specification, there's no need to initialize with a digest object.
pub fn new(secret: &[u8], digits: u32) -> Self {
HOTP {
secret: secret.to_vec(),
digits,
}
}
/// Creates a new HOTP instance from a utf8-encoded string secret
/// and specified digit count.
pub fn new_from_utf8(secret: &str, digits: u32) -> Self {
HOTP::new(secret.as_bytes(), digits)
}
/// Creates a new HOTP instance from a base32-encoded string secret
/// and specified digit count.
///
/// # Panics
/// This method panics if the provided string is not correctly
/// base32-encoded.
pub fn new_from_base32(secret: &str, digits: u32) -> Self {
let decoded = base32_decode(secret).expect("Failed to decode base32 string");
HOTP::new(&decoded, digits)
}
/// Creates a new HOTP instance from a byte-array representation of
/// the secret and a default digit count of 6.
pub fn default_from_secret(secret: &[u8]) -> Self {
HOTP::new(secret, 6)
}
/// Creates a new HOTP instance from an utf8-encoded string secret
/// and a default digit count of 6..
pub fn default_from_utf8(secret: &str) -> Self {
HOTP::new_from_utf8(secret, 6)
}
/// Creates a new HOTP instance from a base32-encoded string secret
/// and a default digit count of 6..
///
/// # Panics
/// This method panics if the provided string is not correctly
/// base32-encoded.
pub fn default_from_base32(secret: &str) -> Self {
HOTP::new_from_base32(secret, 6)
}
}
/// All getters for the ['HOTP'] struct
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
impl HOTP {
/// Gets the number of digits of the code.
pub fn get_digits(&self) -> u32 {
self.digits
}
}
/// All otp generation methods for the [`HOTP`] struct.
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
impl HOTP {
/// Generates and returns the HOTP value.
///
/// Uses the given counter value.
///
/// # Panics
/// This method panics if the hash's secret is incorrectly given.
pub fn get_otp(&self, counter: u64) -> OTPResult {
let hash = hash_generic(&counter.to_be_bytes(), &self.secret, &MacDigest::SHA1);
let offset = (hash[hash.len() - 1] & 0xf) as usize;
let bytes: [u8; 4] = hash[offset..offset + 4]
.try_into()
.expect("Failed byte get");
let code = get_code(bytes, self.digits);
OTPResult::new(self.digits, code)
}
}