aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--migrations/mysql/2019-10-10-083032_add_column_to_twofactor/down.sql0
-rw-r--r--migrations/mysql/2019-10-10-083032_add_column_to_twofactor/up.sql1
-rw-r--r--migrations/postgresql/2019-10-10-083032_add_column_to_twofactor/down.sql0
-rw-r--r--migrations/postgresql/2019-10-10-083032_add_column_to_twofactor/up.sql1
-rw-r--r--migrations/sqlite/2019-10-10-083032_add_column_to_twofactor/down.sql0
-rw-r--r--migrations/sqlite/2019-10-10-083032_add_column_to_twofactor/up.sql1
-rw-r--r--src/api/core/two_factor/authenticator.rs60
-rw-r--r--src/api/identity.rs2
-rw-r--r--src/db/models/two_factor.rs2
-rw-r--r--src/db/schemas/mysql/schema.rs1
-rw-r--r--src/db/schemas/postgresql/schema.rs1
-rw-r--r--src/db/schemas/sqlite/schema.rs1
12 files changed, 60 insertions, 10 deletions
diff --git a/migrations/mysql/2019-10-10-083032_add_column_to_twofactor/down.sql b/migrations/mysql/2019-10-10-083032_add_column_to_twofactor/down.sql
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/migrations/mysql/2019-10-10-083032_add_column_to_twofactor/down.sql
diff --git a/migrations/mysql/2019-10-10-083032_add_column_to_twofactor/up.sql b/migrations/mysql/2019-10-10-083032_add_column_to_twofactor/up.sql
new file mode 100644
index 00000000..cfcd6ca2
--- /dev/null
+++ b/migrations/mysql/2019-10-10-083032_add_column_to_twofactor/up.sql
@@ -0,0 +1 @@
+ALTER TABLE twofactor ADD COLUMN last_used INTEGER NOT NULL DEFAULT 0; \ No newline at end of file
diff --git a/migrations/postgresql/2019-10-10-083032_add_column_to_twofactor/down.sql b/migrations/postgresql/2019-10-10-083032_add_column_to_twofactor/down.sql
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/migrations/postgresql/2019-10-10-083032_add_column_to_twofactor/down.sql
diff --git a/migrations/postgresql/2019-10-10-083032_add_column_to_twofactor/up.sql b/migrations/postgresql/2019-10-10-083032_add_column_to_twofactor/up.sql
new file mode 100644
index 00000000..cfcd6ca2
--- /dev/null
+++ b/migrations/postgresql/2019-10-10-083032_add_column_to_twofactor/up.sql
@@ -0,0 +1 @@
+ALTER TABLE twofactor ADD COLUMN last_used INTEGER NOT NULL DEFAULT 0; \ No newline at end of file
diff --git a/migrations/sqlite/2019-10-10-083032_add_column_to_twofactor/down.sql b/migrations/sqlite/2019-10-10-083032_add_column_to_twofactor/down.sql
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/migrations/sqlite/2019-10-10-083032_add_column_to_twofactor/down.sql
diff --git a/migrations/sqlite/2019-10-10-083032_add_column_to_twofactor/up.sql b/migrations/sqlite/2019-10-10-083032_add_column_to_twofactor/up.sql
new file mode 100644
index 00000000..14d3c683
--- /dev/null
+++ b/migrations/sqlite/2019-10-10-083032_add_column_to_twofactor/up.sql
@@ -0,0 +1 @@
+ALTER TABLE twofactor ADD COLUMN last_used INTEGER NOT NULL DEFAULT 0;
diff --git a/src/api/core/two_factor/authenticator.rs b/src/api/core/two_factor/authenticator.rs
index eeb91c46..7b4c5f92 100644
--- a/src/api/core/two_factor/authenticator.rs
+++ b/src/api/core/two_factor/authenticator.rs
@@ -77,7 +77,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
- validate_totp_code(token, &twofactor.data)?;
+ validate_totp_code(&user.uuid, token, &twofactor.data, &conn)?;
_generate_recover_code(&mut user, &conn);
twofactor.save(&conn)?;
@@ -94,27 +94,69 @@ fn activate_authenticator_put(data: JsonUpcase<EnableAuthenticatorData>, headers
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() {
Ok(code) => code,
_ => 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 {
- use oath::{totp_raw_now, HashType};
+pub fn validate_totp_code(user_uuid: &str, totp_code: u64, secret: &str, conn: &DbConn) -> EmptyResult {
+ use oath::{totp_raw_custom_time, HashType};
+ use std::time::{UNIX_EPOCH, SystemTime};
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");
+ let mut twofactor = TwoFactor::find_by_user_and_type(&user_uuid, TwoFactorType::Authenticator as i32, &conn)?;
+
+ // 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!");
}
diff --git a/src/api/identity.rs b/src/api/identity.rs
index 2da203dd..03460ebe 100644
--- a/src/api/identity.rs
+++ b/src/api/identity.rs
@@ -197,7 +197,7 @@ fn twofactor_auth(
let mut remember = data.two_factor_remember.unwrap_or(0);
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::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)?,
diff --git a/src/db/models/two_factor.rs b/src/db/models/two_factor.rs
index d869245c..ecc3c69a 100644
--- a/src/db/models/two_factor.rs
+++ b/src/db/models/two_factor.rs
@@ -19,6 +19,7 @@ pub struct TwoFactor {
pub atype: i32,
pub enabled: bool,
pub data: String,
+ pub last_used: i32,
}
#[allow(dead_code)]
@@ -47,6 +48,7 @@ impl TwoFactor {
atype: atype as i32,
enabled: true,
data,
+ last_used: 0,
}
}
diff --git a/src/db/schemas/mysql/schema.rs b/src/db/schemas/mysql/schema.rs
index 51ec1880..2e705112 100644
--- a/src/db/schemas/mysql/schema.rs
+++ b/src/db/schemas/mysql/schema.rs
@@ -92,6 +92,7 @@ table! {
atype -> Integer,
enabled -> Bool,
data -> Text,
+ last_used -> Integer,
}
}
diff --git a/src/db/schemas/postgresql/schema.rs b/src/db/schemas/postgresql/schema.rs
index 1bc924c9..0112c59a 100644
--- a/src/db/schemas/postgresql/schema.rs
+++ b/src/db/schemas/postgresql/schema.rs
@@ -92,6 +92,7 @@ table! {
atype -> Integer,
enabled -> Bool,
data -> Text,
+ last_used -> Integer,
}
}
diff --git a/src/db/schemas/sqlite/schema.rs b/src/db/schemas/sqlite/schema.rs
index 750cecd3..43abe5c4 100644
--- a/src/db/schemas/sqlite/schema.rs
+++ b/src/db/schemas/sqlite/schema.rs
@@ -92,6 +92,7 @@ table! {
atype -> Integer,
enabled -> Bool,
data -> Text,
+ last_used -> Integer,
}
}