Merge pull request #657 from BlackDex/totp-timedrift

Updated authenticator TOTP
pull/664/head
Daniel García 5 years ago committed by GitHub
commit dc515b83f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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,58 @@ 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();
// The amount of steps back and forward in time
let steps = 1;
for step in -steps..=steps {
let time_step = (current_time / 30) as i32 + step;
// We need to calculate the time offsite and cast it as an i128.
// Else we can't do math with it on a default u64 variable.
let time_offset: i128 = (step * 30).into();
let generated = totp_raw_custom_time(&decoded_secret, 6, 0, 30, (current_time as i128 + time_offset) as u64, &HashType::SHA1);
// Check the the given code equals the generated and if the time_step is larger then the one last used.
if generated == totp_code && time_step > twofactor.last_used {
// If the step does not equals 0 the time is drifted either server or client side.
if step != 0 {
info!("TOTP Time drift detected. The step offset is {}", step);
}
// Save the last used time step so only totp time steps higher then this one are allowed.
twofactor.last_used = time_step;
twofactor.save(&conn)?;
return Ok(());
} else if generated == totp_code && time_step <= twofactor.last_used {
warn!("This or a TOTP code within {} steps back and forward has already been used!", steps);
err!("Invalid TOTP Code!");
}
} }
Ok(()) // 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…
Cancel
Save