Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

use proper types in hmac_secret example, rather than dictionaries #166

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 65 additions & 39 deletions examples/hmac_secret.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,38 @@
creates a new credential for it with the extension enabled, and uses it to
derive two separate secrets.
"""
from fido2.hid import CtapHidDevice
from fido2.client import Fido2Client, UserInteraction
from getpass import getpass
import sys
from __future__ import annotations
import os
import sys
from getpass import getpass
from typing import Iterable

from fido2.client import Fido2Client, UserInteraction
from fido2.cose import ES256
from fido2.hid import CtapHidDevice
from fido2.webauthn import (
PublicKeyCredentialCreationOptions,
PublicKeyCredentialDescriptor,
PublicKeyCredentialParameters,
PublicKeyCredentialRequestOptions,
PublicKeyCredentialRpEntity,
PublicKeyCredentialType,
PublicKeyCredentialUserEntity,
)


try:
from fido2.pcsc import CtapPcscDevice

have_pcsc = True
except ImportError:
CtapPcscDevice = None
have_pcsc = False


def enumerate_devices():
for dev in CtapHidDevice.list_devices():
yield dev
if CtapPcscDevice:
for dev in CtapPcscDevice.list_devices():
yield dev
def enumerate_devices() -> Iterable[CtapHidDevice | CtapPcscDevice]:
yield from CtapHidDevice.list_devices()
if have_pcsc:
yield from CtapPcscDevice.list_devices()


# Handle user interaction
Expand All @@ -73,50 +87,62 @@ def request_uv(self, permissions, rd_id):
sys.exit(1)

# Prepare parameters for makeCredential
rp = {"id": "example.com", "name": "Example RP"}
user = {"id": b"user_id", "name": "A. User"}
rp = PublicKeyCredentialRpEntity(id="example.com", name="Example RP")
user = PublicKeyCredentialUserEntity(id=b"user_id", name="A. User")
challenge = b"Y2hhbGxlbmdl"

# Create a credential with a HmacSecret
result = client.make_credential(
{
"rp": rp,
"user": user,
"challenge": challenge,
"pubKeyCredParams": [{"type": "public-key", "alg": -7}],
"extensions": {"hmacCreateSecret": True},
},
PublicKeyCredentialCreationOptions(
rp=rp,
user=user,
challenge=challenge,
pub_key_cred_params=[
PublicKeyCredentialParameters(
type=PublicKeyCredentialType.PUBLIC_KEY, alg=ES256.ALGORITHM
)
],
extensions={"hmacCreateSecret": True},
),
)

# HmacSecret result:
if not result.extension_results.get("hmacCreateSecret"):
if result.extension_results is None or not result.extension_results.get(
"hmacCreateSecret"
):
print("Failed to create credential with HmacSecret")
sys.exit(1)

credential = result.attestation_object.auth_data.credential_data
assert credential is not None
print("New credential created, with the HmacSecret extension.")

# Prepare parameters for getAssertion
challenge = b"Q0hBTExFTkdF" # Use a new challenge for each call.
allow_list = [{"type": "public-key", "id": credential.credential_id}]
allow_list = [
PublicKeyCredentialDescriptor(
type=PublicKeyCredentialType.PUBLIC_KEY, id=credential.credential_id
)
]

# Generate a salt for HmacSecret:
salt = os.urandom(32)
print("Authenticate with salt:", salt.hex())

# Authenticate the credential
result = client.get_assertion(
{
"rpId": rp["id"],
"challenge": challenge,
"allowCredentials": allow_list,
"extensions": {"hmacGetSecret": {"salt1": salt}},
},
assertion_result = client.get_assertion(
options=PublicKeyCredentialRequestOptions(
rp_id=rp.id,
challenge=challenge,
allow_credentials=allow_list,
extensions={"hmacGetSecret": {"salt1": salt}},
),
).get_response(
0
) # Only one cred in allowList, only one response.

output1 = result.extension_results["hmacGetSecret"]["output1"]
assert assertion_result.extension_results is not None
output1 = assertion_result.extension_results["hmacGetSecret"]["output1"]
print("Authenticated, secret:", output1.hex())

# Authenticate again, using two salts to generate two secrets:
Expand All @@ -126,17 +152,17 @@ def request_uv(self, permissions, rd_id):
print("Authenticate with second salt:", salt2.hex())

# The first salt is reused, which should result in the same secret.
result = client.get_assertion(
{
"rpId": rp["id"],
"challenge": challenge,
"allowCredentials": allow_list,
"extensions": {"hmacGetSecret": {"salt1": salt, "salt2": salt2}},
},
assertion_result = client.get_assertion(
options=PublicKeyCredentialRequestOptions(
challenge=challenge,
rp_id=rp.id,
allow_credentials=allow_list,
extensions={"hmacGetSecret": {"salt1": salt, "salt2": salt2}},
),
).get_response(
0
) # One cred in allowCredentials, single response.

output = result.extension_results["hmacGetSecret"]
assert assertion_result.extension_results is not None
output = assertion_result.extension_results["hmacGetSecret"]
print("Old secret:", output["output1"].hex())
print("New secret:", output["output2"].hex())