libr2fa/lib.rs
1use std::any::Any;
2
3/// rust implementation for HTOP, TOTP and steam guard tow-factor-authentication
4///
5/// usage:
6/// ```rust
7/// use libr2fa::HOTPKey;
8/// use libr2fa::HMACType;
9/// use libr2fa::Key;
10///
11/// let mut hotp_key = HOTPKey {
12/// key: "MFSWS5LGNBUXKZLBO5TGQ33JO5SWC2DGNF2WCZLIMZUXKZLXMFUGM2LVNFQWK53IMZUXK2A=".to_string(),
13/// hmac_type: HMACType::SHA1,
14/// ..Default::default()
15/// };
16///
17/// let code = hotp_key.get_code().unwrap();
18/// ```
19use serde::{Deserialize, Serialize};
20
21mod error;
22mod hmac_type;
23mod hotp;
24mod totp;
25mod uri;
26
27pub use error::Error;
28pub use hmac_type::HMACType;
29pub use hotp::HOTPKey;
30pub use totp::TOTPKey;
31pub use uri::URI;
32
33#[cfg(feature = "steam")]
34pub mod steam;
35#[cfg(feature = "steam")]
36pub use steam::SteamKey;
37
38#[cfg(test)]
39mod test;
40
41/// KeyType is the type of the key
42/// HOTP is the counter based key
43/// TOTP is the time based key
44/// STEAM is the steam guard key (TODO not implemented yet)
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
46pub enum KeyType {
47 HOTP,
48 #[default]
49 TOTP,
50 #[cfg(feature = "steam")]
51 Steam,
52}
53
54impl std::fmt::Display for KeyType {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 match self {
57 KeyType::HOTP => write!(f, "hotp"),
58 KeyType::TOTP => write!(f, "totp"),
59 #[cfg(feature = "steam")]
60 KeyType::Steam => write!(f, "steam"),
61 }
62 }
63}
64
65impl From<&str> for KeyType {
66 fn from(s: &str) -> Self {
67 match s.to_ascii_lowercase().as_str() {
68 "hotp" => KeyType::HOTP,
69 "totp" => KeyType::TOTP,
70 _ => KeyType::default(),
71 }
72 }
73}
74
75impl From<String> for KeyType {
76 fn from(s: String) -> Self {
77 KeyType::from(s.as_str())
78 }
79}
80
81/// Key is the interface for the keys
82///
83/// usage:
84/// ```rust
85/// use libr2fa::HOTPKey;
86/// use libr2fa::HMACType;
87/// use libr2fa::Key;
88///
89/// let mut hotp_key = HOTPKey {
90/// key: "MFSWS5LGNBUXKZLBO5TGQ33JO5SWC2DGNF2WCZLIMZUXKZLXMFUGM2LVNFQWK53IMZUXK2A=".to_string(),
91/// hmac_type: HMACType::SHA1,
92/// ..Default::default()
93/// };
94///
95/// let code = hotp_key.get_code().unwrap();
96/// ```
97pub trait Key {
98 /// use to downcast to original type
99 ///
100 /// ```rust
101 /// use libr2fa::HOTPKey;
102 /// use libr2fa::HMACType;
103 /// use libr2fa::Key;
104 ///
105 /// let hotp_key = HOTPKey {
106 /// key: "MFSWS5LGNBUXKZLBO5TGQ33JO5SWC2DGNF2WCZLIMZUXKZLXMFUGM2LVNFQWK53IMZUXK2A=".to_string(),
107 /// hmac_type: HMACType::SHA1,
108 /// ..Default::default()
109 /// };
110 ///
111 /// let key: Box<dyn Key> = Box::new(hotp_key);
112 ///
113 /// let hotp_key = key.as_any().downcast_ref::<HOTPKey>();
114 ///
115 /// assert!(hotp_key.is_some());
116 /// ```
117 fn as_any(&self) -> &dyn Any;
118
119 /// get_code returns the code for the key
120 ///
121 /// if it is HTOP key, it will increment the counter
122 fn get_code(&mut self) -> Result<String, error::Error>;
123
124 /// get the name of the key
125 ///
126 /// ```rust
127 /// use libr2fa::HOTPKey;
128 /// use libr2fa::HMACType;
129 /// use libr2fa::Key;
130 ///
131 /// let mut hotp_key = HOTPKey {
132 /// key: "MFSWS5LGNBUXKZLBO5TGQ33JO5SWC2DGNF2WCZLIMZUXKZLXMFUGM2LVNFQWK53IMZUXK2A=".to_string(),
133 /// hmac_type: HMACType::SHA1,
134 /// ..Default::default()
135 /// };
136 ///
137 /// hotp_key.set_name("test");
138 ///
139 /// assert_eq!(hotp_key.get_name(), "test")
140 /// ```
141 fn get_name(&self) -> &str;
142
143 /// get the recovery codes
144 ///
145 /// ```rust
146 /// use libr2fa::HOTPKey;
147 /// use libr2fa::HMACType;
148 /// use libr2fa::Key;
149 ///
150 /// let mut hotp_key = HOTPKey {
151 /// key: "MFSWS5LGNBUXKZLBO5TGQ33JO5SWC2DGNF2WCZLIMZUXKZLXMFUGM2LVNFQWK53IMZUXK2A=".to_string(),
152 /// hmac_type: HMACType::SHA1,
153 /// ..Default::default()
154 /// };
155 ///
156 /// hotp_key.set_recovery_codes(vec!["test".to_string()]);
157 ///
158 /// assert_eq!(hotp_key.get_recovery_codes(), &["test".to_string()])
159 ///
160 /// ```
161 fn get_recovery_codes(&self) -> Vec<String>;
162
163 /// get the type of the key
164 fn get_type(&self) -> KeyType;
165
166 /// set the name of the key
167 ///
168 /// ```rust
169 /// use libr2fa::HOTPKey;
170 /// use libr2fa::HMACType;
171 /// use libr2fa::Key;
172 ///
173 /// let mut hotp_key = HOTPKey {
174 /// name: "".to_string(),
175 /// key: "MFSWS5LGNBUXKZLBO5TGQ33JO5SWC2DGNF2WCZLIMZUXKZLXMFUGM2LVNFQWK53IMZUXK2A=".to_string(),
176 /// hmac_type: HMACType::SHA1,
177 /// ..Default::default()
178 /// };
179 ///
180 /// hotp_key.set_name("test");
181 ///
182 /// assert_eq!(hotp_key.get_name(), "test")
183 /// ```
184 fn set_name(&mut self, name: &str);
185
186 /// set the recovery codes
187 ///
188 /// ```rust
189 /// use libr2fa::HOTPKey;
190 /// use libr2fa::HMACType;
191 /// use libr2fa::Key;
192 ///
193 /// let mut hotp_key = HOTPKey {
194 /// key: "MFSWS5LGNBUXKZLBO5TGQ33JO5SWC2DGNF2WCZLIMZUXKZLXMFUGM2LVNFQWK53IMZUXK2A=".to_string(),
195 /// hmac_type: HMACType::SHA1,
196 /// ..Default::default()
197 /// };
198 ///
199 /// hotp_key.set_recovery_codes(vec!["test".to_string()]);
200 ///
201 /// assert_eq!(hotp_key.get_recovery_codes(), &["test".to_string()])
202 ///
203 /// ```
204 fn set_recovery_codes(&mut self, recovery_codes: Vec<String>);
205}
206
207/// create a new key from the uri string
208///
209/// ```rust
210/// use libr2fa::otpauth_from_uri;
211/// use libr2fa::TOTPKey;
212/// use libr2fa::HMACType;
213/// use libr2fa::Key;
214///
215/// let totp_key1 = otpauth_from_uri("otpauth://totp/ACME%20Co:john.doe@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA256&digits=7&period=60");
216/// if let Err(err) = totp_key1 {
217/// panic!("{}", err);
218/// }
219/// let mut totp_key1 = totp_key1.unwrap();
220///
221/// let mut totp_key2 = TOTPKey {
222/// name: "ACME Co:john.doe@email.com".to_string(),
223/// key: "HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ".to_string(),
224/// digits: 7,
225/// time_step: 60,
226/// hmac_type: HMACType::SHA256,
227/// issuer: Some("ACME Co".to_string()),
228/// ..Default::default()
229/// };
230///
231/// assert_eq!(totp_key1.get_name(), totp_key2.get_name());
232/// assert_eq!(totp_key1.get_type(), totp_key2.get_type());
233/// assert_eq!(totp_key1.get_code(), totp_key2.get_code());
234/// ```
235pub fn otpauth_from_uri(uri: &str) -> Result<Box<dyn Key>, Error> {
236 let uri_struct = URI::from(uri);
237
238 match uri_struct.key_type {
239 KeyType::HOTP => HOTPKey::from_uri_struct(&uri_struct),
240 KeyType::TOTP => TOTPKey::from_uri_struct(&uri_struct),
241 #[cfg(feature = "steam")]
242 KeyType::Steam => steam::SteamKey::from_uri_struct(&uri_struct),
243 }
244}
245
246/// create a new key from the uri qrcode
247///
248/// ```rust
249/// use libr2fa::otpauth_from_uri_qrcode;
250/// use libr2fa::TOTPKey;
251/// use libr2fa::HMACType;
252/// use libr2fa::Key;
253///
254/// let totp_key1 = otpauth_from_uri_qrcode("public/uri_qrcode_test.png");
255/// if let Err(err) = totp_key1 {
256/// panic!("{}", err);
257/// }
258/// let mut totp_key1 = totp_key1.unwrap();
259///
260/// let mut totp_key2 = TOTPKey {
261/// name: "ACME Co:john.doe@email.com".to_string(),
262/// issuer: Some("ACME Co".to_string()),
263/// key: "HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ".to_string(),
264/// digits: 7,
265/// time_step: 60,
266/// hmac_type: HMACType::SHA256,
267/// ..Default::default()
268/// };
269///
270/// assert_eq!(totp_key1.get_name(), totp_key2.get_name());
271/// assert_eq!(totp_key1.get_type(), totp_key2.get_type());
272/// assert_eq!(totp_key1.get_code(), totp_key2.get_code());
273/// ```
274#[cfg(feature = "qrcoderead")]
275pub fn otpauth_from_uri_qrcode(path: &str) -> Result<Box<dyn Key>, Error> {
276 let uri_struct = URI::from_qr_code(path)?;
277
278 match uri_struct.key_type {
279 KeyType::HOTP => HOTPKey::from_uri_struct(&uri_struct),
280 KeyType::TOTP => TOTPKey::from_uri_struct(&uri_struct),
281 #[cfg(feature = "steam")]
282 KeyType::Steam => steam::SteamKey::from_uri_struct(&uri_struct),
283 }
284}
285
286pub trait OtpAuthKey {
287 /// to uri struct
288 fn to_uri_struct(&self) -> URI;
289
290 /// get the uri for the key
291 fn get_uri(&self) -> String {
292 self.to_uri_struct().to_string()
293 }
294
295 /// get issuer
296 fn get_issuer(&self) -> Option<&str>;
297
298 /// create the key from the uri struct
299 fn from_uri_struct(uri: &URI) -> Result<Box<dyn Key>, Error>;
300}