Skip to content

Commit 5883e7e

Browse files
committed
Refactored ctap user verifiers
- Windows hello ctap user verifier is now based on winrt (vs winbio) - Added flow images to readme.rst
1 parent e43b5e4 commit 5883e7e

19 files changed

+191
-389
lines changed

.coveragerc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
[run]
22
omit =
33
.tox/*
4-
ctap_keyring_device/touch_id_helpers.py
5-
ctap_keyring_device/windows_hello_helpers.py
4+
ctap_keyring_device/user_verifiers/*
65

76
branch = True
87

README.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,25 @@ See examples in ``ctap-keyring-device/tests``.
110110

111111
|
112112
113+
CTAP Flow Diagrams
114+
==================
115+
116+
Make Credential Flow
117+
********************
118+
119+
.. image:: images/make-credential-flow.png
120+
:alt: Make Credential Flow
121+
122+
|
123+
124+
Get Assertion Flow
125+
******************
126+
127+
.. image:: images/get-assertion-flow.png
128+
:alt: Get Assertion Flow
129+
130+
|
131+
113132
Security Considerations
114133
=======================
115134

ctap_keyring_device/ctap_keyring_device.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333
Credential,
3434
CtapGetAssertionRequest,
3535
)
36-
from ctap_keyring_device.ctap_user_verifiers import CtapUserVerifier
36+
from ctap_keyring_device.user_verifiers.ctap_user_verifier_factory import (
37+
CtapUserVerifierFactory,
38+
)
3739

3840

3941
class CtapKeyringDevice(ctap.CtapDevice):
@@ -92,7 +94,7 @@ def __init__(self):
9294
)
9395

9496
self._next_assertions_ctx: Optional[CtapGetNextAssertionContext] = None
95-
self._user_verifier = CtapUserVerifier.create()
97+
self._user_verifier = CtapUserVerifierFactory.create()
9698

9799
@classmethod
98100
def list_devices(cls):

ctap_keyring_device/ctap_user_verifiers.py

Lines changed: 0 additions & 99 deletions
This file was deleted.

ctap_keyring_device/user_verifiers/__init__.py

Whitespace-only changes.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# -*- coding: utf-8 -*-
2+
import abc
3+
4+
# noinspection PyBroadException
5+
try:
6+
from ctap_keyring_device.user_verifiers.windows_hello_ctap_user_verifier import (
7+
WindowsHelloCtapUserVerifier,
8+
)
9+
except Exception:
10+
WindowsHelloCtapUserVerifier = None
11+
12+
# noinspection PyBroadException
13+
try:
14+
from ctap_keyring_device.user_verifiers.touch_id_ctap_user_verifier import (
15+
TouchIdCtapUserVerifier,
16+
)
17+
except Exception:
18+
TouchIdCtapUserVerifier = None
19+
20+
21+
class CtapUserVerifier(metaclass=abc.ABCMeta):
22+
"""
23+
Implementors of this interface supply a user verification and presence scheme.
24+
25+
This could be with biometrics, face-recognition, password prompt, or otherwise.
26+
"""
27+
28+
@abc.abstractmethod
29+
def available(self) -> bool:
30+
""" If set, this verifier is available on the current OS """
31+
raise NotImplementedError()
32+
33+
@abc.abstractmethod
34+
def verify_user(self, rp_id: str) -> bool:
35+
""" Returns true if the user was successfully verified """
36+
raise NotImplementedError()
37+
38+
39+
class CtapUserVerifierBase(CtapUserVerifier, metaclass=abc.ABCMeta):
40+
"""A base class for user verifiers - implemented methods may throw, in which case False is returned
41+
in both available() and verify_user()"""
42+
43+
def available(self) -> bool:
44+
# noinspection PyBroadException
45+
try:
46+
return self._available()
47+
except Exception:
48+
return False
49+
50+
@abc.abstractmethod
51+
def _available(self) -> bool:
52+
raise NotImplementedError()
53+
54+
def verify_user(self, rp_id: str) -> bool:
55+
if not self.available():
56+
return False
57+
58+
# noinspection PyBroadException
59+
try:
60+
return self._verify_user(rp_id)
61+
except Exception:
62+
return False
63+
64+
@abc.abstractmethod
65+
def _verify_user(self, rp_id: str) -> bool:
66+
raise NotImplementedError()
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# -*- coding: utf-8 -*-
2+
import platform
3+
4+
from ctap_keyring_device.user_verifiers.ctap_user_verifier import CtapUserVerifier
5+
from ctap_keyring_device.user_verifiers.noop_ctap_user_verifier import (
6+
NoopCtapUserVerifier,
7+
)
8+
9+
try:
10+
from ctap_keyring_device.user_verifiers.touch_id_ctap_user_verifier import (
11+
TouchIdCtapUserVerifier,
12+
)
13+
except ImportError:
14+
TouchIdCtapUserVerifier = None
15+
16+
try:
17+
from ctap_keyring_device.user_verifiers.windows_hello_ctap_user_verifier import (
18+
WindowsHelloCtapUserVerifier,
19+
)
20+
except ImportError:
21+
WindowsHelloCtapUserVerifier = None
22+
23+
24+
class CtapUserVerifierFactory:
25+
"""Creates a concrete instance of a CtapUserVerifier implementation, depending on the platform and available
26+
user presence detection mechanisms available"""
27+
28+
@staticmethod
29+
def create() -> CtapUserVerifier:
30+
system = platform.system()
31+
if system == 'Darwin' and TouchIdCtapUserVerifier is not None:
32+
touch_id_verifier = TouchIdCtapUserVerifier()
33+
if touch_id_verifier.available():
34+
return touch_id_verifier
35+
36+
if system == 'Windows' and WindowsHelloCtapUserVerifier is not None:
37+
windows_hello_verifier = WindowsHelloCtapUserVerifier()
38+
if windows_hello_verifier.available():
39+
return windows_hello_verifier
40+
41+
return NoopCtapUserVerifier()
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# -*- coding: utf-8 -*-
2+
from ctap_keyring_device.user_verifiers.ctap_user_verifier import CtapUserVerifierBase
3+
4+
5+
class NoopCtapUserVerifier(CtapUserVerifierBase):
6+
""" Dummy verifier - always returns true """
7+
8+
def _available(self) -> bool:
9+
return True
10+
11+
def _verify_user(self, rp_id: str) -> bool:
12+
return True

ctap_keyring_device/touch_id_helpers.py renamed to ctap_keyring_device/user_verifiers/touch_id_ctap_user_verifier.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,26 @@
33
# Taken from and slightly modified:
44
# https://raw.githubusercontent.com/lukaskollmer/python-touch-id/master/touchid.py (MIT License)
55

6+
from threading import Event
7+
68
# noinspection PyUnresolvedReferences
79
from LocalAuthentication import LAContext, LAPolicyDeviceOwnerAuthentication
8-
from threading import Event
10+
11+
from ctap_keyring_device.user_verifiers.ctap_user_verifier import CtapUserVerifierBase
912

1013

11-
class TouchId:
14+
class TouchIdCtapUserVerifier(CtapUserVerifierBase):
15+
""" A Touch ID based CTAP User Verifier - Prompts for the user's login password / fingerprint """
16+
1217
LA_POLICY = LAPolicyDeviceOwnerAuthentication
1318

1419
def __init__(self):
1520
self._context = LAContext.new()
1621

17-
def available(self) -> bool:
22+
def _available(self) -> bool:
1823
return self._context.canEvaluatePolicy_error_(self.LA_POLICY, None)[0]
1924

20-
def verify(self, reason) -> bool:
21-
if not self.available():
22-
raise Exception("Touch ID isn't available on this machine")
23-
25+
def _verify_user(self, rp_id: str) -> bool:
2426
success, err, event = False, None, Event()
2527

2628
def cb(_success, _error):
@@ -31,10 +33,9 @@ def cb(_success, _error):
3133

3234
event.set()
3335

34-
self._context.evaluatePolicy_localizedReason_reply_(self.LA_POLICY, reason, cb)
36+
self._context.evaluatePolicy_localizedReason_reply_(
37+
self.LA_POLICY, 'verify ctap user identity of ' + rp_id, cb
38+
)
3539

3640
event.wait()
37-
if err:
38-
raise RuntimeError(err)
39-
40-
return success
41+
return err is None and success
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# noinspection PyUnresolvedReferences
4+
import winrt.windows.security.credentials.ui as ui
5+
import asyncio
6+
7+
from ctap_keyring_device.user_verifiers.ctap_user_verifier import CtapUserVerifierBase
8+
9+
10+
class WindowsHelloCtapUserVerifier(CtapUserVerifierBase):
11+
""" A Windows Hello based CTAP User verifier; Prompts for a PIN / fingerprint """
12+
13+
def __init__(self):
14+
self._event_loop = asyncio.get_event_loop()
15+
16+
def _available(self) -> bool:
17+
fut = ui.UserConsentVerifier.check_availability_async()
18+
res = self._event_loop.run_until_complete(fut)
19+
20+
return res == ui.UserConsentVerifierAvailability.AVAILABLE
21+
22+
def _verify_user(self, rp_id: str) -> bool:
23+
fut = ui.UserConsentVerifier.request_verification_async(
24+
'verify ctap user identity of ' + rp_id
25+
)
26+
res = self._event_loop.run_until_complete(fut)
27+
28+
return res == ui.UserConsentVerificationResult.VERIFIED

0 commit comments

Comments
 (0)