mitigate sidechannel timing attack for basic auth
Information on whether a user with a given handle exists or not could be collected by checking the difference between response times of auth-required endpoints with and without a real handle being passed into Basic auth. This is because the time-expensive password hash would only be computed for users that exist, lengthening the response time. In local testing, this was a difference of 8ms vs. 35-60ms. A hash is now computed even if a user with the requested handle doesn't exist, mitigating the issue and leaving only negligible differences inbetween all response times, from which no information can be obtained.
This commit is contained in:
@@ -15,8 +15,8 @@ use crate::{
|
|||||||
users::{
|
users::{
|
||||||
User,
|
User,
|
||||||
auth::{
|
auth::{
|
||||||
AuthError, COOKIE_NAME, TokenSize, UserAuthRequired, UserAuthenticate,
|
AuthError, COOKIE_NAME, TokenSize, UserAuthDummyData, UserAuthRequired,
|
||||||
UserPasswordHashing,
|
UserAuthenticate, UserPasswordHashing,
|
||||||
},
|
},
|
||||||
sessions::Session,
|
sessions::Session,
|
||||||
},
|
},
|
||||||
@@ -81,6 +81,15 @@ impl UserPasswordHashing for User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: generate these at startup using predefined Argon2 params if
|
||||||
|
// these ever change from ::Default - the PHC must have the same factors as real hashes.
|
||||||
|
impl UserAuthDummyData for User {
|
||||||
|
/// This PHC generated for b"password"
|
||||||
|
const DUMMY_PASSWORD_PHC: &str = "$argon2id$v=19$m=19456,t=2,p=1$PXcTKpFhLRB70fVF35XYDQ$QOW2IxdPUvqD38+ScqX5SgO+jwweaMO9DUGqmkTeofQ";
|
||||||
|
/// Different than the input password of the PHC
|
||||||
|
const DUMMY_PASSWORD: &str = "different_password";
|
||||||
|
}
|
||||||
|
|
||||||
impl From<argon2::password_hash::Error> for AuthError {
|
impl From<argon2::password_hash::Error> for AuthError {
|
||||||
fn from(err: argon2::password_hash::Error) -> Self {
|
fn from(err: argon2::password_hash::Error) -> Self {
|
||||||
AuthError::PassHashError(err)
|
AuthError::PassHashError(err)
|
||||||
@@ -175,7 +184,10 @@ fn authenticate_basic(credentials: &str) -> Result<Option<User>, AuthError> {
|
|||||||
true => Ok(Some(User::get_by_id(id)?)),
|
true => Ok(Some(User::get_by_id(id)?)),
|
||||||
false => Err(AuthError::InvalidCredentials),
|
false => Err(AuthError::InvalidCredentials),
|
||||||
},
|
},
|
||||||
_ => Err(AuthError::InvalidCredentials),
|
_ => {
|
||||||
|
let _ = User::match_hash_password(User::DUMMY_PASSWORD, User::DUMMY_PASSWORD_PHC)?;
|
||||||
|
Err(AuthError::InvalidCredentials)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ pub trait UserPasswordHashing {
|
|||||||
/// Returns whether the password matches the hash
|
/// Returns whether the password matches the hash
|
||||||
fn match_hash_password(passw: &str, hash: &str) -> Result<bool, argon2::password_hash::Error>;
|
fn match_hash_password(passw: &str, hash: &str) -> Result<bool, argon2::password_hash::Error>;
|
||||||
}
|
}
|
||||||
|
pub trait UserAuthDummyData {
|
||||||
|
const DUMMY_PASSWORD_PHC: &str;
|
||||||
|
const DUMMY_PASSWORD: &str;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum AuthError {
|
pub enum AuthError {
|
||||||
|
|||||||
Reference in New Issue
Block a user