yubi_opt/lib.rs
1//! # YubiOTP
2//!
3//! This crate allows you to verify Yubico OTPs.
4//! To get details about what a Yubico OTP is, you can read about it here: <https://developers.yubico.com/OTP/>
5//!
6//! In order to use this crate you should first check the OTP of the user with the `is_valid_otp` function.
7//! If the OTP has the correct format, you can send the OTP to Yubico for verification. To do that, you
8//! will have to create a `YubicoClient` with your credentials.
9//! You can then call the `verify` function on it to send the request.
10//! Check if the result has the state `Ok`, if it's any other state the OTP is invalid!
11//!
12//! In case the state was `Ok` you will probably also want to check the public id of the key to compare it against the one you have on record.
13//! To get the public id of any OTP you can call the `get_public_id` function.
14
15mod response_state;
16pub mod yubico_client;
17
18const OTP_MIN_LENGTH: usize = 32;
19const OTP_MAX_LENGTH: usize = 48;
20
21/// Validates if a give OTP if in a valid format.
22///
23/// # Arguments
24///
25/// * `otp`: The OTP which format should be checked
26///
27/// returns: bool
28///
29/// # Examples
30///
31/// ```
32/// assert!(yubi_opt::is_valid_otp("cccjgjgkhcbbcvchfkfhiiuunbtnvgihdfiktncvlhck"));
33/// ```
34pub fn is_valid_otp(otp: &str) -> bool {
35 if (otp.len() < OTP_MIN_LENGTH) || (otp.len() > OTP_MAX_LENGTH) {
36 return false;
37 }
38
39 return otp
40 .chars()
41 .map(|c| c as u32)
42 .all(|c| (0x20..=0x7E).contains(&c));
43}
44
45/// Will return the public id of an OTP.
46///
47/// # Arguments
48///
49/// * `otp`: OTP from which the public id should be extracted
50///
51/// returns: Result<&str, &str>
52///
53/// # Examples
54///
55/// ```
56/// let public_id = yubi_opt::get_public_id("cccjgjgkhcbbcvchfkfhiiuunbtnvgihdfiktncvlhck").unwrap();
57/// assert_eq!(public_id, "cccjgjgkhcbb")
58/// ```
59pub fn get_public_id(otp: &str) -> Result<&str, &str> {
60 if !is_valid_otp(otp) {
61 return Err("OTP is invalid");
62 }
63
64 //Last 32 chars will always be the unique passcode
65 Ok(otp.split_at(otp.len() - 32).0)
66}
67
68#[cfg(test)]
69mod tests {
70 use crate::response_state::State;
71 use crate::yubico_client::YubicoClient;
72
73 use super::*;
74
75 #[test]
76 fn test_opt_validator() {
77 let valid_otp = is_valid_otp("cccjgjgkhcbbcvchfkfhiiuunbtnvgihdfiktncvlhck");
78 let another_valid_otp = is_valid_otp("cccjgjgkhcbbgefdkbbditfjrlniggevfhenublfnrev");
79 let invalid_otp_char = is_valid_otp("cccjgjgkhcbbcvchfkツhiiuunbtnvgihdfiktncvlhck");
80 let invalid_otp_length = is_valid_otp("gefdkbbditfjrlniggevfh");
81
82 assert!(valid_otp);
83 assert!(another_valid_otp);
84 assert_eq!(invalid_otp_char, false);
85 assert_eq!(invalid_otp_length, false);
86 }
87
88 #[test]
89 fn test_get_public_id() {
90 let valid_otp = get_public_id("cccjgjgkhcbbcvchfkfhiiuunbtnvgihdfiktncvlhck");
91 let another_valid_otp = get_public_id("cccjgjgkhcbbgefdkbbditfjrlniggevfhenublfnrev");
92 let invalid_otp = get_public_id("gefdkbbditfjrlniggevfh");
93
94 assert_eq!(valid_otp.ok().unwrap(), "cccjgjgkhcbb");
95 assert_eq!(another_valid_otp.ok().unwrap(), "cccjgjgkhcbb");
96 assert!(invalid_otp.err().is_some());
97 }
98
99 //TODO: Make a more decent test for this, currently its using the client_id and api_key from the
100 // Test Vectors site: https://developers.yubico.com/OTP/Specifications/Test_vectors.html
101 // However the OTP will always return `ReplayedOtp` and not `Ok` which is unfortunate.
102 // Should be enough for the start tho to see if the http requests are successful.
103 #[test]
104 fn test_yubico_client() {
105 //Hit: Do NOT use the credentials here, these are example credentials from the documentation!
106 //Instead get your own api_key and client_id from here: https://upgrade.yubico.com/getapikey/
107 let client = YubicoClient::new(1, None);
108
109 let x = client
110 .verify("vvungrrdhvtklknvrtvuvbbkeidikkvgglrvdgrfcdft")
111 .unwrap();
112
113 assert_eq!(x.state, State::ReplayedOtp)
114 }
115}