core/webauthn: Implement FIDO2 unlocking from softlock.

pull/971/head
Andrew Kozlik 4 years ago committed by matejcik
parent 0f81886c9f
commit b867ac1d01

@ -28,7 +28,17 @@ from apps.webauthn.resident_credentials import (
)
if False:
from typing import Any, Coroutine, Iterable, Iterator, List, Optional, Tuple
from typing import (
Any,
Callable,
Coroutine,
Iterable,
Iterator,
List,
Optional,
Tuple,
Union,
)
_CID_BROADCAST = const(0xFFFFFFFF) # broadcast channel id
@ -756,6 +766,36 @@ class Fido2State(State):
self.finished = True
class Fido2Unlock(Fido2State):
def __init__(
self,
process_func: Callable[[Cmd, "DialogManager"], Union[State, Cmd]],
req: Cmd,
dialog_mgr: "DialogManager",
) -> None:
super().__init__(req.cid, dialog_mgr.iface)
self.process_func = process_func
self.req = req
self.resp = None # type: Optional[Cmd]
self.dialog_mgr = dialog_mgr
async def confirm_dialog(self) -> Union[bool, "State"]:
if not await verify_user(KeepaliveCallback(self.cid, self.iface)):
return False
set_homescreen()
resp = self.process_func(self.req, self.dialog_mgr)
if isinstance(resp, State):
return resp
else:
self.resp = resp
return True
async def on_confirm(self) -> None:
if self.resp:
await send_cmd(self.resp, self.iface)
class Fido2ConfirmMakeCredential(Fido2State, ConfirmInfo):
def __init__(
self,
@ -897,14 +937,14 @@ class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable):
class Fido2ConfirmNoPin(State):
def __init__(self, cid: int, iface: io.HID) -> None:
super().__init__(cid, iface)
self.finished = True
def timeout_ms(self) -> int:
return _FIDO2_CONFIRM_TIMEOUT_MS
async def confirm_dialog(self) -> bool:
cmd = cbor_error(self.cid, _ERR_UNSUPPORTED_OPTION)
await send_cmd(cmd, self.iface)
self.finished = True
text = Text("FIDO2 Verify User", ui.ICON_WRONG, ui.RED)
text.bold("Unable to verify user.")
text.br_half()
@ -1003,9 +1043,8 @@ class DialogManager:
self.state = state
self.reset_timeout()
self.result = _RESULT_NONE
if state.keepalive_status() is not None:
self.keepalive = self.keepalive_loop()
loop.schedule(self.keepalive)
self.keepalive = self.keepalive_loop()
loop.schedule(self.keepalive)
self.workflow = self.dialog_workflow()
loop.schedule(self.workflow)
return True
@ -1427,6 +1466,23 @@ def algorithms_from_pub_key_cred_params(pub_key_cred_params: List[dict]) -> List
def cbor_make_credential(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
if config.is_unlocked():
resp = cbor_make_credential_process(req, dialog_mgr)
else:
resp = Fido2Unlock(cbor_make_credential_process, req, dialog_mgr)
if isinstance(resp, State):
if dialog_mgr.set_state(resp):
return None
else:
return cmd_error(req.cid, _ERR_CHANNEL_BUSY)
else:
return resp
def cbor_make_credential_process(
req: Cmd, dialog_mgr: DialogManager
) -> Union[State, Cmd]:
from apps.webauthn import knownapps
if not storage.is_initialized():
@ -1456,11 +1512,7 @@ def cbor_make_credential(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
excluded_creds = credentials_from_descriptor_list(exclude_list, rp_id_hash)
if not utils.is_empty_iterator(excluded_creds):
# This authenticator is already registered.
if not dialog_mgr.set_state(
Fido2ConfirmExcluded(req.cid, dialog_mgr.iface, cred)
):
return cmd_error(req.cid, _ERR_CHANNEL_BUSY)
return None
return Fido2ConfirmExcluded(req.cid, dialog_mgr.iface, cred)
# Check that the relying party supports ECDSA with SHA-256 or EdDSA. We don't support any other algorithms.
pub_key_cred_params = param[_MAKECRED_CMD_PUB_KEY_CRED_PARAMS]
@ -1520,33 +1572,22 @@ def cbor_make_credential(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
if user_verification and not config.has_pin():
# User verification requested, but PIN is not enabled.
state_set = dialog_mgr.set_state(Fido2ConfirmNoPin(req.cid, dialog_mgr.iface))
if state_set:
return cbor_error(req.cid, _ERR_UNSUPPORTED_OPTION)
else:
return cmd_error(req.cid, _ERR_CHANNEL_BUSY)
return Fido2ConfirmNoPin(req.cid, dialog_mgr.iface)
# Check that the pinAuth parameter is absent. Client PIN is not supported.
if _MAKECRED_CMD_PIN_AUTH in param:
return cbor_error(req.cid, _ERR_PIN_AUTH_INVALID)
# Ask user to confirm registration.
state_set = dialog_mgr.set_state(
Fido2ConfirmMakeCredential(
req.cid,
dialog_mgr.iface,
client_data_hash,
cred,
resident_key,
user_verification,
)
return Fido2ConfirmMakeCredential(
req.cid,
dialog_mgr.iface,
client_data_hash,
cred,
resident_key,
user_verification,
)
if not state_set:
return cmd_error(req.cid, _ERR_CHANNEL_BUSY)
return None
def use_self_attestation(rp_id_hash: bytes) -> bool:
from apps.webauthn import knownapps
@ -1607,6 +1648,23 @@ def cbor_make_credential_sign(
def cbor_get_assertion(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
if config.is_unlocked():
resp = cbor_get_assertion_process(req, dialog_mgr)
else:
resp = Fido2Unlock(cbor_get_assertion_process, req, dialog_mgr)
if isinstance(resp, State):
if dialog_mgr.set_state(resp):
return None
else:
return cmd_error(req.cid, _ERR_CHANNEL_BUSY)
else:
return resp
def cbor_get_assertion_process(
req: Cmd, dialog_mgr: DialogManager
) -> Union[State, Cmd]:
if not storage.is_initialized():
if __debug__:
log.warning(__name__, "not initialized")
@ -1672,18 +1730,12 @@ def cbor_get_assertion(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
if user_verification and not config.has_pin():
# User verification requested, but PIN is not enabled.
state_set = dialog_mgr.set_state(Fido2ConfirmNoPin(req.cid, dialog_mgr.iface))
if state_set:
return cbor_error(req.cid, _ERR_UNSUPPORTED_OPTION)
else:
return cmd_error(req.cid, _ERR_CHANNEL_BUSY)
return Fido2ConfirmNoPin(req.cid, dialog_mgr.iface)
if not cred_list:
# No credentials. This authenticator is not registered.
if user_presence:
state_set = dialog_mgr.set_state(
Fido2ConfirmNoCredentials(req.cid, dialog_mgr.iface, rp_id)
)
return Fido2ConfirmNoCredentials(req.cid, dialog_mgr.iface, rp_id)
else:
return cbor_error(req.cid, _ERR_NO_CREDENTIALS)
elif not user_presence and not user_verification:
@ -1706,23 +1758,16 @@ def cbor_get_assertion(req: Cmd, dialog_mgr: DialogManager) -> Optional[Cmd]:
return cbor_error(req.cid, _ERR_OTHER)
else:
# Ask user to confirm one of the credentials.
state_set = dialog_mgr.set_state(
Fido2ConfirmGetAssertion(
req.cid,
dialog_mgr.iface,
client_data_hash,
cred_list,
hmac_secret,
resident,
user_verification,
)
return Fido2ConfirmGetAssertion(
req.cid,
dialog_mgr.iface,
client_data_hash,
cred_list,
hmac_secret,
resident,
user_verification,
)
if not state_set:
return cmd_error(req.cid, _ERR_CHANNEL_BUSY)
return None
def cbor_get_assertion_hmac_secret(
cred: Credential, hmac_secret: dict

Loading…
Cancel
Save