Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion proto
5 changes: 5 additions & 0 deletions src/grpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,7 @@ mod tests {
final_page_content: String::new(),
instance: None,
settings: None,
attestation_request: None,
})
}

Expand Down Expand Up @@ -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(),
)
Expand Down Expand Up @@ -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(),
);
Expand Down
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
1 change: 1 addition & 0 deletions src/tests/cookies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ fn enrollment_response() -> core_response::Payload {
final_page_content: String::new(),
instance: None,
settings: None,
attestation_request: None,
})
}

Expand Down
221 changes: 221 additions & 0 deletions src/tests/device_identity.rs
Original file line number Diff line number Diff line change
@@ -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<T: Message>(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());
}
1 change: 1 addition & 0 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod cookies;
mod device_identity;
mod mfa_config;
mod mfa_flow;
mod mtls;
Expand Down
Loading