Skip to content

Commit 2c7eec5

Browse files
committed
Don't cache info in Client
Also add Authenticator Config tests.
1 parent f207d0a commit 2c7eec5

File tree

1 file changed

+215
-0
lines changed

1 file changed

+215
-0
lines changed

tests/device/test_config.py

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
from fido2.ctap2.pin import ClientPin
2+
from fido2.ctap2.config import Config
3+
from fido2.server import Fido2Server
4+
from fido2.client import Fido2Client, ClientError
5+
from fido2.ctap import CtapError
6+
7+
from . import TEST_PIN, CliInteraction
8+
9+
import pytest
10+
11+
12+
@pytest.fixture(autouse=True, scope="module")
13+
def preconditions(dev_manager):
14+
if not Config.is_supported(dev_manager.info):
15+
pytest.skip("Config not supported by authenticator")
16+
17+
18+
@pytest.fixture
19+
def client_pin(ctap2, pin_protocol):
20+
return ClientPin(ctap2, pin_protocol)
21+
22+
23+
def get_config(
24+
ctap2,
25+
pin_protocol,
26+
pin=TEST_PIN,
27+
permissions=ClientPin.PERMISSION.AUTHENTICATOR_CFG,
28+
):
29+
token = ClientPin(ctap2, pin_protocol).get_pin_token(pin, permissions)
30+
return Config(ctap2, pin_protocol, token)
31+
32+
33+
def test_always_uv(ctap2, pin_protocol, device, printer):
34+
always_uv = ctap2.info.options.get("alwaysUv")
35+
if always_uv is None:
36+
pytest.skip("AlwaysUv not supported")
37+
38+
# Toggle on, if off
39+
if not always_uv:
40+
config = get_config(ctap2, pin_protocol)
41+
config.toggle_always_uv()
42+
43+
assert ctap2.get_info().options["alwaysUv"] is True
44+
45+
rp = {"id": "example.com", "name": "Example RP"}
46+
server = Fido2Server(rp)
47+
user = {"id": b"user_id", "name": "A. User"}
48+
49+
create_options, state = server.register_begin(user, user_verification="discouraged")
50+
51+
# Create a credential
52+
client = Fido2Client(
53+
device,
54+
"https://example.com",
55+
user_interaction=CliInteraction(printer, "WrongPin"),
56+
)
57+
58+
# Should require PIN due to alwaysUV and fail
59+
with pytest.raises(ClientError, match="PIN_INVALID"):
60+
client.make_credential(create_options.public_key)
61+
62+
# Toggle back off, if toggled on
63+
if not always_uv:
64+
config = get_config(ctap2, pin_protocol)
65+
config.toggle_always_uv()
66+
assert ctap2.get_info().options["alwaysUv"] is False
67+
68+
# Now create the credential without requiring auth
69+
client.make_credential(create_options.public_key)
70+
71+
72+
def test_force_pin_change(ctap2, pin_protocol, client_pin):
73+
assert ctap2.get_info().force_pin_change is False
74+
client_pin.get_pin_token(TEST_PIN)
75+
76+
config = get_config(ctap2, pin_protocol)
77+
config.set_min_pin_length(force_change_pin=True)
78+
assert ctap2.get_info().force_pin_change is True
79+
80+
with pytest.raises(CtapError, match="PIN_INVALID"):
81+
client_pin.get_pin_token(TEST_PIN)
82+
83+
pin = TEST_PIN[::-1]
84+
client_pin.change_pin(TEST_PIN, pin)
85+
client_pin.change_pin(pin, TEST_PIN)
86+
client_pin.get_pin_token(TEST_PIN)
87+
88+
89+
def test_min_pin_length(dev_manager, ctap2, pin_protocol, client_pin, printer):
90+
config = get_config(ctap2, pin_protocol)
91+
92+
orig_len = ctap2.info.min_pin_length
93+
94+
config.set_min_pin_length(min_pin_length=orig_len + 2)
95+
96+
pin = TEST_PIN * 4
97+
98+
# Too short
99+
with pytest.raises(CtapError, match="PIN_POLICY_VIOLATION"):
100+
client_pin.change_pin(TEST_PIN, pin[:orig_len])
101+
102+
# Just long enough
103+
client_pin.change_pin(TEST_PIN, pin[: orig_len + 2])
104+
105+
# Even longer
106+
client_pin.change_pin(pin[: orig_len + 2], pin[: orig_len + 4])
107+
108+
config = get_config(ctap2, pin_protocol, pin=pin[: orig_len + 4])
109+
110+
# Cannot shorten min pin length
111+
with pytest.raises(CtapError, match="PIN_POLICY_VIOLATION"):
112+
config.set_min_pin_length(min_pin_length=orig_len)
113+
114+
config.set_min_pin_length(min_pin_length=orig_len + 6)
115+
116+
# Current PIN is too short
117+
assert ctap2.get_info().force_pin_change is True
118+
119+
client_pin.change_pin(pin[: orig_len + 4], pin[: orig_len + 6])
120+
assert ctap2.get_info().force_pin_change is False
121+
122+
# Test minPinLength extension
123+
rp = {"id": "example.com", "name": "Example RP"}
124+
server = Fido2Server(rp)
125+
user = {"id": b"user_id", "name": "A. User"}
126+
127+
create_options, state = server.register_begin(user, user_verification="discouraged")
128+
129+
if "setMinPINLength" in ctap2.info.options:
130+
config = get_config(ctap2, pin_protocol, pin=pin[: orig_len + 6])
131+
config.set_min_pin_length(rp_ids=[rp["id"]])
132+
client = Fido2Client(
133+
dev_manager.device,
134+
"https://example.com",
135+
user_interaction=CliInteraction(printer, pin[: orig_len + 6]),
136+
)
137+
138+
result = client.make_credential(
139+
{
140+
**create_options["publicKey"],
141+
"extensions": {"minPinLength": True},
142+
}
143+
)
144+
auth_data = server.register_complete(state, result)
145+
assert auth_data.extensions["minPinLength"] == orig_len + 6
146+
147+
# Restore original config
148+
dev_manager.factory_reset(setup=True)
149+
assert dev_manager.info.min_pin_length == orig_len
150+
151+
152+
@pytest.fixture(scope="module")
153+
def enable_ep(dev_manager):
154+
if "ep" not in dev_manager.info.options:
155+
pytest.skip("Enterprise Attestation not supported")
156+
157+
assert dev_manager.info.options["ep"] is False
158+
159+
# Enable EP
160+
pin_protocol = ClientPin(dev_manager.ctap2).protocol
161+
config = get_config(dev_manager.ctap2, pin_protocol)
162+
config.enable_enterprise_attestation()
163+
assert dev_manager.info.options["ep"] is True
164+
165+
yield None
166+
167+
# Restore original config
168+
dev_manager.factory_reset(setup=True)
169+
assert dev_manager.info.options["ep"] is False
170+
171+
172+
@pytest.fixture(scope="module")
173+
def att_cert(dev_manager):
174+
rp = {"id": "example.com", "name": "Example RP"}
175+
user = {"id": b"user_id", "name": "A. User"}
176+
177+
server = Fido2Server(rp, attestation="direct")
178+
create_options, state = server.register_begin(user)
179+
result = dev_manager.client.make_credential(create_options.public_key)
180+
return result.response.attestation_object.att_stmt["x5c"][0]
181+
182+
183+
def test_ep_platform(client, enable_ep, att_cert):
184+
rp = {"id": "example.com", "name": "Example RP"}
185+
user = {"id": b"user_id", "name": "A. User"}
186+
187+
server = Fido2Server(rp, attestation="enterprise")
188+
create_options, state = server.register_begin(user)
189+
190+
client._enterprise_rpid_list = [rp["id"]]
191+
result = client.make_credential(create_options.public_key)
192+
cert = result.response.attestation_object.att_stmt["x5c"][0]
193+
194+
assert att_cert != cert
195+
196+
197+
def test_ep_vendor(pytestconfig, device, printer, enable_ep, att_cert):
198+
ep_rp_id = pytestconfig.getoption("ep_rp_id")
199+
if not ep_rp_id:
200+
pytest.skip("No RP ID provided with --ep-rp-id")
201+
202+
rp = {"id": ep_rp_id, "name": "Example RP"}
203+
user = {"id": b"user_id", "name": "A. User"}
204+
205+
server = Fido2Server(rp, attestation="enterprise")
206+
create_options, state = server.register_begin(user)
207+
208+
client = Fido2Client(
209+
device, f"https://{ep_rp_id}", user_interaction=CliInteraction(printer)
210+
)
211+
212+
result = client.make_credential(create_options.public_key)
213+
cert = result.response.attestation_object.att_stmt["x5c"][0]
214+
215+
assert att_cert != cert

0 commit comments

Comments
 (0)