commit 7d2bc9e162a92202ee8d9ff60960c7d4fb3739f2
parent c6c00729e39566bb978538729888f58c6638c5f0
Author: Daniel GarcĂa <dani-garcia@users.noreply.github.com>
Date: Sun, 3 Mar 2019 16:09:15 +0100
Added option to force 2fa at logins and made some changes to two factor code.
Added newlines to config options to keep them a reasonable length.
Diffstat:
4 files changed, 77 insertions(+), 78 deletions(-)
diff --git a/src/api/core/two_factor.rs b/src/api/core/two_factor.rs
@@ -102,6 +102,14 @@ fn recover(data: JsonUpcase<RecoverTwoFactor>, conn: DbConn) -> JsonResult {
Ok(Json(json!({})))
}
+fn _generate_recover_code(user: &mut User, conn: &DbConn) {
+ if user.totp_recover.is_none() {
+ let totp_recover = BASE32.encode(&crypto::get_random(vec![0u8; 20]));
+ user.totp_recover = Some(totp_recover);
+ user.save(conn).ok();
+ }
+}
+
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct DisableTwoFactorData {
@@ -196,9 +204,7 @@ fn activate_authenticator(data: JsonUpcase<EnableAuthenticatorData>, headers: He
let twofactor = TwoFactor::new(user.uuid.clone(), type_, key.to_uppercase());
// Validate the token provided with the key
- if !twofactor.check_totp_code(token) {
- err!("Invalid totp code")
- }
+ validate_totp_code(token, &twofactor.data)?;
_generate_recover_code(&mut user, &conn);
twofactor.save(&conn)?;
@@ -215,12 +221,30 @@ fn activate_authenticator_put(data: JsonUpcase<EnableAuthenticatorData>, headers
activate_authenticator(data, headers, conn)
}
-fn _generate_recover_code(user: &mut User, conn: &DbConn) {
- if user.totp_recover.is_none() {
- let totp_recover = BASE32.encode(&crypto::get_random(vec![0u8; 20]));
- user.totp_recover = Some(totp_recover);
- user.save(conn).ok();
+pub fn validate_totp_code_str(totp_code: &str, secret: &str) -> EmptyResult {
+ let totp_code: u64 = match totp_code.parse() {
+ Ok(code) => code,
+ _ => err!("TOTP code is not a number"),
+ };
+
+ validate_totp_code(totp_code, secret)
+}
+
+pub fn validate_totp_code(totp_code: u64, secret: &str) -> EmptyResult {
+ use data_encoding::BASE32;
+ use oath::{totp_raw_now, HashType};
+
+ let decoded_secret = match BASE32.decode(secret.as_bytes()) {
+ Ok(s) => s,
+ Err(_) => err!("Invalid TOTP secret"),
+ };
+
+ let generated = totp_raw_now(&decoded_secret, 6, 0, 30, &HashType::SHA1);
+ if generated != totp_code {
+ err!("Invalid TOTP code");
}
+
+ Ok(())
}
use u2f::messages::{RegisterResponse, SignResponse, U2fSignRequest};
@@ -671,20 +695,12 @@ fn activate_yubikey_put(data: JsonUpcase<EnableYubikeyData>, headers: Headers, c
activate_yubikey(data, headers, conn)
}
-pub fn validate_yubikey_login(user_uuid: &str, response: &str, conn: &DbConn) -> EmptyResult {
+pub fn validate_yubikey_login(response: &str, twofactor_data: &str) -> EmptyResult {
if response.len() != 44 {
err!("Invalid Yubikey OTP length");
}
- let yubikey_type = TwoFactorType::YubiKey as i32;
-
- let twofactor = match TwoFactor::find_by_user_and_type(user_uuid, yubikey_type, &conn) {
- Some(tf) => tf,
- None => err!("No YubiKey devices registered"),
- };
-
- let yubikey_metadata: YubikeyMetadata =
- serde_json::from_str(&twofactor.data).expect("Can't parse Yubikey Metadata");
+ let yubikey_metadata: YubikeyMetadata = serde_json::from_str(twofactor_data).expect("Can't parse Yubikey Metadata");
let response_id = &response[..12];
if !yubikey_metadata.Keys.contains(&response_id.to_owned()) {
diff --git a/src/api/identity.rs b/src/api/identity.rs
@@ -152,63 +152,45 @@ fn twofactor_auth(
conn: &DbConn,
) -> ApiResult<Option<String>> {
let twofactors = TwoFactor::find_by_user(user_uuid, conn);
- let providers: Vec<_> = twofactors.iter().map(|tf| tf.type_).collect();
// No twofactor token if twofactor is disabled
if twofactors.is_empty() {
return Ok(None);
}
- let provider = data.two_factor_provider.unwrap_or(providers[0]); // If we aren't given a two factor provider, asume the first one
+ let twofactor_ids: Vec<_> = twofactors.iter().map(|tf| tf.type_).collect();
+ let selected_id = data.two_factor_provider.unwrap_or(twofactor_ids[0]); // If we aren't given a two factor provider, asume the first one
let twofactor_code = match data.two_factor_token {
Some(ref code) => code,
- None => err_json!(_json_err_twofactor(&providers, user_uuid, conn)?),
+ None => err_json!(_json_err_twofactor(&twofactor_ids, user_uuid, conn)?),
};
- let twofactor = twofactors.iter().filter(|tf| tf.type_ == provider).nth(0);
+ let selected_twofactor = twofactors.into_iter().filter(|tf| tf.type_ == selected_id).nth(0);
- match TwoFactorType::from_i32(provider) {
- Some(TwoFactorType::Remember) => {
- use crate::crypto::ct_eq;
- match device.twofactor_remember {
- Some(ref remember) if ct_eq(remember, twofactor_code) => return Ok(None), // No twofactor token needed here
- _ => err_json!(_json_err_twofactor(&providers, user_uuid, conn)?),
- }
- }
+ use crate::api::core::two_factor as _tf;
+ use crate::crypto::ct_eq;
- Some(TwoFactorType::Authenticator) => {
- let twofactor = match twofactor {
- Some(tf) => tf,
- None => err!("TOTP not enabled"),
- };
+ let selected_data = _selected_data(selected_twofactor);
+ let mut remember = data.two_factor_remember.unwrap_or(0);
- let totp_code: u64 = match twofactor_code.parse() {
- Ok(code) => code,
- _ => err!("Invalid TOTP code"),
- };
+ match TwoFactorType::from_i32(selected_id) {
+ Some(TwoFactorType::Authenticator) => _tf::validate_totp_code_str(twofactor_code, &selected_data?)?,
+ Some(TwoFactorType::U2f) => _tf::validate_u2f_login(user_uuid, twofactor_code, conn)?,
+ Some(TwoFactorType::YubiKey) => _tf::validate_yubikey_login(twofactor_code, &selected_data?)?,
- if !twofactor.check_totp_code(totp_code) {
- err_json!(_json_err_twofactor(&providers, user_uuid, conn)?)
+ Some(TwoFactorType::Remember) => {
+ match device.twofactor_remember {
+ Some(ref code) if !CONFIG.disable_2fa_remember() && ct_eq(code, twofactor_code) => {
+ remember = 1; // Make sure we also return the token here, otherwise it will only remember the first time
+ }
+ _ => err_json!(_json_err_twofactor(&twofactor_ids, user_uuid, conn)?),
}
}
-
- Some(TwoFactorType::U2f) => {
- use crate::api::core::two_factor;
-
- two_factor::validate_u2f_login(user_uuid, &twofactor_code, conn)?;
- }
-
- Some(TwoFactorType::YubiKey) => {
- use crate::api::core::two_factor;
-
- two_factor::validate_yubikey_login(user_uuid, twofactor_code, conn)?;
- }
-
_ => err!("Invalid two factor provider"),
}
- if data.two_factor_remember.unwrap_or(0) == 1 {
+ if !CONFIG.disable_2fa_remember() && remember == 1 {
Ok(Some(device.refresh_twofactor_remember()))
} else {
device.delete_twofactor_remember();
@@ -216,6 +198,13 @@ fn twofactor_auth(
}
}
+fn _selected_data(tf: Option<TwoFactor>) -> ApiResult<String> {
+ match tf {
+ Some(tf) => Ok(tf.data),
+ None => err!("Two factor doesn't exist"),
+ }
+}
+
fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> ApiResult<Value> {
use crate::api::core::two_factor;
diff --git a/src/config.rs b/src/config.rs
@@ -224,24 +224,27 @@ make_config! {
/// General settings
settings {
- /// Domain URL |> This needs to be set to the URL used to access the server, including 'http[s]://' and port, if it's different than the default. Some server functions don't work correctly without this value
+ /// Domain URL |> This needs to be set to the URL used to access the server, including 'http[s]://'
+ /// and port, if it's different than the default. Some server functions don't work correctly without this value
domain: String, true, def, "http://localhost".to_string();
/// Domain Set |> Indicates if the domain is set by the admin. Otherwise the default will be used.
domain_set: bool, false, def, false;
/// Enable web vault
web_vault_enabled: bool, false, def, true;
- /// Disable icon downloads |> Set to true to disable icon downloading, this would still serve icons from $ICON_CACHE_FOLDER,
- /// but it won't produce any external network request. Needs to set $ICON_CACHE_TTL to 0,
+ /// Disable icon downloads |> Set to true to disable icon downloading, this would still serve icons from
+ /// $ICON_CACHE_FOLDER, but it won't produce any external network request. Needs to set $ICON_CACHE_TTL to 0,
/// otherwise it will delete them and they won't be downloaded again.
disable_icon_download: bool, true, def, false;
/// Allow new signups |> Controls if new users can register. Note that while this is disabled, users could still be invited
signups_allowed: bool, true, def, true;
/// Allow invitations |> Controls whether users can be invited by organization admins, even when signups are disabled
invitations_allowed: bool, true, def, true;
- /// Password iterations |> Number of server-side passwords hashing iterations. The changes only apply when a user changes their password. Not recommended to lower the value
+ /// Password iterations |> Number of server-side passwords hashing iterations.
+ /// The changes only apply when a user changes their password. Not recommended to lower the value
password_iterations: i32, true, def, 100_000;
- /// Show password hints |> Controls if the password hint should be shown directly in the web page. Otherwise, if email is disabled, there is no way to see the password hint
+ /// Show password hints |> Controls if the password hint should be shown directly in the web page.
+ /// Otherwise, if email is disabled, there is no way to see the password hint
show_password_hint: bool, true, def, true;
/// Admin page token |> The token used to authenticate in this very same page. Changing it here won't deauthorize the current session
@@ -255,9 +258,14 @@ make_config! {
/// Negative icon cache expiry |> Number of seconds before trying to download an icon that failed again.
icon_cache_negttl: u64, true, def, 259_200;
/// Icon download timeout |> Number of seconds when to stop attempting to download an icon.
- icon_download_timeout: u64, true, def, 10;
+ icon_download_timeout: u64, true, def, 10;
- /// Reload templates (Dev) |> When this is set to true, the templates get reloaded with every request. ONLY use this during development, as it can slow down the server
+ /// Disable Two-Factor remember |> Enabling this would force the users to use a second factor to login every time.
+ /// Note that the checkbox would still be present, but ignored.
+ disable_2fa_remember: bool, true, def, false;
+
+ /// Reload templates (Dev) |> When this is set to true, the templates get reloaded with every request.
+ /// ONLY use this during development, as it can slow down the server
reload_templates: bool, true, def, false;
/// Log routes at launch (Dev)
@@ -267,7 +275,8 @@ make_config! {
/// Log file path
log_file: String, false, option;
- /// Enable DB WAL |> Turning this off might lead to worse performance, but might help if using bitwarden_rs on some exotic filesystems, that do not support WAL. Please make sure you read project wiki on the topic before changing this setting.
+ /// Enable DB WAL |> Turning this off might lead to worse performance, but might help if using bitwarden_rs on some exotic filesystems,
+ /// that do not support WAL. Please make sure you read project wiki on the topic before changing this setting.
enable_db_wal: bool, false, def, true;
/// Disable Admin Token (Know the risks!) |> Disables the Admin Token for the admin page so you may use your own auth in-front
diff --git a/src/db/models/two_factor.rs b/src/db/models/two_factor.rs
@@ -42,21 +42,6 @@ impl TwoFactor {
}
}
- pub fn check_totp_code(&self, totp_code: u64) -> bool {
- let totp_secret = self.data.as_bytes();
-
- use data_encoding::BASE32;
- use oath::{totp_raw_now, HashType};
-
- let decoded_secret = match BASE32.decode(totp_secret) {
- Ok(s) => s,
- Err(_) => return false,
- };
-
- let generated = totp_raw_now(&decoded_secret, 6, 0, 30, &HashType::SHA1);
- generated == totp_code
- }
-
pub fn to_json(&self) -> Value {
json!({
"Enabled": self.enabled,