Skip to content

Commit 930dcdd

Browse files
chore(tests): added 12 more end-to-end tests
1 parent 6567b9c commit 930dcdd

6 files changed

Lines changed: 730 additions & 0 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use crate::ops::webauthn::{
2+
CredentialPropsExtension, MakeCredentialsRequestExtensions, ResidentKeyRequirement,
3+
UserVerificationRequirement,
4+
};
5+
use crate::transport::hid::get_virtual_device;
6+
use crate::transport::{Channel, Device};
7+
use test_log::test;
8+
use tokio::sync::broadcast::error::TryRecvError;
9+
10+
use super::helpers::*;
11+
12+
#[test(tokio::test)]
13+
async fn test_cred_props_rk_required() {
14+
let mut device = get_virtual_device();
15+
let mut channel = device.channel().await.unwrap();
16+
17+
let state_recv = channel.get_ux_update_receiver();
18+
let uv_handle = tokio::spawn(handle_updates(
19+
state_recv,
20+
vec![ExpectedUpdate::PresenceRequired],
21+
));
22+
23+
let extensions = MakeCredentialsRequestExtensions {
24+
cred_props: Some(true),
25+
..Default::default()
26+
};
27+
let mc_resp = make_credential(
28+
&mut channel,
29+
b"cred-props-rk-required",
30+
ResidentKeyRequirement::Required,
31+
UserVerificationRequirement::Discouraged,
32+
Some(extensions),
33+
)
34+
.await
35+
.expect("MakeCredential failed");
36+
37+
assert_eq!(
38+
mc_resp.unsigned_extensions_output.cred_props,
39+
Some(CredentialPropsExtension { rk: Some(true) })
40+
);
41+
42+
let mut rx = uv_handle.await.unwrap();
43+
assert_eq!(rx.try_recv(), Err(TryRecvError::Empty));
44+
}
45+
46+
#[test(tokio::test)]
47+
async fn test_cred_props_rk_discouraged() {
48+
let mut device = get_virtual_device();
49+
let mut channel = device.channel().await.unwrap();
50+
51+
let state_recv = channel.get_ux_update_receiver();
52+
let uv_handle = tokio::spawn(handle_updates(
53+
state_recv,
54+
vec![ExpectedUpdate::PresenceRequired],
55+
));
56+
57+
let extensions = MakeCredentialsRequestExtensions {
58+
cred_props: Some(true),
59+
..Default::default()
60+
};
61+
let mc_resp = make_credential(
62+
&mut channel,
63+
b"cred-props-rk-discouraged",
64+
ResidentKeyRequirement::Discouraged,
65+
UserVerificationRequirement::Discouraged,
66+
Some(extensions),
67+
)
68+
.await
69+
.expect("MakeCredential failed");
70+
71+
// FIDO 2.1 authenticator: platform can definitively say rk=false
72+
assert_eq!(
73+
mc_resp.unsigned_extensions_output.cred_props,
74+
Some(CredentialPropsExtension { rk: Some(false) })
75+
);
76+
77+
let mut rx = uv_handle.await.unwrap();
78+
assert_eq!(rx.try_recv(), Err(TryRecvError::Empty));
79+
}
80+
81+
#[test(tokio::test)]
82+
async fn test_cred_props_not_requested() {
83+
let mut device = get_virtual_device();
84+
let mut channel = device.channel().await.unwrap();
85+
86+
let state_recv = channel.get_ux_update_receiver();
87+
let uv_handle = tokio::spawn(handle_updates(
88+
state_recv,
89+
vec![ExpectedUpdate::PresenceRequired],
90+
));
91+
92+
let mc_resp = make_credential(
93+
&mut channel,
94+
b"cred-props-not-requested",
95+
ResidentKeyRequirement::Discouraged,
96+
UserVerificationRequirement::Discouraged,
97+
None,
98+
)
99+
.await
100+
.expect("MakeCredential failed");
101+
102+
assert_eq!(mc_resp.unsigned_extensions_output.cred_props, None);
103+
104+
let mut rx = uv_handle.await.unwrap();
105+
assert_eq!(rx.try_recv(), Err(TryRecvError::Empty));
106+
}
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
use ctap_types::ctap2::credential_management::CredentialProtectionPolicy as Ctap2CredentialProtectionPolicy;
2+
3+
use crate::ops::webauthn::{
4+
CredentialProtectionExtension, CredentialProtectionPolicy, MakeCredentialsRequestExtensions,
5+
ResidentKeyRequirement, UserVerificationRequirement,
6+
};
7+
use crate::pin::PinManagement;
8+
use crate::proto::CtapError;
9+
use crate::transport::hid::get_virtual_device;
10+
use crate::transport::{Channel, Device};
11+
use crate::webauthn::Error;
12+
use test_log::test;
13+
use tokio::sync::broadcast::error::TryRecvError;
14+
15+
use super::helpers::*;
16+
17+
fn cred_protect_extensions(policy: CredentialProtectionPolicy) -> MakeCredentialsRequestExtensions {
18+
MakeCredentialsRequestExtensions {
19+
cred_protect: Some(CredentialProtectionExtension {
20+
policy,
21+
enforce_policy: true,
22+
}),
23+
..Default::default()
24+
}
25+
}
26+
27+
/// Level 1: userVerificationOptional — discoverable assertion without UV succeeds.
28+
#[test(tokio::test)]
29+
async fn test_cred_protect_level1() {
30+
let mut device = get_virtual_device();
31+
let mut channel = device.channel().await.unwrap();
32+
channel.change_pin(PIN.to_owned(), TIMEOUT).await.unwrap();
33+
34+
let user_id = b"cp-level1-user";
35+
let state_recv = channel.get_ux_update_receiver();
36+
let uv_handle = tokio::spawn(handle_updates(
37+
state_recv,
38+
vec![
39+
ExpectedUpdate::PinRequired, // MakeCredential PIN
40+
ExpectedUpdate::PresenceRequired, // MakeCredential touch
41+
ExpectedUpdate::PresenceRequired, // GetAssertion touch
42+
],
43+
));
44+
45+
let extensions =
46+
cred_protect_extensions(CredentialProtectionPolicy::UserVerificationOptional);
47+
let mc_resp = make_credential(
48+
&mut channel,
49+
user_id,
50+
ResidentKeyRequirement::Required,
51+
UserVerificationRequirement::Preferred,
52+
Some(extensions),
53+
)
54+
.await
55+
.expect("MakeCredential with credProtect level 1 failed");
56+
57+
assert_eq!(
58+
mc_resp
59+
.authenticator_data
60+
.extensions
61+
.as_ref()
62+
.and_then(|e| e.cred_protect),
63+
Some(Ctap2CredentialProtectionPolicy::Optional)
64+
);
65+
66+
// Discoverable assertion without UV should succeed at level 1
67+
get_assertion(
68+
&mut channel,
69+
vec![],
70+
UserVerificationRequirement::Discouraged,
71+
None,
72+
)
73+
.await
74+
.expect("Level 1: discoverable assertion without UV should succeed");
75+
76+
let mut rx = uv_handle.await.unwrap();
77+
assert_eq!(rx.try_recv(), Err(TryRecvError::Empty));
78+
}
79+
80+
/// Level 2: userVerificationOptionalWithCredentialIDList —
81+
/// with UV provided (cached PIN token), discoverable assertion succeeds.
82+
/// Without UV it would fail, but the platform caches the PIN token from MakeCredential.
83+
#[test(tokio::test)]
84+
async fn test_cred_protect_level2() {
85+
let mut device = get_virtual_device();
86+
let mut channel = device.channel().await.unwrap();
87+
channel.change_pin(PIN.to_owned(), TIMEOUT).await.unwrap();
88+
89+
let user_id = b"cp-level2-user";
90+
let state_recv = channel.get_ux_update_receiver();
91+
let uv_handle = tokio::spawn(handle_updates(
92+
state_recv,
93+
vec![
94+
ExpectedUpdate::PinRequired, // MakeCredential PIN
95+
ExpectedUpdate::PresenceRequired, // MakeCredential touch
96+
ExpectedUpdate::PresenceRequired, // GetAssertion touch (UV via cached token)
97+
],
98+
));
99+
100+
let extensions = cred_protect_extensions(
101+
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIDList,
102+
);
103+
let mc_resp = make_credential(
104+
&mut channel,
105+
user_id,
106+
ResidentKeyRequirement::Required,
107+
UserVerificationRequirement::Preferred,
108+
Some(extensions),
109+
)
110+
.await
111+
.expect("MakeCredential with credProtect level 2 failed");
112+
113+
assert_eq!(
114+
mc_resp
115+
.authenticator_data
116+
.extensions
117+
.as_ref()
118+
.and_then(|e| e.cred_protect),
119+
Some(Ctap2CredentialProtectionPolicy::OptionalWithCredentialIdList)
120+
);
121+
122+
// Discoverable assertion succeeds because cached PIN token provides UV
123+
let ga_resp = get_assertion(
124+
&mut channel,
125+
vec![],
126+
UserVerificationRequirement::Discouraged,
127+
None,
128+
)
129+
.await
130+
.expect("Level 2: discoverable with UV (cached token) should succeed");
131+
132+
assert_eq!(ga_resp.assertions.len(), 1);
133+
let user = ga_resp.assertions[0]
134+
.user
135+
.as_ref()
136+
.expect("Discoverable assertion should include user entity");
137+
assert_eq!(user.id.as_ref(), user_id);
138+
139+
let mut rx = uv_handle.await.unwrap();
140+
assert_eq!(rx.try_recv(), Err(TryRecvError::Empty));
141+
}
142+
143+
/// Level 3: userVerificationRequired — assertion without UV fails
144+
/// even with the credential ID in the allow list.
145+
#[test(tokio::test)]
146+
async fn test_cred_protect_level3() {
147+
let mut device = get_virtual_device();
148+
let mut channel = device.channel().await.unwrap();
149+
channel.change_pin(PIN.to_owned(), TIMEOUT).await.unwrap();
150+
151+
let user_id = b"cp-level3-user";
152+
let state_recv = channel.get_ux_update_receiver();
153+
let uv_handle = tokio::spawn(handle_updates(
154+
state_recv,
155+
vec![
156+
ExpectedUpdate::PinRequired, // MakeCredential PIN
157+
ExpectedUpdate::PresenceRequired, // MakeCredential touch
158+
ExpectedUpdate::PresenceRequired, // GetAssertion (preflight filters credential, dummy touch)
159+
],
160+
));
161+
162+
let extensions =
163+
cred_protect_extensions(CredentialProtectionPolicy::UserVerificationRequired);
164+
let mc_resp = make_credential(
165+
&mut channel,
166+
user_id,
167+
ResidentKeyRequirement::Required,
168+
UserVerificationRequirement::Required,
169+
Some(extensions),
170+
)
171+
.await
172+
.expect("MakeCredential with credProtect level 3 failed");
173+
174+
assert_eq!(
175+
mc_resp
176+
.authenticator_data
177+
.extensions
178+
.as_ref()
179+
.and_then(|e| e.cred_protect),
180+
Some(Ctap2CredentialProtectionPolicy::Required)
181+
);
182+
183+
// Assertion without UV should fail at level 3
184+
// (preflight cannot discover this credential without UV)
185+
let credential = credential_from(&mc_resp);
186+
let result = get_assertion(
187+
&mut channel,
188+
vec![credential],
189+
UserVerificationRequirement::Discouraged,
190+
None,
191+
)
192+
.await;
193+
assert!(
194+
matches!(result, Err(Error::Ctap(CtapError::NoCredentials))),
195+
"Level 3: assertion without UV should fail, got: {:?}",
196+
result
197+
);
198+
199+
let mut rx = uv_handle.await.unwrap();
200+
assert_eq!(rx.try_recv(), Err(TryRecvError::Empty));
201+
}

0 commit comments

Comments
 (0)