diff --git a/Cargo.toml b/Cargo.toml index 9011747d..5a2f50f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.6.6" +version = "0.6.7" authors = [ "David Mulder " ] @@ -76,7 +76,7 @@ tracing-forest = "^0.1.6" rusqlite = "^0.32.0" hashbrown = { version = "0.14.0", features = ["serde", "inline-more", "ahash"] } lru = "^0.12.3" -kanidm_lib_crypto = { path = "./src/crypto", version = "0.6.6" } +kanidm_lib_crypto = { path = "./src/crypto", version = "0.6.7" } kanidm_utils_users = { path = "./src/users" } walkdir = "2" csv = "1.2.2" diff --git a/src/cli/src/main.rs b/src/cli/src/main.rs index e8b7ff96..531d50d0 100644 --- a/src/cli/src/main.rs +++ b/src/cli/src/main.rs @@ -31,7 +31,7 @@ use std::time::Duration; include!("./opt/tool.rs"); macro_rules! match_sm_auth_client_response { - ($expr:expr, $opts:ident, $($pat:pat => $result:expr),*) => { + ($expr:expr, $req:ident, $($pat:pat => $result:expr),*) => { match $expr { Ok(r) => match r { $($pat => $result),* @@ -47,6 +47,56 @@ macro_rules! match_sm_auth_client_response { println!("auth user unknown"); break; } + ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::SetupPin { + msg, + }) => { + println!("{}", msg); + + let mut pin; + let mut confirm; + loop { + pin = match prompt_password("New PIN: ") { + Ok(password) => password, + Err(err) => { + println!("unable to get pin: {:?}", err); + return ExitCode::FAILURE; + } + }; + + confirm = match prompt_password("Confirm PIN: ") { + Ok(password) => password, + Err(err) => { + println!("unable to get confirmation pin: {:?}", err); + return ExitCode::FAILURE; + } + }; + + if pin == confirm { + break; + } else { + println!("Inputs did not match. Try again."); + } + } + + // Now setup the request for the next loop. + $req = ClientRequest::PamAuthenticateStep(PamAuthRequest::SetupPin { + pin, + }); + continue; + }, + ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Pin) => { + let cred = match prompt_password("PIN: ") { + Ok(password) => password, + Err(err) => { + debug!("unable to get pin: {:?}", err); + return ExitCode::FAILURE; + } + }; + + // Now setup the request for the next loop. + $req = ClientRequest::PamAuthenticateStep(PamAuthRequest::Pin { cred }); + continue; + } _ => { // unexpected response. error!("Error: unexpected response -> {:?}", r); @@ -107,7 +157,7 @@ async fn main() -> ExitCode { let mut req = ClientRequest::PamAuthenticateInit(account_id.clone()); loop { - match_sm_auth_client_response!(daemon_client.call_and_wait(&req, timeout), opts, + match_sm_auth_client_response!(daemon_client.call_and_wait(&req, timeout), req, ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Password) => { // Prompt for and get the password let cred = match prompt_password("Password: ") { @@ -123,22 +173,6 @@ async fn main() -> ExitCode { req = ClientRequest::PamAuthenticateStep(PamAuthRequest::Password { cred }); continue; }, - ClientResponse::PamAuthenticateStepResponse( - PamAuthResponse::DeviceAuthorizationGrant { data }, - ) => { - let msg = match &data.message { - Some(msg) => msg.clone(), - None => format!("Using a browser on another device, visit:\n{}\nAnd enter the code:\n{}", - data.verification_uri, data.user_code) - }; - println!("{}", msg); - - timeout = u64::from(data.expires_in); - req = ClientRequest::PamAuthenticateStep( - PamAuthRequest::DeviceAuthorizationGrant { data }, - ); - continue; - }, ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::MFACode { msg, }) => { @@ -166,80 +200,29 @@ async fn main() -> ExitCode { // Prompt the MFA message println!("{}", msg); + let mut poll_attempt = 0; + req = ClientRequest::PamAuthenticateStep(PamAuthRequest::MFAPoll { poll_attempt }); loop { thread::sleep(Duration::from_secs(polling_interval.into())); - timeout = cfg.get_unix_sock_timeout(); - req = ClientRequest::PamAuthenticateStep(PamAuthRequest::MFAPoll); // Counter intuitive, but we don't need a max poll attempts here because // if the resolver goes away, then this will error on the sock and // will shutdown. This allows the resolver to dynamically extend the // timeout if needed, and removes logic from the front end. match_sm_auth_client_response!( - daemon_client.call_and_wait(&req, timeout), opts, + daemon_client.call_and_wait(&req, timeout), req, ClientResponse::PamAuthenticateStepResponse( PamAuthResponse::MFAPollWait, ) => { // Continue polling if the daemon says to wait + poll_attempt += 1; + req = ClientRequest::PamAuthenticateStep( + PamAuthRequest::MFAPoll { poll_attempt } + ); continue; } ); - } - }, - ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::SetupPin { - msg, - }) => { - // Prompt for a new Hello PIN - println!("{}", msg); - - let mut pin; - let mut confirm; - loop { - pin = match prompt_password("New PIN: ") { - Ok(p) => p, - Err(e) => { - error!("Problem getting input: {}", e); - return ExitCode::FAILURE; - } - }; - - confirm = match prompt_password("Confirm PIN: ") { - Ok(p) => p, - Err(e) => { - error!("Problem getting input: {}", e); - return ExitCode::FAILURE; - } - }; - - if pin == confirm { - break; - } else { - println!("Inputs did not match. Try again."); - } - } - - // Now setup the request for the next loop. - timeout = cfg.get_unix_sock_timeout(); - req = ClientRequest::PamAuthenticateStep(PamAuthRequest::SetupPin { - pin, - }); - continue; - }, - ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::Pin) => { - // Prompt for and get the Hello PIN - let cred = match prompt_password("PIN: ") { - Ok(p) => p, - Err(e) => { - error!("Problem getting input: {}", e); - return ExitCode::FAILURE; - } - }; - - // Now setup the request for the next loop. - timeout = cfg.get_unix_sock_timeout(); - req = ClientRequest::PamAuthenticateStep(PamAuthRequest::Pin { cred }); - continue; } ); } diff --git a/src/common/src/idprovider/himmelblau.rs b/src/common/src/idprovider/himmelblau.rs index a4a007bc..65a503fd 100644 --- a/src/common/src/idprovider/himmelblau.rs +++ b/src/common/src/idprovider/himmelblau.rs @@ -7,14 +7,10 @@ use crate::config::HimmelblauConfig; use crate::config::IdAttr; use crate::db::KeyStoreTxn; use crate::idprovider::interface::tpm; -use crate::unix_proto::{DeviceAuthorizationResponse, PamAuthRequest}; +use crate::unix_proto::PamAuthRequest; use anyhow::{anyhow, Result}; use async_trait::async_trait; -use himmelblau::auth::{ - BrokerClientApplication, ClientInfo, - DeviceAuthorizationResponse as msal_DeviceAuthorizationResponse, IdToken, MFAAuthContinue, - UserToken as UnixUserToken, -}; +use himmelblau::auth::{BrokerClientApplication, UserToken as UnixUserToken}; use himmelblau::discovery::EnrollAttrs; use himmelblau::error::{ErrorResponse, MsalError, AUTH_PENDING, DEVICE_AUTH_FAIL, REQUIRES_MFA}; use himmelblau::graph::{DirectoryObject, Graph}; @@ -361,34 +357,6 @@ impl HimmelblauProvider { } } -impl From for DeviceAuthorizationResponse { - fn from(src: msal_DeviceAuthorizationResponse) -> Self { - Self { - device_code: src.device_code.clone(), - user_code: src.user_code.clone(), - verification_uri: src.verification_uri.clone(), - verification_uri_complete: src.verification_uri_complete.clone(), - expires_in: src.expires_in, - interval: src.interval, - message: src.message.clone(), - } - } -} - -impl From for msal_DeviceAuthorizationResponse { - fn from(src: DeviceAuthorizationResponse) -> Self { - Self { - device_code: src.device_code, - user_code: src.user_code, - verification_uri: src.verification_uri, - verification_uri_complete: src.verification_uri_complete, - expires_in: src.expires_in, - interval: src.interval, - message: src.message, - } - } -} - #[async_trait] impl IdProvider for HimmelblauProvider { async fn provider_authenticate(&self, _tpm: &mut tpm::BoxedDynTpm) -> Result<(), IdpError> { @@ -540,9 +508,9 @@ impl IdProvider for HimmelblauProvider { // Skip Hello authentication if it is disabled by config let hello_enabled = self.config.read().await.get_enable_hello(); if !self.is_domain_joined(keystore).await || hello_key.is_none() || !hello_enabled { - Ok((AuthRequest::Password, AuthCredHandler::Password)) + Ok((AuthRequest::Password, AuthCredHandler::None)) } else { - Ok((AuthRequest::Pin, AuthCredHandler::Pin)) + Ok((AuthRequest::Pin, AuthCredHandler::None)) } } @@ -554,7 +522,7 @@ impl IdProvider for HimmelblauProvider { keystore: &mut D, tpm: &mut tpm::BoxedDynTpm, machine_key: &tpm::MachineKey, - shutdown_rx: &broadcast::Receiver<()>, + _shutdown_rx: &broadcast::Receiver<()>, ) -> Result<(AuthResult, AuthCacheAction), IdpError> { macro_rules! enroll_and_obtain_enrolled_token { ($token:ident) => {{ @@ -660,31 +628,15 @@ impl IdProvider for HimmelblauProvider { } }}; } - let mut shutdown_rx_cl = shutdown_rx.resubscribe(); - match (&cred_handler, pam_next_req) { - (AuthCredHandler::MFA { data }, PamAuthRequest::SetupPin { pin }) => { + match (&mut *cred_handler, pam_next_req) { + (AuthCredHandler::SetupPin { token }, PamAuthRequest::SetupPin { pin }) => { let hello_tag = self.fetch_hello_key_tag(account_id); - let token: Token = serde_json::from_str(data).map_err(|e| { - error!("{:?}", e); - IdpError::BadRequest - })?; - let token = UnixUserToken { - token_type: "".to_string(), - scope: None, - expires_in: 0, - ext_expires_in: 0, - access_token: token.0, - refresh_token: token.1, - id_token: IdToken::default(), - client_info: ClientInfo::default(), - prt: None, - }; let hello_key = match self .client .write() .await - .provision_hello_for_business_key(&token, tpm, machine_key, &pin) + .provision_hello_for_business_key(token, tpm, machine_key, &pin) .await { Ok(hello_key) => hello_key, @@ -709,7 +661,7 @@ impl IdProvider for HimmelblauProvider { auth_and_validate_hello_key!(hello_key, pin) } - (AuthCredHandler::Pin, PamAuthRequest::Pin { cred }) => { + (_, PamAuthRequest::Pin { cred }) => { let hello_tag = self.fetch_hello_key_tag(account_id); let hello_key = keystore .get_tagged_hsm_key(&hello_tag) @@ -724,7 +676,7 @@ impl IdProvider for HimmelblauProvider { auth_and_validate_hello_key!(hello_key, cred) } - (AuthCredHandler::Password, PamAuthRequest::Password { cred }) => { + (_, PamAuthRequest::Password { cred }) => { // Always attempt to force MFA when enrolling the device, otherwise // the device object will not have the MFA claim. If we are already // enrolled but creating a new Hello Pin, we follow the same process, @@ -792,12 +744,24 @@ impl IdProvider for HimmelblauProvider { error!("{:?}", e); IdpError::BadRequest })?; - return Ok(( - AuthResult::Next( - AuthRequest::DeviceAuthorizationGrant { - data: resp.into(), - }, + let msg = match &resp.message { + Some(msg) => msg.to_string(), + None => format!( + "Using a browser on another \ + device, visit:\n{}\nAnd enter the code:\n{}", + resp.verification_uri, resp.user_code ), + }; + let polling_interval = resp.interval.unwrap_or(5); + *cred_handler = + AuthCredHandler::DeviceAuthorizationGrant { + flow: resp, + }; + return Ok(( + AuthResult::Next(AuthRequest::MFAPoll { + msg, + polling_interval, + }), /* An MFA auth cannot cache the password. This would * lead to a potential downgrade to SFA attack (where * the attacker auths with a stolen password, then @@ -830,12 +794,7 @@ impl IdProvider for HimmelblauProvider { match resp.mfa_method.as_str() { "PhoneAppOTP" | "OneWaySMS" | "ConsolidatedTelephony" => { let msg = resp.msg.clone(); - *cred_handler = AuthCredHandler::MFA { - data: serde_json::to_string(&resp).map_err(|e| { - error!("{:?}", e); - IdpError::BadRequest - })?, - }; + *cred_handler = AuthCredHandler::MFA { flow: resp }; return Ok(( AuthResult::Next(AuthRequest::MFACode { msg }), /* An MFA auth cannot cache the password. This would @@ -851,12 +810,7 @@ impl IdProvider for HimmelblauProvider { error!("Invalid response from the server"); IdpError::BadRequest })?; - *cred_handler = AuthCredHandler::MFA { - data: serde_json::to_string(&resp).map_err(|e| { - error!("{:?}", e); - IdpError::BadRequest - })?, - }; + *cred_handler = AuthCredHandler::MFA { flow: resp }; return Ok(( AuthResult::Next(AuthRequest::MFAPoll { msg, @@ -873,35 +827,42 @@ impl IdProvider for HimmelblauProvider { } } } - (_, PamAuthRequest::DeviceAuthorizationGrant { data }) => { - let sleep_interval: u64 = match data.interval.as_ref() { - Some(val) => *val as u64, - None => 5, - }; - let mut mtoken = self + ( + AuthCredHandler::DeviceAuthorizationGrant { flow }, + PamAuthRequest::MFAPoll { poll_attempt }, + ) => { + let polling_interval = flow.interval.unwrap_or(5); + // Convert `expires_in` (a lifetime in seconds) to max_poll_attempts + let max_poll_attempts = flow.expires_in / polling_interval; + if poll_attempt > max_poll_attempts { + error!("MFA DAG polling timed out"); + return Err(IdpError::BadRequest); + } + let token = match self .client .write() .await - .acquire_token_by_device_flow(data.clone().into()) - .await; - while let Err(MsalError::AcquireTokenFailed(ref resp)) = mtoken { - if resp.error_codes.contains(&AUTH_PENDING) { - debug!("Polling for acquire_token_by_device_flow"); - sleep(Duration::from_secs(sleep_interval)); - mtoken = self - .client - .write() - .await - .acquire_token_by_device_flow(data.clone().into()) - .await; - } else { - break; + .acquire_token_by_device_flow(flow.clone()) + .await + { + Err(MsalError::AcquireTokenFailed(ref resp)) => { + if resp.error_codes.contains(&AUTH_PENDING) { + debug!("Polling for acquire_token_by_device_flow"); + return Ok(( + AuthResult::Next(AuthRequest::MFAPollWait), + AuthCacheAction::None, + )); + } else { + error!("{}", resp.error_description); + return Err(IdpError::BadRequest); + } } - } - let token = mtoken.map_err(|e| { - error!("{:?}", e); - IdpError::NotFound - })?; + Err(e) => { + error!("{:?}", e); + return Err(IdpError::BadRequest); + } + Ok(token) => token, + }; let token2 = enroll_and_obtain_enrolled_token!(token); match self.token_validate(account_id, &token2).await { Ok(AuthResult::Success { token: token3 }) => { @@ -914,7 +875,10 @@ impl IdProvider for HimmelblauProvider { // attempt here. let sfa_enabled = self.config.read().await.get_enable_sfa_fallback(); if !mfa && !sfa_enabled { - info!("A DAG produced an SFA token, yet SFA fallback is disabled by configuration"); + info!( + "A DAG produced an SFA token, yet SFA \ + fallback is disabled by configuration" + ); return Ok((AuthResult::Denied, AuthCacheAction::None)); } // STOP! If the DAG doesn't hold an MFA amr, then we @@ -924,9 +888,15 @@ impl IdProvider for HimmelblauProvider { let hello_enabled = self.config.read().await.get_enable_hello(); if !mfa || !hello_enabled { if !mfa { - info!("Skipping Hello enrollment because the token doesn't contain an MFA amr"); + info!( + "Skipping Hello enrollment because \ + the token doesn't contain an MFA amr" + ); } else if !hello_enabled { - info!("Skipping Hello enrollment because it is disabled"); + info!( + "Skipping Hello enrollment \ + because it is disabled" + ); } return Ok(( AuthResult::Success { token: token3 }, @@ -935,16 +905,7 @@ impl IdProvider for HimmelblauProvider { } // Setup Windows Hello - *cred_handler = AuthCredHandler::MFA { - data: serde_json::to_string(&Token( - token.access_token.clone(), - token.refresh_token.to_string(), - )) - .map_err(|e| { - error!("{:?}", e); - IdpError::BadRequest - })?, - }; + *cred_handler = AuthCredHandler::SetupPin { token }; return Ok(( AuthResult::Next(AuthRequest::SetupPin { msg: format!( @@ -960,16 +921,12 @@ impl IdProvider for HimmelblauProvider { Err(e) => Err(e), } } - (AuthCredHandler::MFA { data }, PamAuthRequest::MFACode { cred }) => { - let mut flow: MFAAuthContinue = serde_json::from_str(data).map_err(|e| { - error!("{:?}", e); - IdpError::BadRequest - })?; + (AuthCredHandler::MFA { ref mut flow }, PamAuthRequest::MFACode { cred }) => { let token = self .client .write() .await - .acquire_token_by_mfa_flow(account_id, Some(&cred), None, &mut flow) + .acquire_token_by_mfa_flow(account_id, Some(&cred), None, flow) .await .map_err(|e| { error!("{:?}", e); @@ -989,16 +946,7 @@ impl IdProvider for HimmelblauProvider { } // Setup Windows Hello - *cred_handler = AuthCredHandler::MFA { - data: serde_json::to_string(&Token( - token.access_token.clone(), - token.refresh_token.to_string(), - )) - .map_err(|e| { - error!("{:?}", e); - IdpError::BadRequest - })?, - }; + *cred_handler = AuthCredHandler::SetupPin { token }; return Ok(( AuthResult::Next(AuthRequest::SetupPin { msg: format!( @@ -1014,49 +962,35 @@ impl IdProvider for HimmelblauProvider { Err(e) => Err(e), } } - (AuthCredHandler::MFA { data }, PamAuthRequest::MFAPoll) => { - let mut flow: MFAAuthContinue = serde_json::from_str(data).map_err(|e| { - error!("{:?}", e); - IdpError::BadRequest - })?; + (AuthCredHandler::MFA { ref mut flow }, PamAuthRequest::MFAPoll { poll_attempt }) => { let max_poll_attempts = flow.max_poll_attempts.ok_or_else(|| { error!("Invalid response from the server"); IdpError::BadRequest })?; - let polling_interval = flow.polling_interval.ok_or_else(|| { - error!("Invalid response from the server"); - IdpError::BadRequest - })?; - let mut poll_attempt = 1; - let token = loop { - if poll_attempt > max_poll_attempts { - error!("MFA polling timed out"); - return Err(IdpError::BadRequest); - } - if shutdown_rx_cl.try_recv().ok().is_some() { - debug!("Received a signal to shutdown, bailing MFA poll"); - return Err(IdpError::BadRequest); - } - sleep(Duration::from_millis(polling_interval.into())); - match self - .client - .write() - .await - .acquire_token_by_mfa_flow(account_id, None, Some(poll_attempt), &mut flow) - .await - { - Ok(token) => break token, - Err(e) => match e { - MsalError::MFAPollContinue => { - poll_attempt += 1; - continue; - } - e => { - error!("{:?}", e); - return Err(IdpError::NotFound); - } - }, - } + if poll_attempt > max_poll_attempts { + error!("MFA polling timed out"); + return Err(IdpError::BadRequest); + } + let token = match self + .client + .write() + .await + .acquire_token_by_mfa_flow(account_id, None, Some(poll_attempt), flow) + .await + { + Ok(token) => token, + Err(e) => match e { + MsalError::MFAPollContinue => { + return Ok(( + AuthResult::Next(AuthRequest::MFAPollWait), + AuthCacheAction::None, + )); + } + e => { + error!("{:?}", e); + return Err(IdpError::NotFound); + } + }, }; let token2 = enroll_and_obtain_enrolled_token!(token); match self.token_validate(account_id, &token2).await { @@ -1072,16 +1006,7 @@ impl IdProvider for HimmelblauProvider { } // Setup Windows Hello - *cred_handler = AuthCredHandler::MFA { - data: serde_json::to_string(&Token( - token.access_token.clone(), - token.refresh_token.to_string(), - )) - .map_err(|e| { - error!("{:?}", e); - IdpError::BadRequest - })?, - }; + *cred_handler = AuthCredHandler::SetupPin { token }; return Ok(( AuthResult::Next(AuthRequest::SetupPin { msg: format!( @@ -1117,9 +1042,9 @@ impl IdProvider for HimmelblauProvider { IdpError::BadRequest })?; if !self.is_domain_joined(keystore).await || hello_key.is_none() { - Ok((AuthRequest::Password, AuthCredHandler::Password)) + Ok((AuthRequest::Password, AuthCredHandler::None)) } else { - Ok((AuthRequest::Pin, AuthCredHandler::Pin)) + Ok((AuthRequest::Pin, AuthCredHandler::None)) } } @@ -1135,7 +1060,7 @@ impl IdProvider for HimmelblauProvider { _online_at_init: bool, ) -> Result { match (&cred_handler, pam_next_req) { - (AuthCredHandler::Pin, PamAuthRequest::Pin { cred }) => { + (_, PamAuthRequest::Pin { cred }) => { let hello_tag = self.fetch_hello_key_tag(account_id); let hello_key: LoadableIdentityKey = keystore .get_tagged_hsm_key(&hello_tag) diff --git a/src/common/src/idprovider/interface.rs b/src/common/src/idprovider/interface.rs index c7f461a5..0c6bee48 100644 --- a/src/common/src/idprovider/interface.rs +++ b/src/common/src/idprovider/interface.rs @@ -5,9 +5,11 @@ */ use crate::db::KeyStoreTxn; -use crate::unix_proto::{DeviceAuthorizationResponse, PamAuthRequest, PamAuthResponse}; +use crate::unix_proto::{PamAuthRequest, PamAuthResponse}; use async_trait::async_trait; +use himmelblau::{DeviceAuthorizationResponse, MFAAuthContinue, UserToken as UnixUserToken}; use serde::{Deserialize, Serialize}; +use std::fmt; use tokio::sync::broadcast; use uuid::Uuid; @@ -66,31 +68,28 @@ pub struct UserToken { pub valid: bool, } -#[derive(Debug)] pub enum AuthCredHandler { - Password, - DeviceAuthorizationGrant, - /// Additional data required by the provider to complete the - /// authentication, but not required by PAM - /// - /// Sadly due to how this is passed around we can't make this a - /// generic associated type, else it would have to leak up to the - /// daemon. - /// - /// ⚠️ TODO: Optimally this should actually be a tokio oneshot receiver - /// with the decision from a task that is spawned. - MFA { - data: String, - }, - SetupPin, - Pin, + MFA { flow: MFAAuthContinue }, + DeviceAuthorizationGrant { flow: DeviceAuthorizationResponse }, + SetupPin { token: UnixUserToken }, + None, +} + +impl fmt::Debug for AuthCredHandler { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AuthCredHandler::MFA { .. } => f.write_str("MFA { .. }"), + AuthCredHandler::DeviceAuthorizationGrant { .. } => { + f.write_str("DeviceAuthorizationGrant { .. }") + } + AuthCredHandler::SetupPin { .. } => f.write_str("SetupPin { .. }"), + AuthCredHandler::None => f.write_str("None"), + } + } } pub enum AuthRequest { Password, - DeviceAuthorizationGrant { - data: DeviceAuthorizationResponse, - }, MFACode { msg: String, }, @@ -113,9 +112,6 @@ impl Into for AuthRequest { fn into(self) -> PamAuthResponse { match self { AuthRequest::Password => PamAuthResponse::Password, - AuthRequest::DeviceAuthorizationGrant { data } => { - PamAuthResponse::DeviceAuthorizationGrant { data } - } AuthRequest::MFACode { msg } => PamAuthResponse::MFACode { msg }, AuthRequest::MFAPoll { msg, diff --git a/src/common/src/resolver.rs b/src/common/src/resolver.rs index f738207a..e1469223 100644 --- a/src/common/src/resolver.rs +++ b/src/common/src/resolver.rs @@ -46,6 +46,7 @@ enum CacheState { OfflineNextCheck(SystemTime), } +#[allow(clippy::large_enum_variant)] pub enum AuthSession { InProgress { account_id: String, @@ -1042,7 +1043,7 @@ where // contained to the resolver so that it has generic offline-paths // that are possible? match (&cred_handler, &pam_next_req) { - (AuthCredHandler::Password, PamAuthRequest::Password { cred }) => { + (_, PamAuthRequest::Password { cred }) => { match self.check_cache_userpassword(token.uuid, cred).await { Ok(true) => Ok(AuthResult::Success { token: *token.clone(), @@ -1054,11 +1055,7 @@ where } } } - (AuthCredHandler::Password, _) => { - // AuthCredHandler::Password is only valid with a cred provided - return Err(()); - } - (AuthCredHandler::DeviceAuthorizationGrant, _) => { + (AuthCredHandler::DeviceAuthorizationGrant { .. }, _) => { // AuthCredHandler::DeviceAuthorizationGrant is invalid for offline auth return Err(()); } @@ -1066,11 +1063,11 @@ where // AuthCredHandler::MFA is invalid for offline auth return Err(()); } - (AuthCredHandler::SetupPin, _) => { + (AuthCredHandler::SetupPin { .. }, _) => { // AuthCredHandler::SetupPin is invalid for offline auth return Err(()); } - (AuthCredHandler::Pin, PamAuthRequest::Pin { .. }) => { + (_, PamAuthRequest::Pin { .. }) => { // The Pin acts as a single device password, and can be // used to unlock the TPM to validate the authentication. let mut hsm_lock = self.hsm.lock().await; @@ -1095,8 +1092,16 @@ where auth_result } - (AuthCredHandler::Pin, _) => { - // AuthCredHandler::Pin is only valid with a cred provided + (AuthCredHandler::None, PamAuthRequest::MFACode { .. }) => { + // AuthCredHandler::None is invalid with MFACode + return Err(()); + } + (AuthCredHandler::None, PamAuthRequest::MFAPoll { .. }) => { + // AuthCredHandler::None is invalid with MFAPoll + return Err(()); + } + (AuthCredHandler::None, PamAuthRequest::SetupPin { .. }) => { + // AuthCredHandler::None is invalid with SetupPin return Err(()); } } @@ -1162,10 +1167,6 @@ where // Can continue! auth_session } - (auth_session, PamAuthResponse::DeviceAuthorizationGrant { .. }) => { - // Can continue! - auth_session - } (auth_session, PamAuthResponse::MFACode { .. }) => { // Can continue! auth_session diff --git a/src/common/src/unix_proto.rs b/src/common/src/unix_proto.rs index 2042a2e6..e19c1b68 100644 --- a/src/common/src/unix_proto.rs +++ b/src/common/src/unix_proto.rs @@ -22,29 +22,12 @@ pub struct NssGroup { pub members: Vec, } -/* RFC8628: 3.2. Device Authorization Response */ -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct DeviceAuthorizationResponse { - pub device_code: String, - pub user_code: String, - pub verification_uri: String, - pub verification_uri_complete: Option, - pub expires_in: u32, - pub interval: Option, - /* The message is not part of RFC8628, but an add-on from MS. Listed - * optional here to support all implementations. */ - pub message: Option, -} - #[derive(Serialize, Deserialize, Debug)] pub enum PamAuthResponse { Unknown, Success, Denied, Password, - DeviceAuthorizationGrant { - data: DeviceAuthorizationResponse, - }, /// PAM must prompt for an authentication code MFACode { msg: String, @@ -68,9 +51,8 @@ pub enum PamAuthResponse { #[derive(Serialize, Deserialize, Debug)] pub enum PamAuthRequest { Password { cred: String }, - DeviceAuthorizationGrant { data: DeviceAuthorizationResponse }, MFACode { cred: String }, - MFAPoll, + MFAPoll { poll_attempt: u32 }, SetupPin { pin: String }, Pin { cred: String }, } diff --git a/src/pam/Cargo.toml b/src/pam/Cargo.toml index 39762eca..446ca277 100644 --- a/src/pam/Cargo.toml +++ b/src/pam/Cargo.toml @@ -19,6 +19,7 @@ libc = { workspace = true } kanidm_unix_common = { workspace = true } tracing-subscriber = { workspace = true } tracing = { workspace = true } +himmelblau_unix_common.workspace = true [build-dependencies] pkg-config.workspace = true diff --git a/src/pam/src/pam/mod.rs b/src/pam/src/pam/mod.rs index 0458254a..b3e127e6 100755 --- a/src/pam/src/pam/mod.rs +++ b/src/pam/src/pam/mod.rs @@ -403,30 +403,6 @@ impl PamHooks for PamKanidm { req = ClientRequest::PamAuthenticateStep(PamAuthRequest::Password { cred }); continue; }, - ClientResponse::PamAuthenticateStepResponse( - PamAuthResponse::DeviceAuthorizationGrant { data }, - ) => { - let msg = match &data.message { - Some(msg) => msg.clone(), - None => format!("Using a browser on another device, visit:\n{}\nAnd enter the code:\n{}", - data.verification_uri, data.user_code) - }; - match conv.send(PAM_TEXT_INFO, &msg) { - Ok(_) => {} - Err(err) => { - if opts.debug { - println!("Message prompt failed"); - } - return err; - } - } - - timeout = u64::from(data.expires_in); - req = ClientRequest::PamAuthenticateStep( - PamAuthRequest::DeviceAuthorizationGrant { data }, - ); - continue; - }, ClientResponse::PamAuthenticateStepResponse(PamAuthResponse::MFACode { msg, }) => { @@ -482,7 +458,8 @@ impl PamHooks for PamKanidm { let _ = conv.send(PAM_PROMPT_ECHO_OFF, "Press enter to continue"); } - req = ClientRequest::PamAuthenticateStep(PamAuthRequest::MFAPoll); + let mut poll_attempt = 0; + req = ClientRequest::PamAuthenticateStep(PamAuthRequest::MFAPoll { poll_attempt }); loop { thread::sleep(Duration::from_secs(polling_interval.into())); @@ -496,11 +473,13 @@ impl PamHooks for PamKanidm { PamAuthResponse::MFAPollWait, ) => { // Continue polling if the daemon says to wait - req = ClientRequest::PamAuthenticateStep(PamAuthRequest::MFAPoll); + poll_attempt += 1; + req = ClientRequest::PamAuthenticateStep( + PamAuthRequest::MFAPoll { poll_attempt } + ); continue; } ); - } } );