duo.rs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. use chrono::Utc;
  2. use data_encoding::BASE64;
  3. use rocket::Route;
  4. use rocket_contrib::json::Json;
  5. use serde_json;
  6. use crate::api::{ApiResult, EmptyResult, JsonResult, JsonUpcase, PasswordData};
  7. use crate::auth::Headers;
  8. use crate::crypto;
  9. use crate::db::{
  10. models::{TwoFactor, TwoFactorType, User},
  11. DbConn,
  12. };
  13. use crate::error::MapResult;
  14. use crate::CONFIG;
  15. pub fn routes() -> Vec<Route> {
  16. routes![
  17. get_duo,
  18. activate_duo,
  19. activate_duo_put,
  20. ]
  21. }
  22. #[derive(Serialize, Deserialize)]
  23. struct DuoData {
  24. host: String,
  25. ik: String,
  26. sk: String,
  27. }
  28. impl DuoData {
  29. fn global() -> Option<Self> {
  30. match CONFIG.duo_host() {
  31. Some(host) => Some(Self {
  32. host,
  33. ik: CONFIG.duo_ikey().unwrap(),
  34. sk: CONFIG.duo_skey().unwrap(),
  35. }),
  36. None => None,
  37. }
  38. }
  39. fn msg(s: &str) -> Self {
  40. Self {
  41. host: s.into(),
  42. ik: s.into(),
  43. sk: s.into(),
  44. }
  45. }
  46. fn secret() -> Self {
  47. Self::msg("<global_secret>")
  48. }
  49. fn obscure(self) -> Self {
  50. let mut host = self.host;
  51. let mut ik = self.ik;
  52. let mut sk = self.sk;
  53. let digits = 4;
  54. let replaced = "************";
  55. host.replace_range(digits.., replaced);
  56. ik.replace_range(digits.., replaced);
  57. sk.replace_range(digits.., replaced);
  58. Self { host, ik, sk }
  59. }
  60. }
  61. enum DuoStatus {
  62. Global(DuoData),
  63. // Using the global duo config
  64. User(DuoData),
  65. // Using the user's config
  66. Disabled(bool), // True if there is a global setting
  67. }
  68. impl DuoStatus {
  69. fn data(self) -> Option<DuoData> {
  70. match self {
  71. DuoStatus::Global(data) => Some(data),
  72. DuoStatus::User(data) => Some(data),
  73. DuoStatus::Disabled(_) => None,
  74. }
  75. }
  76. }
  77. const DISABLED_MESSAGE_DEFAULT: &str = "<To use the global Duo keys, please leave these fields untouched>";
  78. #[post("/two-factor/get-duo", data = "<data>")]
  79. fn get_duo(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult {
  80. let data: PasswordData = data.into_inner().data;
  81. if !headers.user.check_valid_password(&data.MasterPasswordHash) {
  82. err!("Invalid password");
  83. }
  84. let data = get_user_duo_data(&headers.user.uuid, &conn);
  85. let (enabled, data) = match data {
  86. DuoStatus::Global(_) => (true, Some(DuoData::secret())),
  87. DuoStatus::User(data) => (true, Some(data.obscure())),
  88. DuoStatus::Disabled(true) => (false, Some(DuoData::msg(DISABLED_MESSAGE_DEFAULT))),
  89. DuoStatus::Disabled(false) => (false, None),
  90. };
  91. let json = if let Some(data) = data {
  92. json!({
  93. "Enabled": enabled,
  94. "Host": data.host,
  95. "SecretKey": data.sk,
  96. "IntegrationKey": data.ik,
  97. "Object": "twoFactorDuo"
  98. })
  99. } else {
  100. json!({
  101. "Enabled": enabled,
  102. "Object": "twoFactorDuo"
  103. })
  104. };
  105. Ok(Json(json))
  106. }
  107. #[derive(Deserialize)]
  108. #[allow(non_snake_case, dead_code)]
  109. struct EnableDuoData {
  110. MasterPasswordHash: String,
  111. Host: String,
  112. SecretKey: String,
  113. IntegrationKey: String,
  114. }
  115. impl From<EnableDuoData> for DuoData {
  116. fn from(d: EnableDuoData) -> Self {
  117. Self {
  118. host: d.Host,
  119. ik: d.IntegrationKey,
  120. sk: d.SecretKey,
  121. }
  122. }
  123. }
  124. fn check_duo_fields_custom(data: &EnableDuoData) -> bool {
  125. fn empty_or_default(s: &str) -> bool {
  126. let st = s.trim();
  127. st.is_empty() || s == DISABLED_MESSAGE_DEFAULT
  128. }
  129. !empty_or_default(&data.Host) && !empty_or_default(&data.SecretKey) && !empty_or_default(&data.IntegrationKey)
  130. }
  131. #[post("/two-factor/duo", data = "<data>")]
  132. fn activate_duo(data: JsonUpcase<EnableDuoData>, headers: Headers, conn: DbConn) -> JsonResult {
  133. let data: EnableDuoData = data.into_inner().data;
  134. if !headers.user.check_valid_password(&data.MasterPasswordHash) {
  135. err!("Invalid password");
  136. }
  137. let (data, data_str) = if check_duo_fields_custom(&data) {
  138. let data_req: DuoData = data.into();
  139. let data_str = serde_json::to_string(&data_req)?;
  140. duo_api_request("GET", "/auth/v2/check", "", &data_req).map_res("Failed to validate Duo credentials")?;
  141. (data_req.obscure(), data_str)
  142. } else {
  143. (DuoData::secret(), String::new())
  144. };
  145. let type_ = TwoFactorType::Duo;
  146. let twofactor = TwoFactor::new(headers.user.uuid.clone(), type_, data_str);
  147. twofactor.save(&conn)?;
  148. Ok(Json(json!({
  149. "Enabled": true,
  150. "Host": data.host,
  151. "SecretKey": data.sk,
  152. "IntegrationKey": data.ik,
  153. "Object": "twoFactorDuo"
  154. })))
  155. }
  156. #[put("/two-factor/duo", data = "<data>")]
  157. fn activate_duo_put(data: JsonUpcase<EnableDuoData>, headers: Headers, conn: DbConn) -> JsonResult {
  158. activate_duo(data, headers, conn)
  159. }
  160. fn duo_api_request(method: &str, path: &str, params: &str, data: &DuoData) -> EmptyResult {
  161. const AGENT: &str = "bitwarden_rs:Duo/1.0 (Rust)";
  162. use reqwest::{header::*, Client, Method};
  163. use std::str::FromStr;
  164. let url = format!("https://{}{}", &data.host, path);
  165. let date = Utc::now().to_rfc2822();
  166. let username = &data.ik;
  167. let fields = [&date, method, &data.host, path, params];
  168. let password = crypto::hmac_sign(&data.sk, &fields.join("\n"));
  169. let m = Method::from_str(method).unwrap_or_default();
  170. Client::new()
  171. .request(m, &url)
  172. .basic_auth(username, Some(password))
  173. .header(USER_AGENT, AGENT)
  174. .header(DATE, date)
  175. .send()?
  176. .error_for_status()?;
  177. Ok(())
  178. }
  179. const DUO_EXPIRE: i64 = 300;
  180. const APP_EXPIRE: i64 = 3600;
  181. const AUTH_PREFIX: &str = "AUTH";
  182. const DUO_PREFIX: &str = "TX";
  183. const APP_PREFIX: &str = "APP";
  184. fn get_user_duo_data(uuid: &str, conn: &DbConn) -> DuoStatus {
  185. let type_ = TwoFactorType::Duo as i32;
  186. // If the user doesn't have an entry, disabled
  187. let twofactor = match TwoFactor::find_by_user_and_type(uuid, type_, &conn) {
  188. Some(t) => t,
  189. None => return DuoStatus::Disabled(DuoData::global().is_some()),
  190. };
  191. // If the user has the required values, we use those
  192. if let Ok(data) = serde_json::from_str(&twofactor.data) {
  193. return DuoStatus::User(data);
  194. }
  195. // Otherwise, we try to use the globals
  196. if let Some(global) = DuoData::global() {
  197. return DuoStatus::Global(global);
  198. }
  199. // If there are no globals configured, just disable it
  200. DuoStatus::Disabled(false)
  201. }
  202. // let (ik, sk, ak, host) = get_duo_keys();
  203. fn get_duo_keys_email(email: &str, conn: &DbConn) -> ApiResult<(String, String, String, String)> {
  204. let data = User::find_by_mail(email, &conn)
  205. .and_then(|u| get_user_duo_data(&u.uuid, &conn).data())
  206. .or_else(DuoData::global)
  207. .map_res("Can't fetch Duo keys")?;
  208. Ok((data.ik, data.sk, CONFIG.get_duo_akey(), data.host))
  209. }
  210. pub fn generate_duo_signature(email: &str, conn: &DbConn) -> ApiResult<(String, String)> {
  211. let now = Utc::now().timestamp();
  212. let (ik, sk, ak, host) = get_duo_keys_email(email, conn)?;
  213. let duo_sign = sign_duo_values(&sk, email, &ik, DUO_PREFIX, now + DUO_EXPIRE);
  214. let app_sign = sign_duo_values(&ak, email, &ik, APP_PREFIX, now + APP_EXPIRE);
  215. Ok((format!("{}:{}", duo_sign, app_sign), host))
  216. }
  217. fn sign_duo_values(key: &str, email: &str, ikey: &str, prefix: &str, expire: i64) -> String {
  218. let val = format!("{}|{}|{}", email, ikey, expire);
  219. let cookie = format!("{}|{}", prefix, BASE64.encode(val.as_bytes()));
  220. format!("{}|{}", cookie, crypto::hmac_sign(key, &cookie))
  221. }
  222. pub fn validate_duo_login(email: &str, response: &str, conn: &DbConn) -> EmptyResult {
  223. let split: Vec<&str> = response.split(':').collect();
  224. if split.len() != 2 {
  225. err!("Invalid response length");
  226. }
  227. let auth_sig = split[0];
  228. let app_sig = split[1];
  229. let now = Utc::now().timestamp();
  230. let (ik, sk, ak, _host) = get_duo_keys_email(email, conn)?;
  231. let auth_user = parse_duo_values(&sk, auth_sig, &ik, AUTH_PREFIX, now)?;
  232. let app_user = parse_duo_values(&ak, app_sig, &ik, APP_PREFIX, now)?;
  233. if !crypto::ct_eq(&auth_user, app_user) || !crypto::ct_eq(&auth_user, email) {
  234. err!("Error validating duo authentication")
  235. }
  236. Ok(())
  237. }
  238. fn parse_duo_values(key: &str, val: &str, ikey: &str, prefix: &str, time: i64) -> ApiResult<String> {
  239. let split: Vec<&str> = val.split('|').collect();
  240. if split.len() != 3 {
  241. err!("Invalid value length")
  242. }
  243. let u_prefix = split[0];
  244. let u_b64 = split[1];
  245. let u_sig = split[2];
  246. let sig = crypto::hmac_sign(key, &format!("{}|{}", u_prefix, u_b64));
  247. if !crypto::ct_eq(crypto::hmac_sign(key, &sig), crypto::hmac_sign(key, u_sig)) {
  248. err!("Duo signatures don't match")
  249. }
  250. if u_prefix != prefix {
  251. err!("Prefixes don't match")
  252. }
  253. let cookie_vec = match BASE64.decode(u_b64.as_bytes()) {
  254. Ok(c) => c,
  255. Err(_) => err!("Invalid Duo cookie encoding"),
  256. };
  257. let cookie = match String::from_utf8(cookie_vec) {
  258. Ok(c) => c,
  259. Err(_) => err!("Invalid Duo cookie encoding"),
  260. };
  261. let cookie_split: Vec<&str> = cookie.split('|').collect();
  262. if cookie_split.len() != 3 {
  263. err!("Invalid cookie length")
  264. }
  265. let username = cookie_split[0];
  266. let u_ikey = cookie_split[1];
  267. let expire = cookie_split[2];
  268. if !crypto::ct_eq(ikey, u_ikey) {
  269. err!("Invalid ikey")
  270. }
  271. let expire = match expire.parse() {
  272. Ok(e) => e,
  273. Err(_) => err!("Invalid expire time"),
  274. };
  275. if time >= expire {
  276. err!("Expired authorization")
  277. }
  278. Ok(username.into())
  279. }