mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 20:47:01 +01:00
397 Caching password badlist (#425)
This commit is contained in:
parent
77381c1a2a
commit
2bd8606cb6
|
@ -2,6 +2,7 @@ use crate::idm::account::Account;
|
|||
use crate::idm::claim::Claim;
|
||||
use crate::idm::AuthState;
|
||||
use crate::prelude::*;
|
||||
use hashbrown::HashSet;
|
||||
use kanidm_proto::v1::OperationError;
|
||||
use kanidm_proto::v1::{AuthAllowed, AuthCredential, AuthMech};
|
||||
|
||||
|
@ -15,7 +16,6 @@ use crate::credential::webauthn::WebauthnDomainConfig;
|
|||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
// use webauthn_rs::proto::Credential as WebauthnCredential;
|
||||
use crate::value::PartialValue;
|
||||
pub use std::collections::BTreeSet as Set;
|
||||
use webauthn_rs::proto::RequestChallengeResponse;
|
||||
use webauthn_rs::{AuthenticationState, Webauthn};
|
||||
|
@ -181,13 +181,13 @@ impl CredHandler {
|
|||
pw: &mut Password,
|
||||
who: Uuid,
|
||||
async_tx: &Sender<DelayedAction>,
|
||||
pw_badlist_set: Option<Set<Value>>,
|
||||
pw_badlist_set: Option<&HashSet<String>>,
|
||||
) -> CredState {
|
||||
match cred {
|
||||
AuthCredential::Password(cleartext) => {
|
||||
if pw.verify(cleartext.as_str()).unwrap_or(false) {
|
||||
match pw_badlist_set {
|
||||
Some(p) if p.contains(&PartialValue::new_iutf8(cleartext)) => {
|
||||
Some(p) if p.contains(&cleartext.to_lowercase()) => {
|
||||
lsecurity!(
|
||||
au,
|
||||
"Handler::Password -> Result::Denied - Password found in badlist during login"
|
||||
|
@ -227,7 +227,7 @@ impl CredHandler {
|
|||
webauthn: &Webauthn<WebauthnDomainConfig>,
|
||||
who: Uuid,
|
||||
async_tx: &Sender<DelayedAction>,
|
||||
pw_badlist_set: Option<Set<Value>>,
|
||||
pw_badlist_set: Option<&HashSet<String>>,
|
||||
) -> CredState {
|
||||
match (&pw_mfa.mfa_state, &pw_mfa.pw_state) {
|
||||
(CredVerifyState::Init, CredVerifyState::Init) => {
|
||||
|
@ -290,7 +290,7 @@ impl CredHandler {
|
|||
AuthCredential::Password(cleartext) => {
|
||||
if pw_mfa.pw.verify(cleartext.as_str()).unwrap_or(false) {
|
||||
match pw_badlist_set {
|
||||
Some(p) if p.contains(&PartialValue::new_iutf8(cleartext)) => {
|
||||
Some(p) if p.contains(&cleartext.to_lowercase()) => {
|
||||
pw_mfa.pw_state = CredVerifyState::Fail;
|
||||
lsecurity!(
|
||||
au,
|
||||
|
@ -404,7 +404,7 @@ impl CredHandler {
|
|||
who: Uuid,
|
||||
async_tx: &Sender<DelayedAction>,
|
||||
webauthn: &Webauthn<WebauthnDomainConfig>,
|
||||
pw_badlist_set: Option<Set<Value>>,
|
||||
pw_badlist_set: Option<&HashSet<String>>,
|
||||
) -> CredState {
|
||||
match self {
|
||||
CredHandler::Anonymous => Self::validate_anonymous(au, cred),
|
||||
|
@ -501,8 +501,6 @@ pub(crate) struct AuthSession {
|
|||
//
|
||||
// This handler will then handle the mfa and stepping up through to generate the auth states
|
||||
state: AuthSessionState,
|
||||
// Store the password badlist
|
||||
pw_badlist_set: Option<Set<Value>>,
|
||||
}
|
||||
|
||||
impl AuthSession {
|
||||
|
@ -512,7 +510,6 @@ impl AuthSession {
|
|||
_appid: &Option<String>,
|
||||
webauthn: &Webauthn<WebauthnDomainConfig>,
|
||||
ct: Duration,
|
||||
pw_badlist_set: Option<Set<Value>>,
|
||||
) -> (Option<Self>, AuthState) {
|
||||
// During this setup, determine the credential handler that we'll be using
|
||||
// for this session. This is currently based on presentation of an application
|
||||
|
@ -556,11 +553,7 @@ impl AuthSession {
|
|||
(None, AuthState::Denied(reason.to_string()))
|
||||
} else {
|
||||
// We can proceed
|
||||
let auth_session = AuthSession {
|
||||
account,
|
||||
state,
|
||||
pw_badlist_set,
|
||||
};
|
||||
let auth_session = AuthSession { account, state };
|
||||
// Get the set of mechanisms that can proceed. This is tied
|
||||
// to the session so that it can mutate state and have progression
|
||||
// of what's next, or ordering.
|
||||
|
@ -644,6 +637,7 @@ impl AuthSession {
|
|||
time: &Duration,
|
||||
async_tx: &Sender<DelayedAction>,
|
||||
webauthn: &Webauthn<WebauthnDomainConfig>,
|
||||
pw_badlist_set: Option<&HashSet<String>>,
|
||||
) -> Result<AuthState, OperationError> {
|
||||
let (next_state, response) = match &mut self.state {
|
||||
AuthSessionState::Init(_) | AuthSessionState::Success | AuthSessionState::Denied(_) => {
|
||||
|
@ -659,7 +653,7 @@ impl AuthSession {
|
|||
self.account.uuid,
|
||||
async_tx,
|
||||
webauthn,
|
||||
self.pw_badlist_set.clone(),
|
||||
pw_badlist_set,
|
||||
) {
|
||||
CredState::Success(claims) => {
|
||||
lsecurity!(au, "Successful cred handling");
|
||||
|
@ -738,6 +732,7 @@ mod tests {
|
|||
use crate::idm::delayed::DelayedAction;
|
||||
use crate::idm::AuthState;
|
||||
use crate::prelude::*;
|
||||
use hashbrown::HashSet;
|
||||
pub use std::collections::BTreeSet as Set;
|
||||
|
||||
use crate::utils::duration_from_epoch_now;
|
||||
|
@ -749,6 +744,12 @@ mod tests {
|
|||
use tokio::sync::mpsc::unbounded_channel as unbounded;
|
||||
use webauthn_authenticator_rs::{softtok::U2FSoft, WebauthnAuthenticator};
|
||||
|
||||
fn create_pw_badlist_cache() -> HashSet<String> {
|
||||
let mut s = HashSet::new();
|
||||
s.insert((&"list@no3IBTyqHu$bad").to_lowercase());
|
||||
s
|
||||
}
|
||||
|
||||
fn create_webauthn() -> Webauthn<WebauthnDomainConfig> {
|
||||
Webauthn::new(WebauthnDomainConfig {
|
||||
rp_name: "example.com".to_string(),
|
||||
|
@ -774,7 +775,6 @@ mod tests {
|
|||
&None,
|
||||
&webauthn,
|
||||
duration_from_epoch_now(),
|
||||
Option::None,
|
||||
);
|
||||
|
||||
if let AuthState::Choose(auth_mechs) = state {
|
||||
|
@ -822,7 +822,6 @@ mod tests {
|
|||
&Some("NonExistantAppID".to_string()),
|
||||
&webauthn,
|
||||
duration_from_epoch_now(),
|
||||
Option::None,
|
||||
);
|
||||
|
||||
// We now ignore appids.
|
||||
|
@ -841,15 +840,12 @@ mod tests {
|
|||
$account:expr,
|
||||
$webauthn:expr
|
||||
) => {{
|
||||
let mut pw_badlist_set = Set::new();
|
||||
pw_badlist_set.insert(Value::new_iutf8("list@no3IBTyqHu$bad"));
|
||||
let (session, state) = AuthSession::new(
|
||||
$audit,
|
||||
$account.clone(),
|
||||
&None,
|
||||
$webauthn,
|
||||
duration_from_epoch_now(),
|
||||
Some(pw_badlist_set),
|
||||
);
|
||||
let mut session = session.unwrap();
|
||||
|
||||
|
@ -879,7 +875,7 @@ mod tests {
|
|||
panic!("Invalid auth state")
|
||||
}
|
||||
|
||||
session
|
||||
(session, create_pw_badlist_cache())
|
||||
}};
|
||||
}
|
||||
|
||||
|
@ -901,7 +897,8 @@ mod tests {
|
|||
let (async_tx, mut async_rx) = unbounded();
|
||||
|
||||
// now check
|
||||
let mut session = start_password_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, pw_badlist_cache) =
|
||||
start_password_session!(&mut audit, account, &webauthn);
|
||||
|
||||
let attempt = AuthCredential::Password("bad_password".to_string());
|
||||
match session.validate_creds(
|
||||
|
@ -910,6 +907,7 @@ mod tests {
|
|||
&Duration::from_secs(0),
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Denied(_)) => {}
|
||||
_ => panic!(),
|
||||
|
@ -917,7 +915,8 @@ mod tests {
|
|||
|
||||
// === Now begin a new session, and use a good pw.
|
||||
|
||||
let mut session = start_password_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, pw_badlist_cache) =
|
||||
start_password_session!(&mut audit, account, &webauthn);
|
||||
|
||||
let attempt = AuthCredential::Password("test_password".to_string());
|
||||
match session.validate_creds(
|
||||
|
@ -926,6 +925,7 @@ mod tests {
|
|||
&Duration::from_secs(0),
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Success(_)) => {}
|
||||
_ => panic!(),
|
||||
|
@ -955,7 +955,8 @@ mod tests {
|
|||
let (async_tx, mut async_rx) = unbounded();
|
||||
|
||||
// now check, even though the password is correct, Auth should be denied since it is in badlist
|
||||
let mut session = start_password_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, pw_badlist_cache) =
|
||||
start_password_session!(&mut audit, account, &webauthn);
|
||||
|
||||
let attempt = AuthCredential::Password("list@no3IBTyqHu$bad".to_string());
|
||||
match session.validate_creds(
|
||||
|
@ -964,6 +965,7 @@ mod tests {
|
|||
&Duration::from_secs(0),
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Denied(msg)) => assert!(msg == PW_BADLIST_MSG),
|
||||
_ => panic!(),
|
||||
|
@ -980,15 +982,12 @@ mod tests {
|
|||
$account:expr,
|
||||
$webauthn:expr
|
||||
) => {{
|
||||
let mut pw_badlist_set = Set::new();
|
||||
pw_badlist_set.insert(Value::new_iutf8("list@no3IBTyqHu$bad"));
|
||||
let (session, state) = AuthSession::new(
|
||||
$audit,
|
||||
$account.clone(),
|
||||
&None,
|
||||
$webauthn,
|
||||
duration_from_epoch_now(),
|
||||
Some(pw_badlist_set),
|
||||
);
|
||||
let mut session = session.expect("Session was unable to be created.");
|
||||
|
||||
|
@ -1025,7 +1024,7 @@ mod tests {
|
|||
panic!("Invalid auth state")
|
||||
}
|
||||
|
||||
(session, rchal)
|
||||
(session, rchal, create_pw_badlist_cache())
|
||||
}};
|
||||
}
|
||||
|
||||
|
@ -1070,7 +1069,8 @@ mod tests {
|
|||
|
||||
// check send anon (fail)
|
||||
{
|
||||
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, _, pw_badlist_cache) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
|
||||
match session.validate_creds(
|
||||
&mut audit,
|
||||
|
@ -1078,6 +1078,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||
_ => panic!(),
|
||||
|
@ -1088,7 +1089,8 @@ mod tests {
|
|||
|
||||
// Sending a PW first is an immediate fail.
|
||||
{
|
||||
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, _, pw_badlist_cache) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
|
||||
match session.validate_creds(
|
||||
&mut audit,
|
||||
|
@ -1096,6 +1098,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||
_ => panic!(),
|
||||
|
@ -1103,7 +1106,8 @@ mod tests {
|
|||
}
|
||||
// check send bad totp, should fail immediate
|
||||
{
|
||||
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, _, pw_badlist_cache) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
|
||||
match session.validate_creds(
|
||||
&mut audit,
|
||||
|
@ -1111,6 +1115,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG),
|
||||
_ => panic!(),
|
||||
|
@ -1120,7 +1125,8 @@ mod tests {
|
|||
// check send good totp, should continue
|
||||
// then bad pw, fail pw
|
||||
{
|
||||
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, _, pw_badlist_cache) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
|
||||
match session.validate_creds(
|
||||
&mut audit,
|
||||
|
@ -1128,6 +1134,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||
_ => panic!(),
|
||||
|
@ -1138,6 +1145,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
|
||||
_ => panic!(),
|
||||
|
@ -1147,7 +1155,8 @@ mod tests {
|
|||
// check send good totp, should continue
|
||||
// then good pw, success
|
||||
{
|
||||
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, _, pw_badlist_cache) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
|
||||
match session.validate_creds(
|
||||
&mut audit,
|
||||
|
@ -1155,6 +1164,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||
_ => panic!(),
|
||||
|
@ -1165,6 +1175,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Success(_)) => {}
|
||||
_ => panic!(),
|
||||
|
@ -1215,7 +1226,8 @@ mod tests {
|
|||
// check send good totp, should continue
|
||||
// then badlist pw, failed
|
||||
{
|
||||
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, _, pw_badlist_cache) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
|
||||
match session.validate_creds(
|
||||
&mut audit,
|
||||
|
@ -1223,6 +1235,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||
_ => panic!(),
|
||||
|
@ -1233,6 +1246,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Denied(msg)) => assert!(msg == PW_BADLIST_MSG),
|
||||
_ => panic!(),
|
||||
|
@ -1256,7 +1270,6 @@ mod tests {
|
|||
&None,
|
||||
$webauthn,
|
||||
duration_from_epoch_now(),
|
||||
Option::None,
|
||||
);
|
||||
let mut session = session.unwrap();
|
||||
|
||||
|
@ -1349,6 +1362,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
None,
|
||||
) {
|
||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||
_ => panic!(),
|
||||
|
@ -1369,6 +1383,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
None,
|
||||
) {
|
||||
Ok(AuthState::Success(_)) => {}
|
||||
_ => panic!(),
|
||||
|
@ -1397,6 +1412,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
None,
|
||||
) {
|
||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
|
||||
_ => panic!(),
|
||||
|
@ -1441,6 +1457,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
None,
|
||||
) {
|
||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
|
||||
_ => panic!(),
|
||||
|
@ -1479,7 +1496,8 @@ mod tests {
|
|||
|
||||
// check pw first (fail)
|
||||
{
|
||||
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, _, pw_badlist_cache) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
|
||||
match session.validate_creds(
|
||||
&mut audit,
|
||||
|
@ -1487,6 +1505,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||
_ => panic!(),
|
||||
|
@ -1495,7 +1514,8 @@ mod tests {
|
|||
|
||||
// Check totp first attempt fails.
|
||||
{
|
||||
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, _, pw_badlist_cache) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
|
||||
match session.validate_creds(
|
||||
&mut audit,
|
||||
|
@ -1503,6 +1523,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||
_ => panic!(),
|
||||
|
@ -1513,8 +1534,10 @@ mod tests {
|
|||
// NOTE: We only check bad challenge here as bad softtoken is already
|
||||
// extensively tested.
|
||||
{
|
||||
let (_session, inv_chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, _chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (_session, inv_chal, pw_badlist_cache) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, _chal, _) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
|
||||
let inv_chal = inv_chal.unwrap();
|
||||
|
||||
|
@ -1529,6 +1552,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
|
||||
_ => panic!(),
|
||||
|
@ -1537,7 +1561,8 @@ mod tests {
|
|||
|
||||
// check good webauthn/bad pw (fail)
|
||||
{
|
||||
let (mut session, chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, chal, pw_badlist_cache) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let chal = chal.unwrap();
|
||||
|
||||
let resp = wa
|
||||
|
@ -1550,6 +1575,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||
_ => panic!(),
|
||||
|
@ -1560,6 +1586,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
|
||||
_ => panic!(),
|
||||
|
@ -1574,7 +1601,8 @@ mod tests {
|
|||
|
||||
// Check good webauthn/good pw (pass)
|
||||
{
|
||||
let (mut session, chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, chal, pw_badlist_cache) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let chal = chal.unwrap();
|
||||
|
||||
let resp = wa
|
||||
|
@ -1587,6 +1615,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||
_ => panic!(),
|
||||
|
@ -1597,6 +1626,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Success(_)) => {}
|
||||
_ => panic!(),
|
||||
|
@ -1652,7 +1682,8 @@ mod tests {
|
|||
|
||||
// check pw first (fail)
|
||||
{
|
||||
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, _, pw_badlist_cache) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
|
||||
match session.validate_creds(
|
||||
&mut audit,
|
||||
|
@ -1660,6 +1691,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_AUTH_TYPE_MSG),
|
||||
_ => panic!(),
|
||||
|
@ -1668,7 +1700,8 @@ mod tests {
|
|||
|
||||
// Check bad totp (fail)
|
||||
{
|
||||
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, _, pw_badlist_cache) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
|
||||
match session.validate_creds(
|
||||
&mut audit,
|
||||
|
@ -1676,6 +1709,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_TOTP_MSG),
|
||||
_ => panic!(),
|
||||
|
@ -1684,8 +1718,10 @@ mod tests {
|
|||
|
||||
// check bad webauthn (fail)
|
||||
{
|
||||
let (_session, inv_chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, _chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (_session, inv_chal, pw_badlist_cache) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, _chal, _) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
|
||||
let inv_chal = inv_chal.unwrap();
|
||||
|
||||
|
@ -1700,6 +1736,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_WEBAUTHN_MSG),
|
||||
_ => panic!(),
|
||||
|
@ -1708,7 +1745,8 @@ mod tests {
|
|||
|
||||
// check good webauthn/bad pw (fail)
|
||||
{
|
||||
let (mut session, chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, chal, pw_badlist_cache) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let chal = chal.unwrap();
|
||||
|
||||
let resp = wa
|
||||
|
@ -1721,6 +1759,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||
_ => panic!(),
|
||||
|
@ -1731,6 +1770,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
|
||||
_ => panic!(),
|
||||
|
@ -1745,7 +1785,8 @@ mod tests {
|
|||
|
||||
// check good totp/bad pw (fail)
|
||||
{
|
||||
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, _, pw_badlist_cache) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
|
||||
match session.validate_creds(
|
||||
&mut audit,
|
||||
|
@ -1753,6 +1794,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||
_ => panic!(),
|
||||
|
@ -1763,6 +1805,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Denied(msg)) => assert!(msg == BAD_PASSWORD_MSG),
|
||||
_ => panic!(),
|
||||
|
@ -1771,7 +1814,8 @@ mod tests {
|
|||
|
||||
// check good totp/good pw (pass)
|
||||
{
|
||||
let (mut session, _) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, _, pw_badlist_cache) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
|
||||
match session.validate_creds(
|
||||
&mut audit,
|
||||
|
@ -1779,6 +1823,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||
_ => panic!(),
|
||||
|
@ -1789,6 +1834,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Success(_)) => {}
|
||||
_ => panic!(),
|
||||
|
@ -1797,7 +1843,8 @@ mod tests {
|
|||
|
||||
// Check good webauthn/good pw (pass)
|
||||
{
|
||||
let (mut session, chal) = start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let (mut session, chal, pw_badlist_cache) =
|
||||
start_password_mfa_session!(&mut audit, account, &webauthn);
|
||||
let chal = chal.unwrap();
|
||||
|
||||
let resp = wa
|
||||
|
@ -1810,6 +1857,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Continue(cont)) => assert!(cont == vec![AuthAllowed::Password]),
|
||||
_ => panic!(),
|
||||
|
@ -1820,6 +1868,7 @@ mod tests {
|
|||
&ts,
|
||||
&async_tx,
|
||||
&webauthn,
|
||||
Some(&pw_badlist_cache),
|
||||
) {
|
||||
Ok(AuthState::Success(_)) => {}
|
||||
_ => panic!(),
|
||||
|
|
|
@ -23,6 +23,7 @@ use crate::idm::delayed::{
|
|||
DelayedAction, PasswordUpgrade, UnixPasswordUpgrade, WebauthnCounterIncrement,
|
||||
};
|
||||
|
||||
use hashbrown::HashSet;
|
||||
use kanidm_proto::v1::CredentialStatus;
|
||||
use kanidm_proto::v1::RadiusAuthToken;
|
||||
use kanidm_proto::v1::SetCredentialResponse;
|
||||
|
@ -39,10 +40,16 @@ use async_std::task;
|
|||
use core::task::{Context, Poll};
|
||||
use futures::task as futures_task;
|
||||
|
||||
use concread::bptree::{BptreeMap, BptreeMapWriteTxn};
|
||||
use concread::hashmap::HashMap;
|
||||
use concread::{
|
||||
bptree::{BptreeMap, BptreeMapWriteTxn},
|
||||
CowCell,
|
||||
};
|
||||
use concread::{
|
||||
cowcell::{CowCellReadTxn, CowCellWriteTxn},
|
||||
hashmap::HashMap,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use std::time::Duration;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use url::Url;
|
||||
|
||||
use webauthn_rs::Webauthn;
|
||||
|
@ -67,6 +74,7 @@ pub struct IdmServer {
|
|||
async_tx: Sender<DelayedAction>,
|
||||
// Our webauthn verifier/config
|
||||
webauthn: Webauthn<WebauthnDomainConfig>,
|
||||
pw_badlist_cache: Arc<CowCell<HashSet<String>>>,
|
||||
}
|
||||
|
||||
pub struct IdmServerAuthTransaction<'a> {
|
||||
|
@ -84,6 +92,7 @@ pub struct IdmServerAuthTransaction<'a> {
|
|||
// For flagging eventual actions.
|
||||
async_tx: Sender<DelayedAction>,
|
||||
webauthn: &'a Webauthn<WebauthnDomainConfig>,
|
||||
pw_badlist_cache: CowCellReadTxn<HashSet<String>>,
|
||||
}
|
||||
|
||||
pub struct IdmServerProxyReadTransaction<'a> {
|
||||
|
@ -101,6 +110,7 @@ pub struct IdmServerProxyWriteTransaction<'a> {
|
|||
sid: SID,
|
||||
crypto_policy: &'a CryptoPolicy,
|
||||
webauthn: &'a Webauthn<WebauthnDomainConfig>,
|
||||
pw_badlist_cache: CowCellWriteTxn<'a, HashSet<String>>,
|
||||
}
|
||||
|
||||
pub struct IdmServerDelayed {
|
||||
|
@ -125,9 +135,12 @@ impl IdmServer {
|
|||
let (async_tx, async_rx) = unbounded();
|
||||
|
||||
// Get the domain name, as the relying party id.
|
||||
let rp_id = {
|
||||
let (rp_id, pw_badlist_set) = {
|
||||
let qs_read = task::block_on(qs.read_async());
|
||||
qs_read.get_domain_name(au)?
|
||||
(
|
||||
qs_read.get_domain_name(au)?,
|
||||
qs_read.get_password_badlist(au)?,
|
||||
)
|
||||
};
|
||||
|
||||
// Check that it gels with our origin.
|
||||
|
@ -170,6 +183,7 @@ impl IdmServer {
|
|||
crypto_policy,
|
||||
async_tx,
|
||||
webauthn,
|
||||
pw_badlist_cache: Arc::new(CowCell::new(pw_badlist_set)),
|
||||
},
|
||||
IdmServerDelayed { async_rx },
|
||||
))
|
||||
|
@ -199,6 +213,7 @@ impl IdmServer {
|
|||
sid,
|
||||
async_tx: self.async_tx.clone(),
|
||||
webauthn: &self.webauthn,
|
||||
pw_badlist_cache: self.pw_badlist_cache.read(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,6 +245,7 @@ impl IdmServer {
|
|||
sid,
|
||||
crypto_policy: &self.crypto_policy,
|
||||
webauthn: &self.webauthn,
|
||||
pw_badlist_cache: self.pw_badlist_cache.write(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -369,22 +385,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
};
|
||||
|
||||
let (auth_session, state) = if is_valid {
|
||||
//TODO #397: we can keep a cached map of the badlist, and pass by reference rather than by value
|
||||
let badlist_entry = self
|
||||
.qs_read
|
||||
.internal_search_uuid(au, &UUID_SYSTEM_CONFIG)
|
||||
.map_err(|e| {
|
||||
ladmin_error!(au, "Failed to retrieve system configuration {:?}", e);
|
||||
e
|
||||
})?;
|
||||
AuthSession::new(
|
||||
au,
|
||||
account,
|
||||
&init.appid,
|
||||
self.webauthn,
|
||||
ct,
|
||||
badlist_entry.get_ava_set("badlist_password").cloned(),
|
||||
)
|
||||
AuthSession::new(au, account, &init.appid, self.webauthn, ct)
|
||||
} else {
|
||||
// it's softlocked, don't even bother.
|
||||
lsecurity!(au, "Account is softlocked.");
|
||||
|
@ -514,8 +515,16 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
// Process the credentials here as required.
|
||||
// Basically throw them at the auth_session and see what
|
||||
// falls out.
|
||||
let pw_badlist_cache = Some(&(*self.pw_badlist_cache));
|
||||
auth_session
|
||||
.validate_creds(au, &creds.cred, &ct, &self.async_tx, self.webauthn)
|
||||
.validate_creds(
|
||||
au,
|
||||
&creds.cred,
|
||||
&ct,
|
||||
&self.async_tx,
|
||||
self.webauthn,
|
||||
pw_badlist_cache,
|
||||
)
|
||||
.map(|aus| {
|
||||
// Inspect the result:
|
||||
// if it was a failure, we need to inc the softlock.
|
||||
|
@ -897,15 +906,8 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
|
||||
// check a password badlist to eliminate more content
|
||||
// we check the password as "lower case" to help eliminate possibilities
|
||||
let lc_password = PartialValue::new_iutf8(cleartext);
|
||||
let badlist_entry = self
|
||||
.qs_write
|
||||
.internal_search_uuid(au, &UUID_SYSTEM_CONFIG)
|
||||
.map_err(|e| {
|
||||
ladmin_error!(au, "Failed to retrieve system configuration {:?}", e);
|
||||
e
|
||||
})?;
|
||||
if badlist_entry.attribute_value_pres("badlist_password", &lc_password) {
|
||||
// also, when pw_badlist_cache is read from DB, it is read as Value (iutf8 lowercase)
|
||||
if (&*self.pw_badlist_cache).contains(&cleartext.to_lowercase()) {
|
||||
lsecurity!(au, "Password found in badlist, rejecting");
|
||||
Err(OperationError::PasswordBadListed)
|
||||
} else {
|
||||
|
@ -1517,16 +1519,34 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn commit(self, au: &mut AuditScope) -> Result<(), OperationError> {
|
||||
pub fn commit(mut self, au: &mut AuditScope) -> Result<(), OperationError> {
|
||||
lperf_trace_segment!(
|
||||
au,
|
||||
"idm::server::IdmServerProxyWriteTransaction::commit",
|
||||
|| {
|
||||
if self
|
||||
.qs_write
|
||||
.get_changed_uuids()
|
||||
.contains(&UUID_SYSTEM_CONFIG)
|
||||
{
|
||||
self.reload_password_badlist(au)?;
|
||||
self.pw_badlist_cache.commit();
|
||||
};
|
||||
self.mfareg_sessions.commit();
|
||||
self.qs_write.commit(au)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn reload_password_badlist(&mut self, au: &mut AuditScope) -> Result<(), OperationError> {
|
||||
match self.qs_write.get_password_badlist(au) {
|
||||
Ok(badlist_entry) => {
|
||||
*self.pw_badlist_cache = badlist_entry;
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Need tests of the sessions and the auth ...
|
||||
|
|
|
@ -684,6 +684,32 @@ pub trait QueryServerTransaction<'a> {
|
|||
e
|
||||
})
|
||||
}
|
||||
|
||||
// This is a helper to get password badlist.
|
||||
fn get_password_badlist(
|
||||
&self,
|
||||
audit: &mut AuditScope,
|
||||
) -> Result<HashSet<String>, OperationError> {
|
||||
self.internal_search_uuid(audit, &UUID_SYSTEM_CONFIG)
|
||||
.and_then(|e| match e.get_ava_set("badlist_password") {
|
||||
Some(badlist_entry) => {
|
||||
let mut badlist_hashset = HashSet::with_capacity(badlist_entry.len());
|
||||
badlist_entry
|
||||
.iter()
|
||||
.filter_map(|e| e.as_string())
|
||||
.cloned()
|
||||
.for_each(|s| {
|
||||
badlist_hashset.insert(s);
|
||||
});
|
||||
Ok(badlist_hashset)
|
||||
}
|
||||
None => Err(OperationError::InvalidEntryState),
|
||||
})
|
||||
.map_err(|e| {
|
||||
ladmin_error!(audit, "Failed to retrieve system configuration {:?}", e);
|
||||
e
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Actually conduct a search request
|
||||
|
|
Loading…
Reference in a new issue