Skip to main content

libreauth/
oath.rs

1//! Implementation of standards which are part of the [OATH Reference
2//! Architecture](https://openauthentication.org/specifications-technical-resources/).
3//!
4//! ## Examples
5//!
6//! ```rust
7//! let key_ascii = "12345678901234567890".to_owned();
8//! let mut hotp = libreauth::oath::HOTPBuilder::new()
9//!     .ascii_key(&key_ascii)
10//!     .finalize()
11//!     .unwrap();
12//!
13//! let code = hotp.generate();
14//! assert_eq!(code, "755224");
15//! assert!(hotp.is_valid(&"755224".to_owned()));
16//!
17//! let code = hotp.increment_counter().generate();
18//! assert_eq!(code, "287082");
19//! assert!(hotp.is_valid(&"287082".to_owned()));
20//! ```
21//!
22//! ```rust
23//! let key_base32 = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ".to_owned();
24//! let mut totp = libreauth::oath::TOTPBuilder::new()
25//!     .base32_key(&key_base32)
26//!     .finalize()
27//!     .unwrap();
28//!
29//! let code = totp.generate();
30//! println!("TOTP code: {}", code);
31//!
32//! assert!(totp.is_valid(&code));
33//! ```
34
35use crate::hash::HashFunction;
36
37#[cfg(feature = "oath-uri")]
38const DEFAULT_KEY_URI_PARAM_POLICY: ParametersVisibility = ParametersVisibility::ShowNonDefault;
39const DEFAULT_OTP_HASH: HashFunction = HashFunction::Sha1;
40const DEFAULT_OTP_OUT_BASE: &str = "0123456789";
41const DEFAULT_OTP_OUT_LEN: usize = 6;
42const DEFAULT_TOTP_PERIOD: u32 = 30;
43const DEFAULT_TOTP_T0: u64 = 0;
44const DEFAULT_LOOK_AHEAD: u64 = 0;
45
46/// Error codes used in the C interface.
47///
48/// ## C interface
49/// The C interface uses an enum of type `libreauth_oath_errno` and the
50/// members has been renamed as follows:
51/// <table>
52///     <thead>
53///         <tr>
54///             <th>Rust</th>
55///             <th>C</th>
56///         </tr>
57///     </thead>
58///     <tbody>
59///         <tr>
60///             <td>Success</td>
61///             <td>LIBREAUTH_OATH_SUCCESS</td>
62///         </tr>
63///         <tr>
64///             <td>NullPtr</td>
65///             <td>LIBREAUTH_OATH_NULL_PTR</td>
66///         </tr>
67///         <tr>
68///             <td>NotEnoughSpace</td>
69///             <td>LIBREAUTH_OATH_NOT_ENOUGH_SPACE</td>
70///         </tr>
71///         <tr>
72///             <td>InvalidBaseLen</td>
73///             <td>LIBREAUTH_OATH_INVALID_BASE_LEN</td>
74///         </tr>
75///         <tr>
76///             <td>InvalidKeyLen</td>
77///             <td>LIBREAUTH_OATH_INVALID_KEY_LEN</td>
78///         </tr>
79///         <tr>
80///             <td>CodeTooSmall</td>
81///             <td>LIBREAUTH_OATH_CODE_TOO_SMALL</td>
82///         </tr>
83///         <tr>
84///             <td>CodeTooBig</td>
85///             <td>LIBREAUTH_OATH_CODE_TOO_BIG</td>
86///         </tr>
87///         <tr>
88///             <td>InvalidKey</td>
89///             <td>LIBREAUTH_OATH_INVALID_KEY</td>
90///         </tr>
91///         <tr>
92///             <td>InvalidPeriod</td>
93///             <td>LIBREAUTH_OATH_INVALID_PERIOD</td>
94///         </tr>
95///         <tr>
96///             <td>InvalidUTF8</td>
97///             <td>LIBREAUTH_OATH_INVALID_UTF8</td>
98///         </tr>
99///     </tbody>
100/// </table>
101#[repr(C)]
102#[derive(Clone, Copy, Debug)]
103pub enum ErrorCode {
104	Success = 0,
105
106	NullPtr = 1,
107	NotEnoughSpace = 2,
108
109	InvalidBaseLen = 10,
110	InvalidKeyLen = 11,
111	CodeTooSmall = 12,
112	CodeTooBig = 13,
113
114	InvalidKey = 20,
115	InvalidPeriod = 21,
116
117	InvalidUTF8 = 30,
118}
119
120/// Errors used for the Rust interface.
121///
122/// *To implement `std::error::Error`, the `stderror` feature must be activated*
123#[derive(Clone, Copy, Debug)]
124#[cfg_attr(feature = "thiserror", derive(thiserror::Error))]
125pub enum Error {
126	#[cfg_attr(feature = "thiserror", error("Code too small"))]
127	CodeTooSmall,
128	#[cfg_attr(feature = "thiserror", error("Code too big"))]
129	CodeTooBig,
130
131	#[cfg_attr(feature = "thiserror", error("Invalid key"))]
132	InvalidKey,
133
134	#[cfg_attr(feature = "thiserror", error("Invalid period"))]
135	InvalidPeriod,
136}
137
138impl From<Error> for ErrorCode {
139	fn from(error: Error) -> Self {
140		match error {
141			Error::CodeTooSmall => ErrorCode::CodeTooSmall,
142			Error::CodeTooBig => ErrorCode::CodeTooBig,
143			Error::InvalidKey => ErrorCode::InvalidKey,
144			Error::InvalidPeriod => ErrorCode::InvalidPeriod,
145		}
146	}
147}
148
149macro_rules! builder_common {
150	() => {
151		/// Sets the shared secret.
152		pub fn key(&mut self, key: &[u8]) -> &mut Self {
153			self.key = Some(key.to_owned());
154			self
155		}
156
157		/// Sets the shared secret. This secret is passed as an ASCII string.
158		pub fn ascii_key(&mut self, key: &str) -> &mut Self {
159			self.key = Some(key.as_bytes().to_vec());
160			self
161		}
162
163		/// Sets the shared secret. This secret is passed as an hexadecimal encoded string.
164		pub fn hex_key(&mut self, key: &str) -> &mut Self {
165			match hex::decode(key) {
166				Ok(k) => {
167					self.key = Some(k);
168				}
169				Err(_) => {
170					self.runtime_error = Some(Error::InvalidKey);
171				}
172			}
173			self
174		}
175
176		/// Sets the shared secret. This secret is passed as a base32 encoded string.
177		pub fn base32_key(&mut self, key: &str) -> &mut Self {
178			match base32::decode(base32::Alphabet::Rfc4648 { padding: false }, &key) {
179				Some(k) => {
180					self.key = Some(k);
181				}
182				None => {
183					self.runtime_error = Some(Error::InvalidKey);
184				}
185			}
186			self
187		}
188
189		/// Sets the shared secret. This secret is passed as a base64 encoded string.
190		pub fn base64_key(&mut self, key: &str) -> &mut Self {
191			use base64::Engine;
192			match base64::engine::general_purpose::STANDARD.decode(key) {
193				Ok(k) => {
194					self.key = Some(k);
195				}
196				Err(_) => {
197					self.runtime_error = Some(Error::InvalidKey);
198				}
199			}
200			self
201		}
202
203		fn code_length(&self) -> usize {
204			let base_len = self.output_base.len();
205			let mut nb_bits = base_len;
206			for _ in 1..self.output_len {
207				nb_bits = match nb_bits.checked_mul(base_len) {
208					Some(nb_bits) => nb_bits,
209					None => return usize::MAX,
210				};
211			}
212			nb_bits
213		}
214
215		/// Sets the number of characters for the code. The minimum and maximum values depends the base. Default is 6.
216		pub fn output_len(&mut self, output_len: usize) -> &mut Self {
217			self.output_len = output_len;
218			self
219		}
220
221		/// Sets the base used to represents the output code. Default is "0123456789".
222		pub fn output_base(&mut self, base: &str) -> &mut Self {
223			self.output_base = base.to_string();
224			self
225		}
226
227		/// Sets the hash function. Default is Sha1.
228		pub fn hash_function(&mut self, hash_function: HashFunction) -> &mut Self {
229			self.hash_function = hash_function;
230			self
231		}
232	};
233}
234
235#[cfg(feature = "oath-uri")]
236mod key_uri;
237#[cfg(feature = "oath-uri")]
238pub use self::key_uri::{KeyUriBuilder, ParametersVisibility};
239
240mod hotp;
241pub use self::hotp::HOTP;
242pub use self::hotp::HOTPBuilder;
243
244mod totp;
245pub use self::totp::TOTP;
246pub use self::totp::TOTPBuilder;
247
248#[cfg(feature = "cbindings")]
249mod cbindings;
250#[cfg(feature = "cbindings")]
251pub use self::cbindings::HOTPcfg;
252#[cfg(feature = "cbindings")]
253pub use self::cbindings::libreauth_hotp_generate;
254#[cfg(all(feature = "cbindings", feature = "oath-uri"))]
255pub use self::cbindings::libreauth_hotp_get_uri;
256#[cfg(feature = "cbindings")]
257pub use self::cbindings::libreauth_hotp_init;
258#[cfg(feature = "cbindings")]
259pub use self::cbindings::libreauth_hotp_is_valid;
260
261#[cfg(feature = "cbindings")]
262pub use self::cbindings::TOTPcfg;
263#[cfg(feature = "cbindings")]
264pub use self::cbindings::libreauth_totp_generate;
265#[cfg(all(feature = "cbindings", feature = "oath-uri"))]
266pub use self::cbindings::libreauth_totp_get_uri;
267#[cfg(feature = "cbindings")]
268pub use self::cbindings::libreauth_totp_init;
269#[cfg(feature = "cbindings")]
270pub use self::cbindings::libreauth_totp_is_valid;