From 7db78ea88658d8c24c83fa780ee80bb8617ddea3 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 26 Oct 2026 07:59:05 +0100 Subject: [PATCH 1/5] handle outdated mobile app during remote approve --- .../src/grpc/proxy/client_mfa.rs | 35 +++++++++++-------- .../src/tests/proxy_manager/handler/mfa.rs | 25 ++++++++----- proto | 2 +- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/crates/defguard_core/src/grpc/proxy/client_mfa.rs b/crates/defguard_core/src/grpc/proxy/client_mfa.rs index 8f6201c06e..d7b6f6e7ea 100644 --- a/crates/defguard_core/src/grpc/proxy/client_mfa.rs +++ b/crates/defguard_core/src/grpc/proxy/client_mfa.rs @@ -27,12 +27,13 @@ use defguard_proto::{ ClientMfaFinishRequest, ClientMfaFinishResponse, ClientMfaStartRequest, ClientMfaStartResponse, MfaAdvanced, MfaAwaitingExternal, MfaBiometricSignature, MfaCodeCredential, MfaCompleted, MfaFido2Assertion, MfaFido2Challenge, - MfaFlowApproveRequest, MfaFlowRemoteRequest, MfaFlowRemoteResponse, MfaFlowStartAccepted, - MfaFlowStartRejected, MfaFlowStartRequest, MfaFlowStartResponse, MfaFlowStepFinishRequest, - MfaFlowStepFinishResponse, MfaFlowStepStartRequest, MfaFlowStepStartResponse, MfaMethod, - MfaSignatureChallenge, MfaStartRejectionReason, MfaStepRejection, MfaStepResult, - MfaStepStarted, mfa_flow_start_response, mfa_flow_step_finish_request, mfa_step_result, - mfa_step_started, + MfaFlowApproveRequest, MfaFlowRemoteRejected, MfaFlowRemoteRequest, MfaFlowRemoteResponse, + MfaFlowStartAccepted, MfaFlowStartRejected, MfaFlowStartRequest, MfaFlowStartResponse, + MfaFlowStepFinishRequest, MfaFlowStepFinishResponse, MfaFlowStepStartRequest, + MfaFlowStepStartResponse, MfaMethod, MfaRemoteRejectionReason, MfaSignatureChallenge, + MfaStartRejectionReason, MfaStepRejection, MfaStepResult, MfaStepStarted, + mfa_flow_remote_response, mfa_flow_start_response, mfa_flow_step_finish_request, + mfa_step_result, mfa_step_started, }, enterprise::posture::{DevicePostureCheckRequest, DevicePostureData}, proxy::{ @@ -1065,14 +1066,18 @@ impl ClientMfaServer { .await } Ok(Ok(RemoteAuthSignal::MobileClientOutdated)) => { - remove_waiter_and_send_error( - &waiters, - &hash, - &waiter_identity, - &response_tx, - request_id, - Status::failed_precondition(LEGACY_MOBILE_CLIENT_MESSAGE), - ); + remove_remote_mfa_waiter(&waiters, &hash, &waiter_identity); + let _ = response_tx.send(CoreResponse { + id: request_id, + payload: Some(Payload::MfaFlowRemote(MfaFlowRemoteResponse { + outcome: Some(mfa_flow_remote_response::Outcome::Rejected( + MfaFlowRemoteRejected { + reason: MfaRemoteRejectionReason::MfaRemoteRejectionMobileClientOutdated + .into(), + }, + )), + })), + }); return; } Ok(Ok(RemoteAuthSignal::Superseded)) => { @@ -1122,7 +1127,7 @@ impl ClientMfaServer { Payload::CoreError(Status::internal("mobile approval did not complete").into()) } Ok(outcome) => Payload::MfaFlowRemote(MfaFlowRemoteResponse { - result: Some(outcome.into()), + outcome: Some(mfa_flow_remote_response::Outcome::Result(outcome.into())), }), Err(error) => Payload::CoreError(Status::from(error).into()), }; diff --git a/crates/defguard_proxy_manager/src/tests/proxy_manager/handler/mfa.rs b/crates/defguard_proxy_manager/src/tests/proxy_manager/handler/mfa.rs index f7508d7683..aeaa283bea 100644 --- a/crates/defguard_proxy_manager/src/tests/proxy_manager/handler/mfa.rs +++ b/crates/defguard_proxy_manager/src/tests/proxy_manager/handler/mfa.rs @@ -19,8 +19,9 @@ use defguard_proto::{ client_types::{ ClientMfaFinishRequest, ClientMfaStartRequest, MfaBiometricSignature, MfaCodeCredential, MfaFlowApproveRequest, MfaFlowRemoteRequest, MfaFlowStartRequest, MfaFlowStepFinishRequest, - MfaFlowStepStartRequest, MfaMethod, MfaMobileApprovalProof, MfaStepResult, - mfa_flow_start_response, mfa_flow_step_finish_request, mfa_step_result, mfa_step_started, + MfaFlowStepStartRequest, MfaMethod, MfaMobileApprovalProof, MfaRemoteRejectionReason, + MfaStepResult, mfa_flow_remote_response, mfa_flow_start_response, + mfa_flow_step_finish_request, mfa_step_result, mfa_step_started, }, proxy::{CoreRequest, CoreResponse, core_request, core_response}, }; @@ -257,9 +258,10 @@ fn flow_step_result(response: CoreResponse) -> MfaStepResult { fn flow_remote_result(response: CoreResponse) -> MfaStepResult { match response.payload { - Some(core_response::Payload::MfaFlowRemote(response)) => response - .result - .expect("remote finish must include a result"), + Some(core_response::Payload::MfaFlowRemote(response)) => match response.outcome { + Some(mfa_flow_remote_response::Outcome::Result(result)) => result, + _ => panic!("remote finish must include a result"), + }, Some(core_response::Payload::CoreError(error)) => panic!( "remote finish failed with status={} msg={}", error.status_code, error.message @@ -686,10 +688,17 @@ async fn test_legacy_mfa_finish_rejects_valid_multi_step_mobile_proof( assert_eq!(remote.id, 2); assert_eq!(phone.id, 3); - let (remote_code, remote_message) = assert_error_response_details(&remote); + let Some(core_response::Payload::MfaFlowRemote(response)) = remote.payload else { + panic!("expected MfaFlowRemote rejection"); + }; + let Some(mfa_flow_remote_response::Outcome::Rejected(rejected)) = response.outcome else { + panic!("expected outdated mobile client rejection"); + }; + assert_eq!( + rejected.reason(), + MfaRemoteRejectionReason::MfaRemoteRejectionMobileClientOutdated + ); let (phone_code, phone_message) = assert_error_response_details(&phone); - assert_eq!(remote_code, Code::FailedPrecondition); - assert_eq!(remote_message, LEGACY_MOBILE_CLIENT_MESSAGE); assert_eq!(phone_code, Code::FailedPrecondition); assert_eq!(phone_message, LEGACY_MOBILE_CLIENT_MESSAGE); diff --git a/proto b/proto index 5a66c4e4c4..4645455e6e 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 5a66c4e4c40f223e4702eca87b811a12967a93e3 +Subproject commit 4645455e6e5ea1ed4347fa064303ad9a35f00b61 From 0aa535b322806c159629441c366f7d7d7bc75303 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 26 Oct 2026 08:15:41 +0100 Subject: [PATCH 2/5] fix tests --- .../src/grpc/proxy/client_mfa/tests.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/defguard_core/src/grpc/proxy/client_mfa/tests.rs b/crates/defguard_core/src/grpc/proxy/client_mfa/tests.rs index 70554b8788..bc2e155b6b 100644 --- a/crates/defguard_core/src/grpc/proxy/client_mfa/tests.rs +++ b/crates/defguard_core/src/grpc/proxy/client_mfa/tests.rs @@ -36,7 +36,8 @@ use defguard_proto::{ ClientMfaFinishRequest, ClientMfaStartRequest, MfaCodeCredential, MfaFlowApproveRequest, MfaFlowRemoteRequest, MfaFlowStartRequest, MfaFlowStepFinishRequest, MfaFlowStepStartRequest, MfaMethod, MfaMobileApprovalProof, MfaStartRejectionReason, - mfa_flow_start_response, mfa_flow_step_finish_request, mfa_step_result, mfa_step_started, + mfa_flow_remote_response, mfa_flow_start_response, mfa_flow_step_finish_request, + mfa_step_result, mfa_step_started, }, enterprise::posture::{BoolCheck, DevicePostureCheckRequest, DevicePostureData, bool_check}, proxy::{ClientMfaOidcAuthenticateRequest, ClientMfaTokenValidationRequest, DeviceInfo}, @@ -2077,9 +2078,9 @@ async fn test_mfa_flow_remote_wakes_matching_attempt_and_delivers_psk( let Some(super::Payload::MfaFlowRemote(response)) = response.payload else { panic!("expected a typed remote flow response"); }; - let result = response - .result - .expect("remote response must include a result"); + let Some(mfa_flow_remote_response::Outcome::Result(result)) = response.outcome else { + panic!("remote response must include a result"); + }; let Some(mfa_step_result::Outcome::Completed(completed)) = result.outcome else { panic!("expected a completed remote result"); }; @@ -2208,11 +2209,10 @@ async fn test_mfa_flow_remote_observes_approval_before_registration( let Some(super::Payload::MfaFlowRemote(response)) = response.payload else { panic!("expected a typed remote flow response"); }; - let Some(mfa_step_result::Outcome::Completed(completed)) = response - .result - .expect("remote response must include a result") - .outcome - else { + let Some(mfa_flow_remote_response::Outcome::Result(result)) = response.outcome else { + panic!("remote response must include a result"); + }; + let Some(mfa_step_result::Outcome::Completed(completed)) = result.outcome else { panic!("expected a completed remote result"); }; assert_ne!(completed.preshared_key, ""); From c0ce188ed87cdba9afdab57301013a270d285e56 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 26 Oct 2026 08:24:13 +0100 Subject: [PATCH 3/5] bump ctutils --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52f6168d19..4aa403123f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1434,9 +1434,9 @@ dependencies = [ [[package]] name = "ctutils" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03bb0e1cc970d482d121d9a1744999169b69a07470b3d644a7894e53fcaf4574" +checksum = "fe3e9d21d39e6f39ff46e03d5b621424f88ee23d8a1672c6bfc9ccc7f2da4f6a" dependencies = [ "cmov", ] From 8e34b5b095493f3bdb3fb0ad70bfec3de921a063 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 26 Oct 2026 08:24:22 +0100 Subject: [PATCH 4/5] ignore crypto-bigint CVE (transitive dependency of jsonwebtoken) --- deny.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/deny.toml b/deny.toml index ade2619bad..8d1eca3c69 100644 --- a/deny.toml +++ b/deny.toml @@ -75,6 +75,7 @@ ignore = [ { id = "RUSTSEC-2026-0367", reason = "zip <9 is a build dependency of utoipa-swagger-ui (latest still uses zip 8), only extracts the trusted vendored Swagger UI archive" }, { id = "RUSTSEC-2026-0368", reason = "zip <9 is a build dependency of utoipa-swagger-ui (latest still uses zip 8), only extracts the trusted vendored Swagger UI archive" }, { id = "RUSTSEC-2026-0369", reason = "zip <9 is a build dependency of utoipa-swagger-ui (latest still uses zip 8), only extracts the trusted vendored Swagger UI archive" }, + { id = "RUSTSEC-2026-0387", reason = "Transitive dependency of jsonwebtoken" }, ] # If this is true, then cargo deny will use the git executable to fetch advisory database. # If this is false, then it uses a built-in git library. From 1d19203ca5d93441f62f43e7bc1bd546183eaa9d Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 26 Oct 2026 08:49:52 +0100 Subject: [PATCH 5/5] proto from dev --- proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto b/proto index 4645455e6e..2a39d0a09e 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 4645455e6e5ea1ed4347fa064303ad9a35f00b61 +Subproject commit 2a39d0a09ebed4fe3ea08f1b54be5259ec63bed6