Skip to content

Commit d1a46d5

Browse files
committed
Add PRF/hmac-secret tests
1 parent 226c4d0 commit d1a46d5

File tree

3 files changed

+172
-7
lines changed

3 files changed

+172
-7
lines changed

examples/hmac_secret.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,6 @@
108108
# the credential wasn't made with it, so keep going
109109
print("Failed to create credential with HmacSecret, it might not work")
110110

111-
112-
credential = auth_data.credential_data
113-
114-
# Prepare parameters for getAssertion
115-
allow_list = [{"type": "public-key", "id": credential.credential_id}]
116-
117111
# Generate a salt for HmacSecret:
118112
salt = os.urandom(32)
119113
print("Authenticate with salt:", salt.hex())

tests/device/conftest.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,12 @@ def pin_protocol(request, info):
271271
if proto.VERSION not in info.pin_uv_protocols:
272272
pytest.skip(f"PIN/UV protocol {proto.VERSION} not supported")
273273

274-
return proto()
274+
all_protocols = ClientPin.PROTOCOLS
275+
# Ensure we always negotiate only the selected protocol
276+
ClientPin.PROTOCOLS = [proto]
277+
yield proto()
278+
279+
ClientPin.PROTOCOLS = all_protocols
275280

276281

277282
@pytest.fixture

tests/device/test_prf.py

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
from fido2.server import Fido2Server
2+
from fido2.client import Fido2Client
3+
from fido2.ctap2.extensions import HmacSecretExtension
4+
from fido2.utils import websafe_encode
5+
6+
from . import TEST_PIN, CliInteraction
7+
8+
import os
9+
import pytest
10+
11+
12+
@pytest.fixture(autouse=True, scope="module")
13+
def preconditions(dev_manager):
14+
if "hmac-secret" not in dev_manager.info.extensions:
15+
pytest.skip("HMAC-secret not supported by authenticator")
16+
17+
18+
def test_prf(client, pin_protocol):
19+
rp = {"id": "example.com", "name": "Example RP"}
20+
server = Fido2Server(rp)
21+
user = {"id": b"user_id", "name": "A. User"}
22+
uv = "required"
23+
24+
create_options, state = server.register_begin(user, user_verification=uv)
25+
26+
# Create a credential
27+
result = client.make_credential(
28+
{
29+
**create_options["publicKey"],
30+
"extensions": {"prf": {}},
31+
}
32+
)
33+
assert result.client_extension_results.prf.enabled is True
34+
assert result.client_extension_results["prf"]["enabled"] is True
35+
36+
auth_data = server.register_complete(state, result)
37+
credentials = [auth_data.credential_data]
38+
39+
# Complete registration
40+
auth_data = server.register_complete(state, result)
41+
credential = auth_data.credential_data
42+
43+
# Generate a salt for PRF:
44+
salt = websafe_encode(os.urandom(32))
45+
46+
# Prepare parameters for getAssertion
47+
credentials = [credential]
48+
request_options, state = server.authenticate_begin(
49+
credentials, user_verification=uv
50+
)
51+
52+
# Authenticate the credential
53+
result = client.get_assertion(
54+
{
55+
**request_options["publicKey"],
56+
"extensions": {"prf": {"eval": {"first": salt}}},
57+
}
58+
)
59+
60+
# Only one cred in allowCredentials, only one response.
61+
response = result.get_response(0)
62+
63+
output1 = response.client_extension_results.prf.results.first
64+
assert response.client_extension_results["prf"]["results"][
65+
"first"
66+
] == websafe_encode(output1)
67+
68+
# Authenticate again, using two salts to generate two secrets.
69+
70+
# This time we will use evalByCredential, which can be used if there are multiple
71+
# credentials which use different salts. Here it is not needed, but provided for
72+
# completeness of the example.
73+
74+
# Generate a second salt for PRF:
75+
salt2 = websafe_encode(os.urandom(32))
76+
# The first salt is reused, which should result in the same secret.
77+
78+
result = client.get_assertion(
79+
{
80+
**request_options["publicKey"],
81+
"extensions": {
82+
"prf": {
83+
"evalByCredential": {
84+
websafe_encode(credential.credential_id): {
85+
"first": salt,
86+
"second": salt2,
87+
}
88+
}
89+
}
90+
},
91+
}
92+
)
93+
94+
response = result.get_response(0)
95+
96+
output = response.client_extension_results.prf.results
97+
assert output.first == output1
98+
assert output.second != output1
99+
assert response.client_extension_results["prf"]["results"][
100+
"second"
101+
] == websafe_encode(output.second)
102+
103+
104+
def test_hmac_secret(device, pin_protocol, printer):
105+
rp = {"id": "example.com", "name": "Example RP"}
106+
server = Fido2Server(rp)
107+
user = {"id": b"user_id", "name": "A. User"}
108+
uv = "required"
109+
110+
create_options, state = server.register_begin(user, user_verification=uv)
111+
112+
client = Fido2Client(
113+
device,
114+
"https://example.com",
115+
user_interaction=CliInteraction(printer, TEST_PIN),
116+
extensions=[HmacSecretExtension(allow_hmac_secret=True)],
117+
)
118+
119+
# Create a credential
120+
result = client.make_credential(
121+
{
122+
**create_options["publicKey"],
123+
"extensions": {"hmacCreateSecret": True},
124+
}
125+
)
126+
assert result.client_extension_results.hmac_create_secret is True
127+
assert result.client_extension_results["hmacCreateSecret"] is True
128+
129+
# Complete registration
130+
auth_data = server.register_complete(state, result)
131+
credentials = [auth_data.credential_data]
132+
133+
# Generate a salt for HmacSecret:
134+
salt = os.urandom(32)
135+
136+
# Prepare parameters for getAssertion
137+
request_options, state = server.authenticate_begin(
138+
credentials, user_verification=uv
139+
)
140+
141+
result = client.get_assertion(
142+
{
143+
**request_options["publicKey"],
144+
"extensions": {"hmacGetSecret": {"salt1": salt}},
145+
}
146+
)
147+
result = result.get_response(0)
148+
149+
output1 = result.client_extension_results.hmac_get_secret.output1
150+
assert result.client_extension_results["hmacGetSecret"][
151+
"output1"
152+
] == websafe_encode(output1)
153+
154+
salt2 = os.urandom(32)
155+
156+
result = client.get_assertion(
157+
{
158+
**request_options["publicKey"],
159+
"extensions": {"hmacGetSecret": {"salt1": salt, "salt2": salt2}},
160+
}
161+
)
162+
result = result.get_response(0)
163+
164+
output = result.client_extension_results.hmac_get_secret
165+
assert output.output1 == output1
166+
assert output.output2 != output1

0 commit comments

Comments
 (0)