Skip to content

Commit

Permalink
Clean up examples and extract common code
Browse files Browse the repository at this point in the history
  • Loading branch information
dainnilsson committed Nov 6, 2024
1 parent 5bcdf75 commit 5eead78
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 369 deletions.
55 changes: 7 additions & 48 deletions examples/cred_blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,62 +29,21 @@
Connects to the first FIDO device found which supports the CredBlob extension,
creates a new credential for it with the extension enabled, and stores some data.
"""
from fido2.hid import CtapHidDevice
from fido2.client import Fido2Client, WindowsClient, UserInteraction
from fido2.server import Fido2Server
from getpass import getpass
import ctypes
from exampleutils import get_client
import sys
import os

try:
from fido2.pcsc import CtapPcscDevice
except ImportError:
CtapPcscDevice = None


def enumerate_devices():
for dev in CtapHidDevice.list_devices():
yield dev
if CtapPcscDevice:
for dev in CtapPcscDevice.list_devices():
yield dev


# Handle user interaction
class CliInteraction(UserInteraction):
def prompt_up(self):
print("\nTouch your authenticator device now...\n")

def request_pin(self, permissions, rd_id):
return getpass("Enter PIN: ")

def request_uv(self, permissions, rd_id):
print("User Verification required.")
return True
# Locate a suitable FIDO authenticator
client = get_client(lambda client: "credBlob" in client.info.extensions)


# Prefer UV token if supported
uv = "discouraged"

if WindowsClient.is_available() and not ctypes.windll.shell32.IsUserAnAdmin():
# Use the Windows WebAuthn API if available, and we're not running as admin
client = WindowsClient("https://example.com")
else:
# Locate a device
for dev in enumerate_devices():
client = Fido2Client(
dev, "https://example.com", user_interaction=CliInteraction()
)
if "credBlob" in client.info.extensions:
break
else:
print("No Authenticator with the CredBlob extension found!")
sys.exit(1)

# Prefer UV token if supported
if client.info.options.get("uv") or client.info.options.get("bioEnroll"):
uv = "preferred"
print("Authenticator is configured for User Verification")
if client.info.options.get("uv") or client.info.options.get("bioEnroll"):
uv = "preferred"
print("Authenticator is configured for User Verification")


server = Fido2Server({"id": "example.com", "name": "Example RP"})
Expand Down
54 changes: 8 additions & 46 deletions examples/credential.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,57 +31,19 @@
This works with both FIDO 2.0 devices as well as with U2F devices.
On Windows, the native WebAuthn API will be used.
"""
from fido2.hid import CtapHidDevice
from fido2.client import Fido2Client, WindowsClient, UserInteraction
from fido2.server import Fido2Server
from getpass import getpass
import sys
import ctypes
from exampleutils import get_client

# Locate a suitable FIDO authenticator
client = get_client()

# Handle user interaction
class CliInteraction(UserInteraction):
def prompt_up(self):
print("\nTouch your authenticator device now...\n")

def request_pin(self, permissions, rd_id):
return getpass("Enter PIN: ")

def request_uv(self, permissions, rd_id):
print("User Verification required.")
return True


uv = "discouraged"

if WindowsClient.is_available() and not ctypes.windll.shell32.IsUserAnAdmin():
# Use the Windows WebAuthn API if available, and we're not running as admin
client = WindowsClient("https://example.com")
# Prefer UV if supported and configured
if client.info.options.get("uv") or client.info.options.get("bioEnroll"):
uv = "preferred"
print("Authenticator supports User Verification")
else:
# Locate a device
dev = next(CtapHidDevice.list_devices(), None)
if dev is not None:
print("Use USB HID channel.")
else:
try:
from fido2.pcsc import CtapPcscDevice

dev = next(CtapPcscDevice.list_devices(), None)
print("Use NFC channel.")
except Exception as e:
print("NFC channel search error:", e)

if not dev:
print("No FIDO device found")
sys.exit(1)

# Set up a FIDO 2 client using the origin https://example.com
client = Fido2Client(dev, "https://example.com", user_interaction=CliInteraction())

# Prefer UV if supported and configured
if client.info.options.get("uv") or client.info.options.get("bioEnroll"):
uv = "preferred"
print("Authenticator supports User Verification")
uv = "discouraged"


server = Fido2Server({"id": "example.com", "name": "Example RP"}, attestation="direct")
Expand Down
97 changes: 97 additions & 0 deletions examples/exampleutils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Copyright (c) 2024 Yubico AB
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

"""
Utilities for common functionality used by several examples in this directory.
"""

from fido2.hid import CtapHidDevice
from fido2.client import Fido2Client, WindowsClient, UserInteraction
from getpass import getpass
import ctypes


try:
from fido2.pcsc import CtapPcscDevice
except ImportError:
CtapPcscDevice = None


# Handle user interaction via CLI prompts
class CliInteraction(UserInteraction):
def __init__(self):
self._pin = None

def prompt_up(self):
print("\nTouch your authenticator device now...\n")

def request_pin(self, permissions, rd_id):
if not self._pin:
self._pin = getpass("Enter PIN: ")
return self._pin

def request_uv(self, permissions, rd_id):
print("User Verification required.")
return True


