From 6cee6a800642b12838cc7b29bd6b2476409eb2f8 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 30 Oct 2026 13:24:42 +0100 Subject: [PATCH 01/12] add device-identity proto --- proto | 2 +- src/grpc.rs | 5 + src/lib.rs | 6 + src/tests/cookies.rs | 1 + src/tests/device_identity.rs | 221 +++++++++++++++++++++++++++++++++++ src/tests/mod.rs | 1 + 6 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 src/tests/device_identity.rs diff --git a/proto b/proto index 2a39d0a0..dd0b2456 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 2a39d0a09ebed4fe3ea08f1b54be5259ec63bed6 +Subproject commit dd0b2456aa9f57eabab626d799dd5052a9dd7420 diff --git a/src/grpc.rs b/src/grpc.rs index 21b7ba29..c298799f 100644 --- a/src/grpc.rs +++ b/src/grpc.rs @@ -723,6 +723,7 @@ mod tests { final_page_content: String::new(), instance: None, settings: None, + attestation_request: None, }) } @@ -760,6 +761,8 @@ mod tests { .send( core_request::Payload::EnrollmentStart(EnrollmentStartRequest { token: "enrollment-token".to_owned(), + proposed_wireguard_pubkey: None, + device_identity: None, }), test_device_info(), ) @@ -794,6 +797,8 @@ mod tests { let result = server.send( core_request::Payload::EnrollmentStart(EnrollmentStartRequest { token: "enrollment-token".to_owned(), + proposed_wireguard_pubkey: None, + device_identity: None, }), test_device_info(), ); diff --git a/src/lib.rs b/src/lib.rs index 5a64bef0..08b4f690 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,6 +41,12 @@ pub(crate) mod generated { } } + pub(crate) mod device_identity { + pub(crate) mod v1 { + tonic::include_proto!("defguard.device_identity.v1"); + } + } + pub mod client_types { tonic::include_proto!("defguard.client_types"); } diff --git a/src/tests/cookies.rs b/src/tests/cookies.rs index 1c8c46a8..b8e31f84 100644 --- a/src/tests/cookies.rs +++ b/src/tests/cookies.rs @@ -89,6 +89,7 @@ fn enrollment_response() -> core_response::Payload { final_page_content: String::new(), instance: None, settings: None, + attestation_request: None, }) } diff --git a/src/tests/device_identity.rs b/src/tests/device_identity.rs new file mode 100644 index 00000000..eb7e09ba --- /dev/null +++ b/src/tests/device_identity.rs @@ -0,0 +1,221 @@ +use axum::http::StatusCode; +use prost::Message; + +use super::support::{app_with_fake_core, post_json}; +use crate::{ + generated::defguard::device_identity::v1::{ + AppleAssertion, AppleAttestationIdentity, AppleEvidence, AttestationIdentity, + AttestationRequest, AttestationResponse, AttestationStatus, CredentialChallenge, + IdentityErrorCode, IdentityState, PossessionChallenge, PossessionResponse, Signature, + SignatureAlgorithm, TpmAttestationIdentity, TpmAttestationResponse, TpmEvidence, + attestation_identity, attestation_request, attestation_response, possession_response, + }, + proto::{ + DeviceConfigResponse, EnrollmentStartRequest, EnrollmentStartResponse, InstanceInfoRequest, + InstanceInfoResponse, NewDevice, core_request, core_response, enrollment_start_request, + }, +}; + +fn assert_wire_equal(actual: &T, expected: &T) { + assert_eq!(actual.encode_to_vec(), expected.encode_to_vec()); +} + +fn device_config() -> DeviceConfigResponse { + DeviceConfigResponse { + identity_state: Some(IdentityState { + identity_id: "identity".into(), + attestation_status: AttestationStatus::Attested as i32, + key_fingerprint: vec![0, 255, 1], + attested_at: Some(1_800_000_000), + last_possession_verified_at: Some(1_800_000_030), + ..Default::default() + }), + possession_challenge: Some(PossessionChallenge { + challenge_id: "next-challenge".into(), + data: vec![0, 255, 128, 1], + expires_at: 1_800_000_300, + }), + ..Default::default() + } +} + +#[tokio::test] +async fn test_device_identity_enrollment_start_forwarding() { + for backend in [ + attestation_identity::Backend::Tpm(TpmAttestationIdentity { + ek_public: vec![0, 255], + ek_certificate_chain_der: vec![vec![128, 1], vec![2, 0]], + ak_public: vec![3, 255], + }), + attestation_identity::Backend::AppleAppAttest(AppleAttestationIdentity { + key_id: "apple-key-id".into(), + }), + ] { + let challenge = match &backend { + attestation_identity::Backend::Tpm(_) => { + attestation_request::Challenge::Tpm(CredentialChallenge { + credential_blob: vec![0, 255], + encrypted_secret: vec![128, 1], + }) + } + attestation_identity::Backend::AppleAppAttest(_) => { + attestation_request::Challenge::AppleAppAttest(()) + } + }; + let request = EnrollmentStartRequest { + token: "enrollment-token".into(), + proposed_wireguard_pubkey: Some("wireguard-key".into()), + device_identity: Some( + enrollment_start_request::DeviceIdentity::AttestationIdentity( + AttestationIdentity { + backend: Some(backend), + }, + ), + ), + }; + let response = EnrollmentStartResponse { + deadline_timestamp: 4_000_000_000, + attestation_request: Some(AttestationRequest { + session_id: "attestation-session".into(), + data: vec![0, 255, 128, 1], + challenge: Some(challenge), + expires_at: 1_800_000_300, + }), + ..Default::default() + }; + let expected_request = request.clone(); + let expected_response = response.clone(); + let app = app_with_fake_core(move |payload| { + let core_request::Payload::EnrollmentStart(received) = payload else { + panic!("expected enrollment start"); + }; + assert_wire_equal(&received, &expected_request); + core_response::Payload::EnrollmentStart(expected_response.clone()) + }); + + let (status, body) = post_json(&app, "/api/v1/enrollment/start", &request).await; + assert_eq!(status, StatusCode::OK); + let received: EnrollmentStartResponse = serde_json::from_value(body).unwrap(); + assert_wire_equal(&received, &response); + } +} + +#[tokio::test] +async fn test_device_identity_create_device_forwarding() { + for result in [ + attestation_response::Result::Tpm(TpmAttestationResponse { + activation_secret: vec![0, 255, 128], + evidence: Some(TpmEvidence { + ek_public: vec![1, 255], + ek_certificate_chain_der: vec![vec![2, 128]], + ak_public: vec![3, 0], + identity_public: vec![4, 255], + attest: vec![5, 128], + signature: Some(Signature { + algorithm: SignatureAlgorithm::EcdsaSha256 as i32, + signature: vec![6, 0], + }), + }), + }), + attestation_response::Result::AppleAppAttest(AppleEvidence { + attestation_object: vec![0, 255, 128], + }), + attestation_response::Result::Error(IdentityErrorCode::Unavailable as i32), + ] { + let request = NewDevice { + name: "device".into(), + pubkey: "wireguard-key".into(), + token: None, + attestation_response: Some(AttestationResponse { + session_id: "attestation-session".into(), + result: Some(result), + }), + }; + let expected_request = request.clone(); + let response = device_config(); + let expected_response = response.clone(); + let app = app_with_fake_core(move |payload| { + let core_request::Payload::NewDevice(received) = payload else { + panic!("expected new device"); + }; + assert_wire_equal(&received, &expected_request); + core_response::Payload::DeviceConfig(expected_response.clone()) + }); + + let (status, body) = post_json(&app, "/api/v1/enrollment/create_device", &request).await; + assert_eq!(status, StatusCode::OK); + let received: DeviceConfigResponse = serde_json::from_value(body).unwrap(); + assert_wire_equal(&received, &response); + } +} + +#[tokio::test] +async fn test_device_identity_poll_forwarding_and_challenge_recovery() { + for proof in [ + Some(possession_response::Result::Signature(Signature { + algorithm: SignatureAlgorithm::EcdsaSha256 as i32, + signature: vec![0, 255, 128], + })), + Some(possession_response::Result::AppleAppAttest( + AppleAssertion { + assertion_object: vec![0, 255, 128], + }, + )), + Some(possession_response::Result::Error( + IdentityErrorCode::IdentityLost as i32, + )), + None, + ] { + let request = InstanceInfoRequest { + token: "polling-token".into(), + possession_response: proof.map(|result| PossessionResponse { + identity_id: "identity".into(), + challenge_id: "previous-challenge".into(), + result: Some(result), + }), + }; + let expected_request = request.clone(); + let response = InstanceInfoResponse { + device_config: Some(device_config()), + }; + let expected_response = response.clone(); + let app = app_with_fake_core(move |payload| { + let core_request::Payload::InstanceInfo(received) = payload else { + panic!("expected polling request"); + }; + assert_wire_equal(&received, &expected_request); + core_response::Payload::InstanceInfo(expected_response.clone()) + }); + + let (status, body) = post_json(&app, "/api/v1/poll", &request).await; + assert_eq!(status, StatusCode::OK); + let received: InstanceInfoResponse = serde_json::from_value(body).unwrap(); + assert_wire_equal(&received, &response); + } +} + +#[tokio::test] +async fn test_device_identity_enrollment_failure_forwarding() { + let request = EnrollmentStartRequest { + token: "enrollment-token".into(), + device_identity: Some(enrollment_start_request::DeviceIdentity::IdentityError( + IdentityErrorCode::Unavailable as i32, + )), + ..Default::default() + }; + let expected_request = request.clone(); + let app = app_with_fake_core(move |payload| { + let core_request::Payload::EnrollmentStart(received) = payload else { + panic!("expected enrollment start"); + }; + assert_wire_equal(&received, &expected_request); + core_response::Payload::EnrollmentStart(EnrollmentStartResponse { + deadline_timestamp: 4_000_000_000, + ..Default::default() + }) + }); + let (status, body) = post_json(&app, "/api/v1/enrollment/start", &request).await; + assert_eq!(status, StatusCode::OK); + let response: EnrollmentStartResponse = serde_json::from_value(body).unwrap(); + assert!(response.attestation_request.is_none()); +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index c45a6c1c..e84cede9 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,4 +1,5 @@ mod cookies; +mod device_identity; mod mfa_config; mod mfa_flow; mod mtls; From 0455a97f229f3bc8873402c3269275502f4c6482 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Nov 2026 10:09:00 +0100 Subject: [PATCH 02/12] update protos --- proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto b/proto index dd0b2456..2a278340 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit dd0b2456aa9f57eabab626d799dd5052a9dd7420 +Subproject commit 2a278340b9fdc9458784604edd5e45206c6a3b4a From c4304439c1d1c8cf3792ad1793292d6cc62e0511 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 2 Nov 2026 12:03:14 +0100 Subject: [PATCH 03/12] bump proto --- proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto b/proto index 2a278340..c9cd0c87 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 2a278340b9fdc9458784604edd5e45206c6a3b4a +Subproject commit c9cd0c87eee32d4e7c9bc8df4cbaeafcb239231b From 1d33cf6f6e461edd6c769edffc9ff771b63a0a68 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 3 Nov 2026 11:31:03 +0100 Subject: [PATCH 04/12] rename proposed_wireguard_key -> wireguard_key --- proto | 2 +- src/grpc.rs | 4 ++-- src/tests/device_identity.rs | 2 +- src/tests/mfa_flow.rs | 2 ++ 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/proto b/proto index c9cd0c87..587ab1e0 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit c9cd0c87eee32d4e7c9bc8df4cbaeafcb239231b +Subproject commit 587ab1e03c5d6410e6ecc5abc56379aeb167d4da diff --git a/src/grpc.rs b/src/grpc.rs index c298799f..579aedeb 100644 --- a/src/grpc.rs +++ b/src/grpc.rs @@ -761,7 +761,7 @@ mod tests { .send( core_request::Payload::EnrollmentStart(EnrollmentStartRequest { token: "enrollment-token".to_owned(), - proposed_wireguard_pubkey: None, + wireguard_key: None, device_identity: None, }), test_device_info(), @@ -797,7 +797,7 @@ mod tests { let result = server.send( core_request::Payload::EnrollmentStart(EnrollmentStartRequest { token: "enrollment-token".to_owned(), - proposed_wireguard_pubkey: None, + wireguard_key: None, device_identity: None, }), test_device_info(), diff --git a/src/tests/device_identity.rs b/src/tests/device_identity.rs index eb7e09ba..409be026 100644 --- a/src/tests/device_identity.rs +++ b/src/tests/device_identity.rs @@ -64,7 +64,7 @@ async fn test_device_identity_enrollment_start_forwarding() { }; let request = EnrollmentStartRequest { token: "enrollment-token".into(), - proposed_wireguard_pubkey: Some("wireguard-key".into()), + wireguard_key: Some("wireguard-key".into()), device_identity: Some( enrollment_start_request::DeviceIdentity::AttestationIdentity( AttestationIdentity { diff --git a/src/tests/mfa_flow.rs b/src/tests/mfa_flow.rs index d6722021..76923910 100644 --- a/src/tests/mfa_flow.rs +++ b/src/tests/mfa_flow.rs @@ -61,6 +61,7 @@ async fn test_start_dispatches_to_mfa_flow_start() { pubkey: "device-public-key".to_string(), posture_data: None, selected_methods: vec![MfaMethod::Totp as i32], + possession_response: None, }; let (status, body) = post_json(&app, "/api/v1/mfa-flow/start", &request).await; @@ -84,6 +85,7 @@ async fn test_start_maps_posture_rejection_to_forbidden() { pubkey: "device-public-key".to_string(), posture_data: None, selected_methods: vec![MfaMethod::Totp as i32], + possession_response: None, }; let (status, body) = post_json(&app, "/api/v1/mfa-flow/start", &request).await; From 2c12a34551e854926e84c9174e7d217d24c57eb0 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 6 Nov 2026 08:38:41 +0100 Subject: [PATCH 05/12] attestation failed status --- proto | 2 +- src/tests/device_identity.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/proto b/proto index 587ab1e0..7aae1df2 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 587ab1e03c5d6410e6ecc5abc56379aeb167d4da +Subproject commit 7aae1df2a7cdc86e207a2dd9fe26ef3263951d87 diff --git a/src/tests/device_identity.rs b/src/tests/device_identity.rs index 409be026..ff35f414 100644 --- a/src/tests/device_identity.rs +++ b/src/tests/device_identity.rs @@ -20,11 +20,11 @@ fn assert_wire_equal(actual: &T, expected: &T) { assert_eq!(actual.encode_to_vec(), expected.encode_to_vec()); } -fn device_config() -> DeviceConfigResponse { +fn device_config(attestation_status: AttestationStatus) -> DeviceConfigResponse { DeviceConfigResponse { identity_state: Some(IdentityState { identity_id: "identity".into(), - attestation_status: AttestationStatus::Attested as i32, + attestation_status: attestation_status as i32, key_fingerprint: vec![0, 255, 1], attested_at: Some(1_800_000_000), last_possession_verified_at: Some(1_800_000_030), @@ -132,7 +132,7 @@ async fn test_device_identity_create_device_forwarding() { }), }; let expected_request = request.clone(); - let response = device_config(); + let response = device_config(AttestationStatus::Attested); let expected_response = response.clone(); let app = app_with_fake_core(move |payload| { let core_request::Payload::NewDevice(received) = payload else { @@ -176,7 +176,7 @@ async fn test_device_identity_poll_forwarding_and_challenge_recovery() { }; let expected_request = request.clone(); let response = InstanceInfoResponse { - device_config: Some(device_config()), + device_config: Some(device_config(AttestationStatus::AttestationFailed)), }; let expected_response = response.clone(); let app = app_with_fake_core(move |payload| { From ab08e3ecbe175597ccba537bab9cdffb26856f21 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 6 Nov 2026 10:16:03 +0100 Subject: [PATCH 06/12] device identity proto package v2 --- proto | 2 +- src/lib.rs | 4 ++-- src/tests/device_identity.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/proto b/proto index 7aae1df2..19eac504 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 7aae1df2a7cdc86e207a2dd9fe26ef3263951d87 +Subproject commit 19eac50444ba63db186c390258eb47bd2fcd43ce diff --git a/src/lib.rs b/src/lib.rs index 08b4f690..f500f00a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,8 +42,8 @@ pub(crate) mod generated { } pub(crate) mod device_identity { - pub(crate) mod v1 { - tonic::include_proto!("defguard.device_identity.v1"); + pub(crate) mod v2 { + tonic::include_proto!("defguard.device_identity.v2"); } } diff --git a/src/tests/device_identity.rs b/src/tests/device_identity.rs index ff35f414..970836e0 100644 --- a/src/tests/device_identity.rs +++ b/src/tests/device_identity.rs @@ -3,7 +3,7 @@ use prost::Message; use super::support::{app_with_fake_core, post_json}; use crate::{ - generated::defguard::device_identity::v1::{ + generated::defguard::device_identity::v2::{ AppleAssertion, AppleAttestationIdentity, AppleEvidence, AttestationIdentity, AttestationRequest, AttestationResponse, AttestationStatus, CredentialChallenge, IdentityErrorCode, IdentityState, PossessionChallenge, PossessionResponse, Signature, From 4738b303e887cc5387dabaabb5c2ecfa88008eac Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 10 Nov 2026 12:27:51 +0100 Subject: [PATCH 07/12] update proto --- proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto b/proto index 19eac504..d56fe7ad 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 19eac50444ba63db186c390258eb47bd2fcd43ce +Subproject commit d56fe7adfd61a1e39a8fc95b7d912e43356063e9 From f1f171f957d882fa58bd57689a94e7a688e320ea Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 12 Nov 2026 09:39:06 +0100 Subject: [PATCH 08/12] rename PossessionResponse -> PossessionChallengeSolution --- src/tests/device_identity.rs | 23 +++++++++++++---------- src/tests/mfa_flow.rs | 4 ++-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/tests/device_identity.rs b/src/tests/device_identity.rs index 970836e0..181b9638 100644 --- a/src/tests/device_identity.rs +++ b/src/tests/device_identity.rs @@ -6,9 +6,10 @@ use crate::{ generated::defguard::device_identity::v2::{ AppleAssertion, AppleAttestationIdentity, AppleEvidence, AttestationIdentity, AttestationRequest, AttestationResponse, AttestationStatus, CredentialChallenge, - IdentityErrorCode, IdentityState, PossessionChallenge, PossessionResponse, Signature, - SignatureAlgorithm, TpmAttestationIdentity, TpmAttestationResponse, TpmEvidence, - attestation_identity, attestation_request, attestation_response, possession_response, + IdentityErrorCode, IdentityState, PossessionChallenge, PossessionChallengeSolution, + Signature, SignatureAlgorithm, TpmAttestationIdentity, TpmAttestationResponse, TpmEvidence, + attestation_identity, attestation_request, attestation_response, + possession_challenge_solution, }, proto::{ DeviceConfigResponse, EnrollmentStartRequest, EnrollmentStartResponse, InstanceInfoRequest, @@ -152,23 +153,25 @@ async fn test_device_identity_create_device_forwarding() { #[tokio::test] async fn test_device_identity_poll_forwarding_and_challenge_recovery() { for proof in [ - Some(possession_response::Result::Signature(Signature { - algorithm: SignatureAlgorithm::EcdsaSha256 as i32, - signature: vec![0, 255, 128], - })), - Some(possession_response::Result::AppleAppAttest( + Some(possession_challenge_solution::Result::Signature( + Signature { + algorithm: SignatureAlgorithm::EcdsaSha256 as i32, + signature: vec![0, 255, 128], + }, + )), + Some(possession_challenge_solution::Result::AppleAppAttest( AppleAssertion { assertion_object: vec![0, 255, 128], }, )), - Some(possession_response::Result::Error( + Some(possession_challenge_solution::Result::Error( IdentityErrorCode::IdentityLost as i32, )), None, ] { let request = InstanceInfoRequest { token: "polling-token".into(), - possession_response: proof.map(|result| PossessionResponse { + possession_challenge_solution: proof.map(|result| PossessionChallengeSolution { identity_id: "identity".into(), challenge_id: "previous-challenge".into(), result: Some(result), diff --git a/src/tests/mfa_flow.rs b/src/tests/mfa_flow.rs index 76923910..8f32bbb2 100644 --- a/src/tests/mfa_flow.rs +++ b/src/tests/mfa_flow.rs @@ -61,7 +61,7 @@ async fn test_start_dispatches_to_mfa_flow_start() { pubkey: "device-public-key".to_string(), posture_data: None, selected_methods: vec![MfaMethod::Totp as i32], - possession_response: None, + possession_challenge_solution: None, }; let (status, body) = post_json(&app, "/api/v1/mfa-flow/start", &request).await; @@ -85,7 +85,7 @@ async fn test_start_maps_posture_rejection_to_forbidden() { pubkey: "device-public-key".to_string(), posture_data: None, selected_methods: vec![MfaMethod::Totp as i32], - possession_response: None, + possession_challenge_solution: None, }; let (status, body) = post_json(&app, "/api/v1/mfa-flow/start", &request).await; From 6692b6ff5d1e440cd718b0a8f719322ffd75ec95 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 12 Nov 2026 11:53:33 +0100 Subject: [PATCH 09/12] new attestation protocol --- build.rs | 3 + proto | 2 +- src/grpc.rs | 5 - src/handlers/attestation.rs | 77 ++++++++++ src/handlers/enrollment.rs | 5 +- src/handlers/mod.rs | 1 + src/tests/cookies.rs | 115 +++++++++++++-- src/tests/device_identity.rs | 265 +++++++++++++++++++++++++++-------- 8 files changed, 395 insertions(+), 78 deletions(-) create mode 100644 src/handlers/attestation.rs diff --git a/build.rs b/build.rs index 6f59883d..776ed008 100644 --- a/build.rs +++ b/build.rs @@ -9,6 +9,9 @@ fn main() -> Result<(), Box> { // These types contain sensitive data. .skip_debug([ "ActivateUserRequest", + "AttestationFinishRequest", + "AttestationStartRequest", + "AttestationStartResponse", "AuthInfoResponse", "AuthenticateRequest", "AuthenticateResponse", diff --git a/proto b/proto index d56fe7ad..32c9ff97 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit d56fe7adfd61a1e39a8fc95b7d912e43356063e9 +Subproject commit 32c9ff97ebe9d26e795188e10fa97e9ba24b7993 diff --git a/src/grpc.rs b/src/grpc.rs index 579aedeb..21b7ba29 100644 --- a/src/grpc.rs +++ b/src/grpc.rs @@ -723,7 +723,6 @@ mod tests { final_page_content: String::new(), instance: None, settings: None, - attestation_request: None, }) } @@ -761,8 +760,6 @@ mod tests { .send( core_request::Payload::EnrollmentStart(EnrollmentStartRequest { token: "enrollment-token".to_owned(), - wireguard_key: None, - device_identity: None, }), test_device_info(), ) @@ -797,8 +794,6 @@ mod tests { let result = server.send( core_request::Payload::EnrollmentStart(EnrollmentStartRequest { token: "enrollment-token".to_owned(), - wireguard_key: None, - device_identity: None, }), test_device_info(), ); diff --git a/src/handlers/attestation.rs b/src/handlers/attestation.rs new file mode 100644 index 00000000..acc442e7 --- /dev/null +++ b/src/handlers/attestation.rs @@ -0,0 +1,77 @@ +use axum::{Json, Router, extract::State, routing::post}; +use axum_extra::extract::PrivateCookieJar; +use serde::Deserialize; + +use crate::{ + error::ApiError, + generated::defguard::device_identity::v2::AttestationChallengeSolution, + handlers::{enrollment_token, get_core_response}, + http::AppState, + proto::{ + AttestationFinishRequest, AttestationFinishResponse, AttestationStartRequest, + AttestationStartResponse, DeviceInfo, attestation_start_request, core_request, + core_response, + }, +}; + +pub(crate) fn router() -> Router { + Router::new() + .route("/start", post(start)) + .route("/finish", post(finish)) +} + +#[derive(Deserialize)] +struct AttestationStartBody { + device_id: i64, + device_identity: Option, +} + +#[derive(Deserialize)] +struct AttestationFinishBody { + device_id: i64, + solution: Option, +} + +#[instrument(level = "debug", skip(state, cookie_jar, req))] +async fn start( + State(state): State, + device_info: DeviceInfo, + cookie_jar: PrivateCookieJar, + Json(req): Json, +) -> Result, ApiError> { + let token = enrollment_token(&cookie_jar)?; + let rx = state.grpc_server.send( + core_request::Payload::AttestationStart(AttestationStartRequest { + token, + device_id: req.device_id, + device_identity: req.device_identity, + }), + device_info, + )?; + match get_core_response(rx, None).await? { + core_response::Payload::AttestationStart(response) => Ok(Json(response)), + _ => Err(ApiError::InvalidResponseType), + } +} + +#[instrument(level = "debug", skip(state, cookie_jar, req))] +async fn finish( + State(state): State, + device_info: DeviceInfo, + cookie_jar: PrivateCookieJar, + Json(req): Json, +) -> Result, ApiError> { + let token = enrollment_token(&cookie_jar)?; + let rx = state.grpc_server.send( + core_request::Payload::AttestationFinish(AttestationFinishRequest { + token, + device_id: req.device_id, + solution: req.solution, + }), + device_info, + )?; + match get_core_response(rx, None).await? { + core_response::Payload::AttestationFinish(response) => Ok(Json(response)), + _ => Err(ApiError::InvalidResponseType), + } +} diff --git a/src/handlers/enrollment.rs b/src/handlers/enrollment.rs index 3c5788c7..2d047536 100644 --- a/src/handlers/enrollment.rs +++ b/src/handlers/enrollment.rs @@ -2,7 +2,9 @@ use axum::{Json, Router, extract::State, routing::post}; use axum_extra::extract::PrivateCookieJar; use time::OffsetDateTime; -use super::register_mfa::router as register_mfa_router; +use super::{ + attestation::router as attestation_router, register_mfa::router as register_mfa_router, +}; use crate::{ error::ApiError, handlers::{get_core_response, mobile_client::register_mobile_auth}, @@ -19,6 +21,7 @@ use crate::{ pub(crate) fn router() -> Router { Router::new() .nest("/register-mfa", register_mfa_router()) + .nest("/attestation", attestation_router()) .route("/start", post(start_enrollment_process)) .route("/activate_user", post(activate_user)) .route("/create_device", post(create_device)) diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 4e9c5700..444b022a 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -19,6 +19,7 @@ use crate::{ proto::core_response::Payload, }; +mod attestation; pub(crate) mod desktop_client_mfa; pub(crate) mod enrollment; pub(crate) mod mfa_config; diff --git a/src/tests/cookies.rs b/src/tests/cookies.rs index b8e31f84..87d37499 100644 --- a/src/tests/cookies.rs +++ b/src/tests/cookies.rs @@ -13,7 +13,8 @@ use crate::{ grpc::ProxyServer, http::{AppState, ENROLLMENT_COOKIE_NAME, PASSWORD_RESET_COOKIE_NAME, build_router}, proto::{ - AuthInfoResponse, CoreRequest, EnrollmentStartResponse, OpenIdProviderKind, + AttestationFinishResponse, AttestationStartResponse, AuthInfoResponse, CoreRequest, Device, + DeviceConfigResponse, EnrollmentStartResponse, OpenIdProviderKind, PasswordResetStartResponse, core_response, }, tests::support::{cookie_key, test_proxy_server, test_public_settings}, @@ -89,7 +90,6 @@ fn enrollment_response() -> core_response::Payload { final_page_content: String::new(), instance: None, settings: None, - attestation_request: None, }) } @@ -328,33 +328,120 @@ async fn test_oidc_cookies_remain_secure_over_http() { } #[tokio::test] -async fn test_enrollment_cookie_removal_attributes_follow_core_public_url() { +async fn test_enrollment_cookie_is_kept_for_attestation_and_removed_after_activation() { for (public_url, secure) in [ ("https://proxy.example.com", true), ("http://proxy.example.com", false), ] { - let cookies = request_cookie_removal( - public_url, + let TestApp { + app, + mut core_requests, + response_server, + } = test_app(Some(public_url)); + let start = Step::new( + "/api/v1/enrollment/start", + r#"{"token":"test-token"}"#, + enrollment_response(), + ); + let response = request_with_core_response( + &app, + &mut core_requests, + &response_server, + start.request(None), + start.payload, + ) + .await; + let cookies = parse_set_cookies(&response); + assert_eq!(cookies.len(), 1); + assert_cookie( + &cookies[0], + ENROLLMENT_COOKIE_NAME, + "/api/v1/enrollment", + secure, + ); + assert_eq!( + cookies[0].expires_datetime().unwrap().unix_timestamp(), + 4_000_000_000 + ); + + for step in [ Step::new( - "/api/v1/enrollment/start", - r#"{"token":"test-token"}"#, - enrollment_response(), + "/api/v1/enrollment/create_device", + r#"{"name":"device","pubkey":"wireguard-key"}"#, + core_response::Payload::DeviceConfig(DeviceConfigResponse { + device: Some(Device { + id: 7, + ..Default::default() + }), + ..Default::default() + }), ), Step::new( - "/api/v1/enrollment/activate_user", - r#"{"password":"test-password"}"#, - core_response::Payload::Empty(()), + "/api/v1/enrollment/attestation/start", + r#"{"device_id":7}"#, + core_response::Payload::AttestationStart(AttestationStartResponse::default()), ), + Step::new( + "/api/v1/enrollment/attestation/finish", + r#"{"device_id":7}"#, + core_response::Payload::AttestationFinish(AttestationFinishResponse::default()), + ), + ] { + let response = tokio::time::timeout( + std::time::Duration::from_secs(10), + request_with_core_response( + &app, + &mut core_requests, + &response_server, + step.request(Some(&cookies)), + step.payload, + ), + ) + .await + .expect("enrollment step did not reach Core"); + assert!( + parse_set_cookies(&response).is_empty(), + "session cookie must not be removed or refreshed" + ); + } + let activate = Step::new( + "/api/v1/enrollment/activate_user", + r#"{"password":"test-password"}"#, + core_response::Payload::Empty(()), + ); + let response = request_with_core_response( + &app, + &mut core_requests, + &response_server, + activate.request(Some(&cookies)), + activate.payload, ) .await; - - assert_eq!(cookies.len(), 1); + let removed = parse_set_cookies(&response); + assert_eq!(removed.len(), 1); assert_removed_cookie( - &cookies[0], + &removed[0], ENROLLMENT_COOKIE_NAME, "/api/v1/enrollment", secure, ); + + for path in [ + "/api/v1/enrollment/attestation/start", + "/api/v1/enrollment/attestation/finish", + ] { + let request = Step::new( + path, + r#"{"device_id":7}"#, + core_response::Payload::Empty(()), + ); + let response = app.clone().oneshot(request.request(None)).await.unwrap(); + assert_eq!(response.status(), StatusCode::UNAUTHORIZED); + } + assert!(matches!( + core_requests.try_recv(), + Err(mpsc::error::TryRecvError::Empty) + )); } } diff --git a/src/tests/device_identity.rs b/src/tests/device_identity.rs index 181b9638..7bc62b1c 100644 --- a/src/tests/device_identity.rs +++ b/src/tests/device_identity.rs @@ -1,19 +1,29 @@ -use axum::http::StatusCode; +use axum::{ + Router, + body::{Body, to_bytes}, + http::{Request, StatusCode, header}, +}; use prost::Message; +use serde::Serialize; +use serde_json::{Value, json}; +use tower::ServiceExt; use super::support::{app_with_fake_core, post_json}; use crate::{ generated::defguard::device_identity::v2::{ - AppleAssertion, AppleAttestationIdentity, AppleEvidence, AttestationIdentity, - AttestationRequest, AttestationResponse, AttestationStatus, CredentialChallenge, + AppleAssertion, AppleAttestationIdentity, AppleEvidence, AttestationChallenge, + AttestationChallengeSolution, AttestationIdentity, AttestationStatus, CredentialChallenge, IdentityErrorCode, IdentityState, PossessionChallenge, PossessionChallengeSolution, Signature, SignatureAlgorithm, TpmAttestationIdentity, TpmAttestationResponse, TpmEvidence, - attestation_identity, attestation_request, attestation_response, + attestation_challenge, attestation_challenge_solution, attestation_identity, possession_challenge_solution, }, + http::ENROLLMENT_COOKIE_NAME, proto::{ - DeviceConfigResponse, EnrollmentStartRequest, EnrollmentStartResponse, InstanceInfoRequest, - InstanceInfoResponse, NewDevice, core_request, core_response, enrollment_start_request, + AttestationFinishRequest, AttestationFinishResponse, AttestationStartRequest, + AttestationStartResponse, CoreError, DeviceConfigResponse, EnrollmentStartResponse, + InstanceInfoRequest, InstanceInfoResponse, attestation_start_request, core_request, + core_response, }, }; @@ -40,8 +50,73 @@ fn device_config(attestation_status: AttestationStatus) -> DeviceConfigResponse } } +fn app_with_enrollment(core: F) -> Router +where + F: Fn(core_request::Payload) -> core_response::Payload + Send + 'static, +{ + app_with_fake_core(move |payload| match payload { + core_request::Payload::EnrollmentStart(request) => { + assert_eq!(request.token, "enrollment-token"); + core_response::Payload::EnrollmentStart(EnrollmentStartResponse { + deadline_timestamp: 4_000_000_000, + ..Default::default() + }) + } + payload => core(payload), + }) +} + +async fn enrollment_cookie(app: &Router) -> String { + let response = app + .clone() + .oneshot( + Request::builder() + .method("POST") + .uri("/api/v1/enrollment/start") + .header(header::CONTENT_TYPE, "application/json") + .header("X-Forwarded-For", "10.0.0.1") + .body(Body::from(r#"{"token":"enrollment-token"}"#)) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::OK); + response.headers()[header::SET_COOKIE] + .to_str() + .unwrap() + .split(';') + .next() + .unwrap() + .into() +} + +async fn post_with_cookie( + app: &Router, + path: &str, + cookie: &str, + body: &T, +) -> (StatusCode, Value) { + let response = app + .clone() + .oneshot( + Request::builder() + .method("POST") + .uri(path) + .header(header::CONTENT_TYPE, "application/json") + .header(header::COOKIE, cookie) + .header("X-Forwarded-For", "10.0.0.1") + .body(Body::from(serde_json::to_vec(body).unwrap())) + .unwrap(), + ) + .await + .unwrap(); + let status = response.status(); + let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap(); + (status, serde_json::from_slice(&bytes).unwrap()) +} + #[tokio::test] -async fn test_device_identity_enrollment_start_forwarding() { +async fn test_device_identity_attestation_start_forwarding() { for backend in [ attestation_identity::Backend::Tpm(TpmAttestationIdentity { ek_public: vec![0, 255], @@ -54,57 +129,63 @@ async fn test_device_identity_enrollment_start_forwarding() { ] { let challenge = match &backend { attestation_identity::Backend::Tpm(_) => { - attestation_request::Challenge::Tpm(CredentialChallenge { + attestation_challenge::Challenge::Tpm(CredentialChallenge { credential_blob: vec![0, 255], encrypted_secret: vec![128, 1], }) } attestation_identity::Backend::AppleAppAttest(_) => { - attestation_request::Challenge::AppleAppAttest(()) + attestation_challenge::Challenge::AppleAppAttest(()) } }; - let request = EnrollmentStartRequest { - token: "enrollment-token".into(), - wireguard_key: Some("wireguard-key".into()), + let request = AttestationStartRequest { + token: "body-token-must-be-ignored".into(), + device_id: 7, device_identity: Some( - enrollment_start_request::DeviceIdentity::AttestationIdentity( + attestation_start_request::DeviceIdentity::AttestationIdentity( AttestationIdentity { backend: Some(backend), }, ), ), }; - let response = EnrollmentStartResponse { - deadline_timestamp: 4_000_000_000, - attestation_request: Some(AttestationRequest { + let response = AttestationStartResponse { + challenge: Some(AttestationChallenge { session_id: "attestation-session".into(), data: vec![0, 255, 128, 1], challenge: Some(challenge), expires_at: 1_800_000_300, }), - ..Default::default() }; - let expected_request = request.clone(); + let mut expected_request = request.clone(); + expected_request.token = "enrollment-token".into(); let expected_response = response.clone(); - let app = app_with_fake_core(move |payload| { - let core_request::Payload::EnrollmentStart(received) = payload else { - panic!("expected enrollment start"); + let app = app_with_enrollment(move |payload| { + let core_request::Payload::AttestationStart(received) = payload else { + panic!("expected attestation start"); }; assert_wire_equal(&received, &expected_request); - core_response::Payload::EnrollmentStart(expected_response.clone()) + core_response::Payload::AttestationStart(expected_response.clone()) }); - let (status, body) = post_json(&app, "/api/v1/enrollment/start", &request).await; + let cookie = enrollment_cookie(&app).await; + let (status, body) = post_with_cookie( + &app, + "/api/v1/enrollment/attestation/start", + &cookie, + &request, + ) + .await; assert_eq!(status, StatusCode::OK); - let received: EnrollmentStartResponse = serde_json::from_value(body).unwrap(); + let received: AttestationStartResponse = serde_json::from_value(body).unwrap(); assert_wire_equal(&received, &response); } } #[tokio::test] -async fn test_device_identity_create_device_forwarding() { +async fn test_device_identity_attestation_finish_forwarding() { for result in [ - attestation_response::Result::Tpm(TpmAttestationResponse { + attestation_challenge_solution::Result::Tpm(TpmAttestationResponse { activation_secret: vec![0, 255, 128], evidence: Some(TpmEvidence { ek_public: vec![1, 255], @@ -118,34 +199,48 @@ async fn test_device_identity_create_device_forwarding() { }), }), }), - attestation_response::Result::AppleAppAttest(AppleEvidence { + attestation_challenge_solution::Result::AppleAppAttest(AppleEvidence { attestation_object: vec![0, 255, 128], }), - attestation_response::Result::Error(IdentityErrorCode::Unavailable as i32), + attestation_challenge_solution::Result::Error(IdentityErrorCode::Unavailable as i32), ] { - let request = NewDevice { - name: "device".into(), - pubkey: "wireguard-key".into(), - token: None, - attestation_response: Some(AttestationResponse { + let status = if matches!(&result, attestation_challenge_solution::Result::Error(_)) { + AttestationStatus::AttestationFailed + } else { + AttestationStatus::Attested + }; + let request = AttestationFinishRequest { + token: "body-token-must-be-ignored".into(), + device_id: 7, + solution: Some(AttestationChallengeSolution { session_id: "attestation-session".into(), result: Some(result), }), }; - let expected_request = request.clone(); - let response = device_config(AttestationStatus::Attested); + let mut expected_request = request.clone(); + expected_request.token = "enrollment-token".into(); + let response = AttestationFinishResponse { + identity_state: device_config(status).identity_state, + }; let expected_response = response.clone(); - let app = app_with_fake_core(move |payload| { - let core_request::Payload::NewDevice(received) = payload else { - panic!("expected new device"); + let app = app_with_enrollment(move |payload| { + let core_request::Payload::AttestationFinish(received) = payload else { + panic!("expected attestation finish"); }; assert_wire_equal(&received, &expected_request); - core_response::Payload::DeviceConfig(expected_response.clone()) + core_response::Payload::AttestationFinish(expected_response.clone()) }); - let (status, body) = post_json(&app, "/api/v1/enrollment/create_device", &request).await; + let cookie = enrollment_cookie(&app).await; + let (status, body) = post_with_cookie( + &app, + "/api/v1/enrollment/attestation/finish", + &cookie, + &request, + ) + .await; assert_eq!(status, StatusCode::OK); - let received: DeviceConfigResponse = serde_json::from_value(body).unwrap(); + let received: AttestationFinishResponse = serde_json::from_value(body).unwrap(); assert_wire_equal(&received, &response); } } @@ -198,27 +293,83 @@ async fn test_device_identity_poll_forwarding_and_challenge_recovery() { } #[tokio::test] -async fn test_device_identity_enrollment_failure_forwarding() { - let request = EnrollmentStartRequest { - token: "enrollment-token".into(), - device_identity: Some(enrollment_start_request::DeviceIdentity::IdentityError( +async fn test_device_identity_failure_reporting_uses_attestation_start() { + let request = AttestationStartRequest { + token: "ignored-body-token".into(), + device_id: 7, + device_identity: Some(attestation_start_request::DeviceIdentity::IdentityError( IdentityErrorCode::Unavailable as i32, )), - ..Default::default() }; - let expected_request = request.clone(); - let app = app_with_fake_core(move |payload| { - let core_request::Payload::EnrollmentStart(received) = payload else { - panic!("expected enrollment start"); + let mut expected = request.clone(); + expected.token = "enrollment-token".into(); + let app = app_with_enrollment(move |payload| { + let core_request::Payload::AttestationStart(received) = payload else { + panic!("expected attestation start") }; - assert_wire_equal(&received, &expected_request); - core_response::Payload::EnrollmentStart(EnrollmentStartResponse { - deadline_timestamp: 4_000_000_000, + assert_wire_equal(&received, &expected); + core_response::Payload::CoreError(CoreError { + status_code: tonic::Code::Unimplemented.into(), + message: "attestation is not implemented".into(), ..Default::default() }) }); - let (status, body) = post_json(&app, "/api/v1/enrollment/start", &request).await; - assert_eq!(status, StatusCode::OK); - let response: EnrollmentStartResponse = serde_json::from_value(body).unwrap(); - assert!(response.attestation_request.is_none()); + let cookie = enrollment_cookie(&app).await; + let (status, _) = post_with_cookie( + &app, + "/api/v1/enrollment/attestation/start", + &cookie, + &request, + ) + .await; + assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR); +} + +#[tokio::test] +async fn test_attestation_requires_a_private_enrollment_cookie() { + let app = app_with_fake_core(|_| panic!("unauthenticated request reached Core")); + for path in [ + "/api/v1/enrollment/attestation/start", + "/api/v1/enrollment/attestation/finish", + ] { + let body = json!({"device_id": 7, "token": "body-token"}); + let (status, _) = post_json(&app, path, &body).await; + assert_eq!(status, StatusCode::UNAUTHORIZED); + let (status, _) = post_with_cookie( + &app, + path, + &format!("{ENROLLMENT_COOKIE_NAME}=unencrypted-token"), + &body, + ) + .await; + assert_eq!(status, StatusCode::UNAUTHORIZED); + } +} + +#[tokio::test] +async fn test_attestation_forwards_core_denials_and_rejects_unexpected_responses() { + for (payload, expected_status) in [ + ( + core_response::Payload::CoreError(CoreError { + status_code: tonic::Code::PermissionDenied.into(), + message: "device belongs to another user".into(), + ..Default::default() + }), + StatusCode::FORBIDDEN, + ), + ( + core_response::Payload::Empty(()), + StatusCode::INTERNAL_SERVER_ERROR, + ), + ] { + let app = app_with_enrollment(move |_| payload.clone()); + let cookie = enrollment_cookie(&app).await; + for path in [ + "/api/v1/enrollment/attestation/start", + "/api/v1/enrollment/attestation/finish", + ] { + let (status, _) = post_with_cookie(&app, path, &cookie, &json!({"device_id":7})).await; + assert_eq!(status, expected_status); + } + } } From 92a069450755e3850239d6c86ff91361e687a0b6 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Thu, 12 Nov 2026 11:55:39 +0100 Subject: [PATCH 10/12] bump proto --- proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto b/proto index 32c9ff97..ca5cc6bf 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 32c9ff97ebe9d26e795188e10fa97e9ba24b7993 +Subproject commit ca5cc6bf2068ed2bf376887b41c9429299b1b4a2 From 20585235898e4d832c0878f9b70285d5462e066a Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 13 Nov 2026 09:41:46 +0100 Subject: [PATCH 11/12] proto before monitor --- proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto b/proto index ca5cc6bf..32c9ff97 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit ca5cc6bf2068ed2bf376887b41c9429299b1b4a2 +Subproject commit 32c9ff97ebe9d26e795188e10fa97e9ba24b7993 From 90a63e9507b3c6e9960e0c0d22611723b3a0208e Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 13 Nov 2026 11:21:49 +0100 Subject: [PATCH 12/12] bump proto --- proto | 2 +- src/handlers/attestation.rs | 9 ++++--- src/tests/device_identity.rs | 46 ++++-------------------------------- 3 files changed, 9 insertions(+), 48 deletions(-) diff --git a/proto b/proto index 32c9ff97..b55df6ae 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 32c9ff97ebe9d26e795188e10fa97e9ba24b7993 +Subproject commit b55df6aefb8b1cb192c7dd1cceefb52d07b00bc9 diff --git a/src/handlers/attestation.rs b/src/handlers/attestation.rs index acc442e7..89450b26 100644 --- a/src/handlers/attestation.rs +++ b/src/handlers/attestation.rs @@ -4,13 +4,12 @@ use serde::Deserialize; use crate::{ error::ApiError, - generated::defguard::device_identity::v2::AttestationChallengeSolution, + generated::defguard::device_identity::v2::{AttestationChallengeSolution, AttestationIdentity}, handlers::{enrollment_token, get_core_response}, http::AppState, proto::{ AttestationFinishRequest, AttestationFinishResponse, AttestationStartRequest, - AttestationStartResponse, DeviceInfo, attestation_start_request, core_request, - core_response, + AttestationStartResponse, DeviceInfo, core_request, core_response, }, }; @@ -23,7 +22,7 @@ pub(crate) fn router() -> Router { #[derive(Deserialize)] struct AttestationStartBody { device_id: i64, - device_identity: Option, + attestation_identity: Option, } #[derive(Deserialize)] @@ -44,7 +43,7 @@ async fn start( core_request::Payload::AttestationStart(AttestationStartRequest { token, device_id: req.device_id, - device_identity: req.device_identity, + attestation_identity: req.attestation_identity, }), device_info, )?; diff --git a/src/tests/device_identity.rs b/src/tests/device_identity.rs index 7bc62b1c..ff75003d 100644 --- a/src/tests/device_identity.rs +++ b/src/tests/device_identity.rs @@ -22,8 +22,7 @@ use crate::{ proto::{ AttestationFinishRequest, AttestationFinishResponse, AttestationStartRequest, AttestationStartResponse, CoreError, DeviceConfigResponse, EnrollmentStartResponse, - InstanceInfoRequest, InstanceInfoResponse, attestation_start_request, core_request, - core_response, + InstanceInfoRequest, InstanceInfoResponse, core_request, core_response, }, }; @@ -141,13 +140,9 @@ async fn test_device_identity_attestation_start_forwarding() { let request = AttestationStartRequest { token: "body-token-must-be-ignored".into(), device_id: 7, - device_identity: Some( - attestation_start_request::DeviceIdentity::AttestationIdentity( - AttestationIdentity { - backend: Some(backend), - }, - ), - ), + attestation_identity: Some(AttestationIdentity { + backend: Some(backend), + }), }; let response = AttestationStartResponse { challenge: Some(AttestationChallenge { @@ -292,39 +287,6 @@ async fn test_device_identity_poll_forwarding_and_challenge_recovery() { } } -#[tokio::test] -async fn test_device_identity_failure_reporting_uses_attestation_start() { - let request = AttestationStartRequest { - token: "ignored-body-token".into(), - device_id: 7, - device_identity: Some(attestation_start_request::DeviceIdentity::IdentityError( - IdentityErrorCode::Unavailable as i32, - )), - }; - let mut expected = request.clone(); - expected.token = "enrollment-token".into(); - let app = app_with_enrollment(move |payload| { - let core_request::Payload::AttestationStart(received) = payload else { - panic!("expected attestation start") - }; - assert_wire_equal(&received, &expected); - core_response::Payload::CoreError(CoreError { - status_code: tonic::Code::Unimplemented.into(), - message: "attestation is not implemented".into(), - ..Default::default() - }) - }); - let cookie = enrollment_cookie(&app).await; - let (status, _) = post_with_cookie( - &app, - "/api/v1/enrollment/attestation/start", - &cookie, - &request, - ) - .await; - assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR); -} - #[tokio::test] async fn test_attestation_requires_a_private_enrollment_cookie() { let app = app_with_fake_core(|_| panic!("unauthenticated request reached Core"));