Updated authenticator TOTP
- Added security check for previouse used codes - Allow TOTP codes with 1 step back and forward when there is a time drift. This means in total 3 codes could be valid. But only newer codes then the previouse used codes are excepted after that.
This commit is contained in:
parent
45d9d8db94
commit
ebf40099f2
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE twofactor ADD COLUMN last_used INTEGER NOT NULL DEFAULT 0;
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE twofactor ADD COLUMN last_used INTEGER NOT NULL DEFAULT 0;
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE twofactor ADD COLUMN last_used INTEGER NOT NULL DEFAULT 0;
|
|
@ -77,7 +77,7 @@ fn activate_authenticator(data: JsonUpcase<EnableAuthenticatorData>, headers: He
|
||||||
let twofactor = TwoFactor::new(user.uuid.clone(), type_, key.to_uppercase());
|
let twofactor = TwoFactor::new(user.uuid.clone(), type_, key.to_uppercase());
|
||||||
|
|
||||||
// Validate the token provided with the key
|
// Validate the token provided with the key
|
||||||
validate_totp_code(token, &twofactor.data)?;
|
validate_totp_code(&user.uuid, token, &twofactor.data, &conn)?;
|
||||||
|
|
||||||
_generate_recover_code(&mut user, &conn);
|
_generate_recover_code(&mut user, &conn);
|
||||||
twofactor.save(&conn)?;
|
twofactor.save(&conn)?;
|
||||||
|
@ -94,27 +94,69 @@ fn activate_authenticator_put(data: JsonUpcase<EnableAuthenticatorData>, headers
|
||||||
activate_authenticator(data, headers, conn)
|
activate_authenticator(data, headers, conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_totp_code_str(totp_code: &str, secret: &str) -> EmptyResult {
|
pub fn validate_totp_code_str(user_uuid: &str, totp_code: &str, secret: &str, conn: &DbConn) -> EmptyResult {
|
||||||
let totp_code: u64 = match totp_code.parse() {
|
let totp_code: u64 = match totp_code.parse() {
|
||||||
Ok(code) => code,
|
Ok(code) => code,
|
||||||
_ => err!("TOTP code is not a number"),
|
_ => err!("TOTP code is not a number"),
|
||||||
};
|
};
|
||||||
|
|
||||||
validate_totp_code(totp_code, secret)
|
validate_totp_code(user_uuid, totp_code, secret, &conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_totp_code(totp_code: u64, secret: &str) -> EmptyResult {
|
pub fn validate_totp_code(user_uuid: &str, totp_code: u64, secret: &str, conn: &DbConn) -> EmptyResult {
|
||||||
use oath::{totp_raw_now, HashType};
|
use oath::{totp_raw_custom_time, HashType};
|
||||||
|
use std::time::{UNIX_EPOCH, SystemTime};
|
||||||
|
|
||||||
let decoded_secret = match BASE32.decode(secret.as_bytes()) {
|
let decoded_secret = match BASE32.decode(secret.as_bytes()) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(_) => err!("Invalid TOTP secret"),
|
Err(_) => err!("Invalid TOTP secret"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let generated = totp_raw_now(&decoded_secret, 6, 0, 30, &HashType::SHA1);
|
let mut twofactor = TwoFactor::find_by_user_and_type(&user_uuid, TwoFactorType::Authenticator as i32, &conn)?;
|
||||||
if generated != totp_code {
|
|
||||||
err!("Invalid TOTP code");
|
// Get the current system time in UNIX Epoch (UTC)
|
||||||
|
let current_time: u64 = SystemTime::now().duration_since(UNIX_EPOCH)
|
||||||
|
.expect("Earlier than 1970-01-01 00:00:00 UTC").as_secs();
|
||||||
|
|
||||||
|
// First check the current time for a valid token.
|
||||||
|
let time_step_now = (current_time / 30) as i32;
|
||||||
|
let generated_now = totp_raw_custom_time(&decoded_secret, 6, 0, 30, current_time, &HashType::SHA1);
|
||||||
|
if generated_now == totp_code && time_step_now > twofactor.last_used {
|
||||||
|
twofactor.last_used = time_step_now;
|
||||||
|
twofactor.save(&conn)?;
|
||||||
|
return Ok(());
|
||||||
|
} else if generated_now == totp_code && time_step_now <= twofactor.last_used {
|
||||||
|
warn!("This or a future TOTP code has already been used!");
|
||||||
|
err!("Invalid TOTP code!");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
// Check for time drifted codes
|
||||||
|
// First check the previous TOTP code
|
||||||
|
let time_step_prev = ((current_time - 30) / 30) as i32;
|
||||||
|
let generated_prev = totp_raw_custom_time(&decoded_secret, 6, 0, 30, current_time - 30, &HashType::SHA1);
|
||||||
|
if generated_prev == totp_code && time_step_prev > twofactor.last_used {
|
||||||
|
info!("TOTP Time drift detected. Token is valide for one step on the past.");
|
||||||
|
twofactor.last_used = time_step_prev;
|
||||||
|
twofactor.save(&conn)?;
|
||||||
|
return Ok(());
|
||||||
|
} else if generated_prev == totp_code && time_step_prev <= twofactor.last_used {
|
||||||
|
warn!("This or a future TOTP code has already been used!");
|
||||||
|
err!("Invalid TOTP code!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second check the next TOTP code
|
||||||
|
let time_step_next = ((current_time + 30) / 30) as i32;
|
||||||
|
let generated_next = totp_raw_custom_time(&decoded_secret, 6, 0, 30, current_time + 30, &HashType::SHA1);
|
||||||
|
if generated_next == totp_code && time_step_next > twofactor.last_used {
|
||||||
|
info!("TOTP Time drift detected. Token is valide for one step on the future.");
|
||||||
|
twofactor.last_used = time_step_next;
|
||||||
|
twofactor.save(&conn)?;
|
||||||
|
return Ok(());
|
||||||
|
} else if generated_next == totp_code && time_step_next <= twofactor.last_used {
|
||||||
|
warn!("This or a previous TOTP code has already been used!");
|
||||||
|
err!("Invalid TOTP code!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else no valide code received, deny access
|
||||||
|
err!("Invalid TOTP code!");
|
||||||
}
|
}
|
||||||
|
|
|
@ -197,7 +197,7 @@ fn twofactor_auth(
|
||||||
let mut remember = data.two_factor_remember.unwrap_or(0);
|
let mut remember = data.two_factor_remember.unwrap_or(0);
|
||||||
|
|
||||||
match TwoFactorType::from_i32(selected_id) {
|
match TwoFactorType::from_i32(selected_id) {
|
||||||
Some(TwoFactorType::Authenticator) => _tf::authenticator::validate_totp_code_str(twofactor_code, &selected_data?)?,
|
Some(TwoFactorType::Authenticator) => _tf::authenticator::validate_totp_code_str(user_uuid, twofactor_code, &selected_data?, conn)?,
|
||||||
Some(TwoFactorType::U2f) => _tf::u2f::validate_u2f_login(user_uuid, twofactor_code, conn)?,
|
Some(TwoFactorType::U2f) => _tf::u2f::validate_u2f_login(user_uuid, twofactor_code, conn)?,
|
||||||
Some(TwoFactorType::YubiKey) => _tf::yubikey::validate_yubikey_login(twofactor_code, &selected_data?)?,
|
Some(TwoFactorType::YubiKey) => _tf::yubikey::validate_yubikey_login(twofactor_code, &selected_data?)?,
|
||||||
Some(TwoFactorType::Duo) => _tf::duo::validate_duo_login(data.username.as_ref().unwrap(), twofactor_code, conn)?,
|
Some(TwoFactorType::Duo) => _tf::duo::validate_duo_login(data.username.as_ref().unwrap(), twofactor_code, conn)?,
|
||||||
|
|
|
@ -19,6 +19,7 @@ pub struct TwoFactor {
|
||||||
pub atype: i32,
|
pub atype: i32,
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub data: String,
|
pub data: String,
|
||||||
|
pub last_used: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -47,6 +48,7 @@ impl TwoFactor {
|
||||||
atype: atype as i32,
|
atype: atype as i32,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
data,
|
data,
|
||||||
|
last_used: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,6 +92,7 @@ table! {
|
||||||
atype -> Integer,
|
atype -> Integer,
|
||||||
enabled -> Bool,
|
enabled -> Bool,
|
||||||
data -> Text,
|
data -> Text,
|
||||||
|
last_used -> Integer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,6 +92,7 @@ table! {
|
||||||
atype -> Integer,
|
atype -> Integer,
|
||||||
enabled -> Bool,
|
enabled -> Bool,
|
||||||
data -> Text,
|
data -> Text,
|
||||||
|
last_used -> Integer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,6 +92,7 @@ table! {
|
||||||
atype -> Integer,
|
atype -> Integer,
|
||||||
enabled -> Bool,
|
enabled -> Bool,
|
||||||
data -> Text,
|
data -> Text,
|
||||||
|
last_used -> Integer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue