Skip to content

Commit fa5a05b

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

6 files changed

Lines changed: 645 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)