Skip to content

Commit

Permalink
Add PRF/hmac-secret tests
Browse files Browse the repository at this point in the history
  • Loading branch information
dainnilsson committed Jan 16, 2025
1 parent 226c4d0 commit d1a46d5
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 7 deletions.
6 changes: 0 additions & 6 deletions examples/hmac_secret.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,6 @@
# the credential wasn't made with it, so keep going
print("Failed to create credential with HmacSecret, it might not work")


credential = auth_data.credential_data

# Prepare parameters for getAssertion
allow_list = [{"type": "public-key", "id": credential.credential_id}]

# Generate a salt for HmacSecret:
salt = os.urandom(32)
print("Authenticate with salt:", salt.hex())
Expand Down
7 changes: 6 additions & 1 deletion tests/device/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,12 @@ def pin_protocol(request, info):
if proto.VERSION not in info.pin_uv_protocols:
pytest.skip(f"PIN/UV protocol {proto.VERSION} not supported")

return proto()
all_protocols = ClientPin.PROTOCOLS
# Ensure we always negotiate only the selected protocol
ClientPin.PROTOCOLS = [proto]
yield proto()

ClientPin.PROTOCOLS = all_protocols


@pytest.fixture
Expand Down
166 changes: 166 additions & 0 deletions tests/device/test_prf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
from fido2.server import Fido2Server
from fido2.client import Fido2Client
from fido2.ctap2.extensions import HmacSecretExtension
from fido2.utils import websafe_encode

from . import TEST_PIN, CliInteraction

import os
import pytest


@pytest.fixture(autouse=True, scope="module")
def preconditions(dev_manager):
if "hmac-secret" not in dev_manager.info.extensions:
pytest.skip("HMAC-secret not supported by authenticator")


def test_prf(client, pin_protocol):
rp = {"id": "example.com", "name": "Example RP"}
server = Fido2Server(rp)
user = {"id": b"user_id", "name": "A. User"}
uv = "required"

create_options, state = server.register_begin(user, user_verification=uv)

# Create a credential
result = client.make_credential(
{
**create_options["publicKey"],
"extensions": {"prf": {}},
}
)
assert result.client_extension_results.prf.enabled is True
assert result.client_extension_results["prf"]["enabled"] is True

auth_data = server.register_complete(state, result)
credentials = [auth_data.credential_data]

# Complete registration
auth_data = server.register_complete(state, result)
credential = auth_data.credential_data

# Generate a salt for PRF:
salt = websafe_encode(os.urandom(32))

# Prepare parameters for getAssertion
credentials = [credential]
request_options, state = server.authenticate_begin(
credentials, user_verification=uv
)

# Authenticate the credential
result = client.get_assertion(
{
**request_options["publicKey"],
"extensions": {"prf": {"eval": {"first": salt}}},
}
)

# Only one cred in allowCredentials, only one response.
response = result.get_response(0)

output1 = response.client_extension_results.prf.results.first
assert response.client_extension_results["prf"]["results"][
"first"
] == websafe_encode(output1)

# Authenticate again, using two salts to generate two secrets.

# This time we will use evalByCredential, which can be used if there are multiple
# credentials which use different salts. Here it is not needed, but provided for
# completeness of the example.

# Generate a second salt for PRF:
salt2 = websafe_encode(os.urandom(32))
# The first salt is reused, which should result in the same secret.

result = client.get_assertion(
{
**request_options["publicKey"],
"extensions": {
"prf": {
"evalByCredential": {
websafe_encode(credential.credential_id): {
"first": salt,
"second": salt2,
}
}
}
},
}
)

response = result.get_response(0)

output = response.client_extension_results.prf.results
assert output.first == output1
assert output.second != output1
assert response.client_extension_results["prf"]["results"][
"second"
] == websafe_encode(output.second)


def test_hmac_secret(device, pin_protocol, printer):
rp = {"id": "example.com", "name": "Example RP"}
server = Fido2Server(rp)
user = {"id": b"user_id", "name": "A. User"}
uv = "required"

create_options, state = server.register_begin(user, user_verification=uv)

client = Fido2Client(
device,
"https://example.com",
user_interaction=CliInteraction(printer, TEST_PIN),
extensions=[HmacSecretExtension(allow_hmac_secret=True)],
)

# Create a credential
result = client.make_credential(
{
**create_options["publicKey"],
"extensions": {"hmacCreateSecret": True},
}
)
assert result.client_extension_results.hmac_create_secret is True
assert result.client_extension_results["hmacCreateSecret"] is True

# Complete registration
auth_data = server.register_complete(state, result)
credentials = [auth_data.credential_data]

# Generate a salt for HmacSecret:
salt = os.urandom(32)

# Prepare parameters for getAssertion
request_options, state = server.authenticate_begin(
credentials, user_verification=uv
)

result = client.get_assertion(
{
**request_options["publicKey"],
"extensions": {"hmacGetSecret": {"salt1": salt}},
}
)
result = result.get_response(0)

output1 = result.client_extension_results.hmac_get_secret.output1
assert result.client_extension_results["hmacGetSecret"][
"output1"
] == websafe_encode(output1)

salt2 = os.urandom(32)

result = client.get_assertion(
{
**request_options["publicKey"],
"extensions": {"hmacGetSecret": {"salt1": salt, "salt2": salt2}},
}
)
result = result.get_response(0)

output = result.client_extension_results.hmac_get_secret
assert output.output1 == output1
assert output.output2 != output1

0 comments on commit d1a46d5

Please sign in to comment.