1 use rocket::Route;
2 use rocket_contrib::json::Json;
3 use serde_json::Value;
4 use yubico::{config::Config, verify};
5
6 use crate::{
7 api::{core::two_factor::_generate_recover_code, EmptyResult, JsonResult, JsonUpcase, PasswordData},
8 auth::Headers,
9 db::{
10 models::{TwoFactor, TwoFactorType},
11 DbConn,
12 },
13 error::{Error, MapResult},
14 CONFIG,
15 };
16
routes() -> Vec<Route>17 pub fn routes() -> Vec<Route> {
18 routes![generate_yubikey, activate_yubikey, activate_yubikey_put,]
19 }
20
21 #[derive(Deserialize, Debug)]
22 #[allow(non_snake_case)]
23 struct EnableYubikeyData {
24 MasterPasswordHash: String,
25 Key1: Option<String>,
26 Key2: Option<String>,
27 Key3: Option<String>,
28 Key4: Option<String>,
29 Key5: Option<String>,
30 Nfc: bool,
31 }
32
33 #[derive(Deserialize, Serialize, Debug)]
34 #[allow(non_snake_case)]
35 pub struct YubikeyMetadata {
36 Keys: Vec<String>,
37 pub Nfc: bool,
38 }
39
parse_yubikeys(data: &EnableYubikeyData) -> Vec<String>40 fn parse_yubikeys(data: &EnableYubikeyData) -> Vec<String> {
41 let data_keys = [&data.Key1, &data.Key2, &data.Key3, &data.Key4, &data.Key5];
42
43 data_keys.iter().filter_map(|e| e.as_ref().cloned()).collect()
44 }
45
jsonify_yubikeys(yubikeys: Vec<String>) -> serde_json::Value46 fn jsonify_yubikeys(yubikeys: Vec<String>) -> serde_json::Value {
47 let mut result = json!({});
48
49 for (i, key) in yubikeys.into_iter().enumerate() {
50 result[format!("Key{}", i + 1)] = Value::String(key);
51 }
52
53 result
54 }
55
get_yubico_credentials() -> Result<(String, String), Error>56 fn get_yubico_credentials() -> Result<(String, String), Error> {
57 if !CONFIG._enable_yubico() {
58 err!("Yubico support is disabled");
59 }
60
61 match (CONFIG.yubico_client_id(), CONFIG.yubico_secret_key()) {
62 (Some(id), Some(secret)) => Ok((id, secret)),
63 _ => err!("`YUBICO_CLIENT_ID` or `YUBICO_SECRET_KEY` environment variable is not set. Yubikey OTP Disabled"),
64 }
65 }
66
verify_yubikey_otp(otp: String) -> EmptyResult67 fn verify_yubikey_otp(otp: String) -> EmptyResult {
68 let (yubico_id, yubico_secret) = get_yubico_credentials()?;
69
70 let config = Config::default().set_client_id(yubico_id).set_key(yubico_secret);
71
72 match CONFIG.yubico_server() {
73 Some(server) => verify(otp, config.set_api_hosts(vec![server])),
74 None => verify(otp, config),
75 }
76 .map_res("Failed to verify OTP")
77 .and(Ok(()))
78 }
79
80 #[post("/two-factor/get-yubikey", data = "<data>")]
generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult81 fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult {
82 // Make sure the credentials are set
83 get_yubico_credentials()?;
84
85 let data: PasswordData = data.into_inner().data;
86 let user = headers.user;
87
88 if !user.check_valid_password(&data.MasterPasswordHash) {
89 err!("Invalid password");
90 }
91
92 let user_uuid = &user.uuid;
93 let yubikey_type = TwoFactorType::YubiKey as i32;
94
95 let r = TwoFactor::find_by_user_and_type(user_uuid, yubikey_type, &conn);
96
97 if let Some(r) = r {
98 let yubikey_metadata: YubikeyMetadata = serde_json::from_str(&r.data)?;
99
100 let mut result = jsonify_yubikeys(yubikey_metadata.Keys);
101
102 result["Enabled"] = Value::Bool(true);
103 result["Nfc"] = Value::Bool(yubikey_metadata.Nfc);
104 result["Object"] = Value::String("twoFactorU2f".to_owned());
105
106 Ok(Json(result))
107 } else {
108 Ok(Json(json!({
109 "Enabled": false,
110 "Object": "twoFactorU2f",
111 })))
112 }
113 }
114
115 #[post("/two-factor/yubikey", data = "<data>")]
activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult116 fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult {
117 let data: EnableYubikeyData = data.into_inner().data;
118 let mut user = headers.user;
119
120 if !user.check_valid_password(&data.MasterPasswordHash) {
121 err!("Invalid password");
122 }
123
124 // Check if we already have some data
125 let mut yubikey_data = match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::YubiKey as i32, &conn) {
126 Some(data) => data,
127 None => TwoFactor::new(user.uuid.clone(), TwoFactorType::YubiKey, String::new()),
128 };
129
130 let yubikeys = parse_yubikeys(&data);
131
132 if yubikeys.is_empty() {
133 return Ok(Json(json!({
134 "Enabled": false,
135 "Object": "twoFactorU2f",
136 })));
137 }
138
139 // Ensure they are valid OTPs
140 for yubikey in &yubikeys {
141 if yubikey.len() == 12 {
142 // YubiKey ID
143 continue;
144 }
145
146 verify_yubikey_otp(yubikey.to_owned()).map_res("Invalid Yubikey OTP provided")?;
147 }
148
149 let yubikey_ids: Vec<String> = yubikeys.into_iter().map(|x| (&x[..12]).to_owned()).collect();
150
151 let yubikey_metadata = YubikeyMetadata {
152 Keys: yubikey_ids,
153 Nfc: data.Nfc,
154 };
155
156 yubikey_data.data = serde_json::to_string(&yubikey_metadata).unwrap();
157 yubikey_data.save(&conn)?;
158
159 _generate_recover_code(&mut user, &conn);
160
161 let mut result = jsonify_yubikeys(yubikey_metadata.Keys);
162
163 result["Enabled"] = Value::Bool(true);
164 result["Nfc"] = Value::Bool(yubikey_metadata.Nfc);
165 result["Object"] = Value::String("twoFactorU2f".to_owned());
166
167 Ok(Json(result))
168 }
169
170 #[put("/two-factor/yubikey", data = "<data>")]
activate_yubikey_put(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult171 fn activate_yubikey_put(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult {
172 activate_yubikey(data, headers, conn)
173 }
174
validate_yubikey_login(response: &str, twofactor_data: &str) -> EmptyResult175 pub fn validate_yubikey_login(response: &str, twofactor_data: &str) -> EmptyResult {
176 if response.len() != 44 {
177 err!("Invalid Yubikey OTP length");
178 }
179
180 let yubikey_metadata: YubikeyMetadata = serde_json::from_str(twofactor_data).expect("Can't parse Yubikey Metadata");
181 let response_id = &response[..12];
182
183 if !yubikey_metadata.Keys.contains(&response_id.to_owned()) {
184 err!("Given Yubikey is not registered");
185 }
186
187 let result = verify_yubikey_otp(response.to_owned());
188
189 match result {
190 Ok(_answer) => Ok(()),
191 Err(_e) => err!("Failed to verify Yubikey against OTP server"),
192 }
193 }
194