def enumerate_devices():
for dev in CtapHidDevice.list_devices():
yield dev
if CtapPcscDevice:
for dev in CtapPcscDevice.list_devices():
yield dev


def get_client(predicate=None, **kwargs):
"""Locate a CTAP device suitable for use.
If running on Windows as non-admin, the predicate check will be skipped and
a webauthn.dll based client will be returned.
Extra kwargs will be passed to the constructor of Fido2Client.
"""
if WindowsClient.is_available() and not ctypes.windll.shell32.IsUserAnAdmin():
# Use the Windows WebAuthn API if available, and we're not running as admin
return WindowsClient("https://example.com")

user_interaction = kwargs.pop("user_interaction", None) or CliInteraction()

# Locate a device
for dev in enumerate_devices():
# Set up a FIDO 2 client using the origin https://example.com
client = Fido2Client(
dev,
"https://example.com",
user_interaction=user_interaction,
**kwargs,
)
# Check if it is suitable for use
if predicate is None or predicate(client):
return client
else:
raise ValueError("No suitable Authenticator found!")
21 changes: 3 additions & 18 deletions examples/hmac_secret.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@
"""
from fido2.hid import CtapHidDevice
from fido2.server import Fido2Server
from fido2.client import Fido2Client, WindowsClient, UserInteraction
from fido2.client import Fido2Client, WindowsClient
from fido2.ctap2.extensions import HmacSecretExtension
from exampleutils import CliInteraction
from functools import partial
from getpass import getpass
import ctypes
import sys
import os
Expand All @@ -58,28 +58,13 @@ def enumerate_devices():
yield dev


# Handle user interaction
class CliInteraction(UserInteraction):
def prompt_up(self):
print("\nTouch your authenticator device now...\n")

def request_pin(self, permissions, rd_id):
return getpass("Enter PIN: ")

def request_uv(self, permissions, rd_id):
print("User Verification required.")
return True


uv = "discouraged"
rk = "discouraged"

if WindowsClient.is_available() and not ctypes.windll.shell32.IsUserAnAdmin():
# Use the Windows WebAuthn API if available, and we're not running as admin
# By default only the PRF extension is allowed, we need to explicitly
# configure the client to allow hmac-secret
client = WindowsClient("https://example.com", allow_hmac_secret=True)
rk = "required" # Windows requires resident key for hmac-secret
else:
# Locate a device
for dev in enumerate_devices():
Expand All @@ -103,7 +88,7 @@ def request_uv(self, permissions, rd_id):
# Prepare parameters for makeCredential
create_options, state = server.register_begin(
user,
resident_key_requirement=rk,
resident_key_requirement="discouraged",
user_verification=uv,
authenticator_attachment="cross-platform",
)
Expand Down
63 changes: 6 additions & 57 deletions examples/large_blobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,66 +31,19 @@
This works with both FIDO 2.0 devices as well as with U2F devices.
On Windows, the native WebAuthn API will be used.
"""
from fido2.hid import CtapHidDevice
from fido2.client import Fido2Client, WindowsClient, UserInteraction
from fido2.server import Fido2Server
from getpass import getpass
import ctypes
from exampleutils import get_client
import sys


try:
from fido2.pcsc import CtapPcscDevice
except ImportError:
CtapPcscDevice = None


def enumerate_devices():
for dev in CtapHidDevice.list_devices():
yield dev
if CtapPcscDevice:
for dev in CtapPcscDevice.list_devices():
yield dev


# Handle user interaction
class CliInteraction(UserInteraction):
def prompt_up(self):
print("\nTouch your authenticator device now...\n")

def request_pin(self, permissions, rd_id):
return getpass("Enter PIN: ")

def request_uv(self, permissions, rd_id):
print("User Verification required.")
return True
# Locate a suitable FIDO authenticator
client = get_client(lambda client: "largeBlobKey" in client.info.extensions)


# LargeBlob requires UV if is it configured
uv = "discouraged"

# Locate a device
if WindowsClient.is_available() and not ctypes.windll.shell32.IsUserAnAdmin():
# Use the Windows WebAuthn API if available, and we're not running as admin
client = WindowsClient("https://example.com")
else:
for dev in enumerate_devices():
client = Fido2Client(
dev, "https://example.com", user_interaction=CliInteraction()
)
if "largeBlobKey" in client.info.extensions:
break
else:
print("No Authenticator with the largeBlobKey extension found!")
sys.exit(1)

if not client.info.options.get("largeBlobs"):
print("Authenticator does not support large blobs!")
sys.exit(1)

# Prefer UV token if supported
if client.info.options.get("uv") or client.info.options.get("bioEnroll"):
uv = "preferred"
print("Authenticator is configured for User Verification")
if client.info.options.get("clientPin"):
uv = "required"


server = Fido2Server({"id": "example.com", "name": "Example RP"})
Expand Down Expand Up @@ -127,10 +80,6 @@ def request_uv(self, permissions, rd_id):

print("Credential created! Writing a blob...")

# If UV is configured, it is required
if auth_data.is_user_verified():
uv = "required"

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

Expand Down
Loading

0 comments on commit 5eead78

Please sign in to comment.