From dfebd6b36087f5f918da8c1af5a3236581cccf2d Mon Sep 17 00:00:00 2001 From: Lgmrszd Date: Mon, 1 Jul 2024 18:42:28 +0300 Subject: [PATCH] Update fido2 to 1.1.3 Adapted changes from https://github.com/solokeys/solo1-cli/pull/169/files --- pyproject.toml | 2 +- solo/cli/key.py | 35 ++++++----------------------------- solo/devices/base.py | 36 ++++++++++++++++++------------------ solo/devices/solo_v1.py | 25 +++++++++++++++++++------ solo/hmac_secret.py | 3 --- solo/solotool.py | 4 ++-- 6 files changed, 46 insertions(+), 59 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b752d01..5e25dbf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ requires = [ "click >= 7.1", "cryptography", "ecdsa", - "fido2 == 0.9.3", + "fido2 == 1.1.3", "intelhex", "pyserial", "pyusb", diff --git a/solo/cli/key.py b/solo/cli/key.py index 113ef95..c66240c 100644 --- a/solo/cli/key.py +++ b/solo/cli/key.py @@ -124,7 +124,6 @@ def feedkernel(count, serial): "--host", help="Relying party's host", default="solokeys.dev", show_default=True ) @click.option("--user", help="User ID", default="they", show_default=True) -@click.option("--pin", help="PIN", default=None) @click.option( "--udp", is_flag=True, default=False, help="Communicate over UDP with software key" ) @@ -134,7 +133,7 @@ def feedkernel(count, serial): default="Touch your authenticator to generate a credential...", show_default=True, ) -def make_credential(serial, host, user, udp, prompt, pin): +def make_credential(serial, host, user, udp, prompt): """Generate a credential. Pass `--prompt ""` to output only the `credential_id` as hex. @@ -142,12 +141,6 @@ def make_credential(serial, host, user, udp, prompt, pin): import solo.hmac_secret - # check for PIN - if not pin: - pin = getpass.getpass("PIN (leave empty for no PIN): ") - if not pin: - pin = None - solo.hmac_secret.make_credential( host=host, user_id=user, @@ -155,7 +148,6 @@ def make_credential(serial, host, user, udp, prompt, pin): output=True, prompt=prompt, udp=udp, - pin=pin, ) @@ -163,7 +155,6 @@ def make_credential(serial, host, user, udp, prompt, pin): @click.option("-s", "--serial", help="Serial number of Solo use") @click.option("--host", help="Relying party's host", default="solokeys.dev") @click.option("--user", help="User ID", default="they") -@click.option("--pin", help="PIN", default=None) @click.option( "--udp", is_flag=True, default=False, help="Communicate over UDP with software key" ) @@ -175,7 +166,7 @@ def make_credential(serial, host, user, udp, prompt, pin): ) @click.argument("credential-id") @click.argument("challenge") -def challenge_response(serial, host, user, prompt, credential_id, challenge, udp, pin): +def challenge_response(serial, host, user, prompt, credential_id, challenge, udp): """Uses `hmac-secret` to implement a challenge-response mechanism. We abuse hmac-secret, which gives us `HMAC(K, hash(challenge))`, where `K` @@ -192,11 +183,6 @@ def challenge_response(serial, host, user, prompt, credential_id, challenge, udp import solo.hmac_secret - # check for PIN - if not pin: - pin = getpass.getpass("PIN (leave empty for no PIN): ") - if not pin: - pin = None solo.hmac_secret.simple_secret( credential_id, @@ -207,7 +193,6 @@ def challenge_response(serial, host, user, prompt, credential_id, challenge, udp prompt=prompt, output=True, udp=udp, - pin=pin, ) @@ -234,10 +219,10 @@ def probe(serial, udp, hash_type, filename): p = solo.client.find(serial, udp=udp) import fido2 - serialized_command = fido2.cbor.dumps({"subcommand": hash_type, "data": data}) + serialized_command = fido2.cbor.encode({"subcommand": hash_type, "data": data}) from solo.commands import SoloBootloader - result = p.send_data_hid(SoloBootloader.HIDCommandProbe, serialized_command) + result = p.send_data_hid(SoloBootloader.CommandProbe, serialized_command) result_hex = result.hex() print(result_hex) if hash_type == "Ed25519": @@ -339,28 +324,20 @@ def set_pin(serial): @click.command() -@click.option("--pin", help="PIN for to access key") @click.option("-s", "--serial", help="Serial number of Solo to use") @click.option( "--udp", is_flag=True, default=False, help="Communicate over UDP with software key" ) -def verify(pin, serial, udp): +def verify(serial, udp): """Verify key is valid""" key = solo.client.find(serial, udp=udp) - if ( - key.client - and ("clientPin" in key.client.info.options) - and key.client.info.options["clientPin"] - and not pin - ): - pin = getpass.getpass("PIN: ") # Any longer and this needs to go in a submodule print("Please press the button on your Solo key") try: - cert = key.make_credential(pin=pin) + cert = key.make_credential() except Fido2ClientError as e: cause = str(e.cause) if "PIN required" in cause: diff --git a/solo/devices/base.py b/solo/devices/base.py index 30fc54d..78b4337 100644 --- a/solo/devices/base.py +++ b/solo/devices/base.py @@ -3,7 +3,8 @@ from cryptography import x509 from cryptography.hazmat.backends import default_backend from fido2.attestation import Attestation -from fido2.ctap2 import CTAP2, CredentialManagement +from fido2.ctap2 import Ctap2, CredentialManagement +from fido2.ctap2.pin import ClientPin from fido2.hid import CTAPHID from fido2.utils import hmac_sha256 from fido2.webauthn import PublicKeyCredentialCreationOptions @@ -76,17 +77,17 @@ def ping(self, data="pong"): def reset( self, ): - CTAP2(self.get_current_hid_device()).reset() + Ctap2(self.get_current_hid_device()).reset() def change_pin(self, old_pin, new_pin): - client = self.get_current_fido_client() - client.client_pin.change_pin(old_pin, new_pin) + client_pin = ClientPin(self.ctap2) + client_pin.change_pin(old_pin, new_pin) def set_pin(self, new_pin): - client = self.get_current_fido_client() - client.client_pin.set_pin(new_pin) + client_pin = ClientPin(self.ctap2) + client_pin.set_pin(new_pin) - def make_credential(self, pin=None): + def make_credential(self): client = self.get_current_fido_client() rp = {"id": self.host, "name": "example site"} user = {"id": self.user_id, "name": "example user"} @@ -97,25 +98,24 @@ def make_credential(self, pin=None): challenge, [{"type": "public-key", "alg": -8}, {"type": "public-key", "alg": -7}], ) - result = client.make_credential(options, pin=pin) + result = client.make_credential(options) attest = result.attestation_object data = result.client_data try: attest.verify(data.hash) except AttributeError: verifier = Attestation.for_type(attest.fmt) - verifier().verify(attest.att_statement, attest.auth_data, data.hash) + verifier().verify(attest.att_stmt, attest.auth_data, data.hash) print("Register valid") - x5c = attest.att_statement["x5c"][0] + x5c = attest.att_stmt["x5c"][0] cert = x509.load_der_x509_certificate(x5c, default_backend()) return cert def cred_mgmt(self, pin): - client = self.get_current_fido_client() - token = client.client_pin.get_pin_token(pin) - ctap2 = CTAP2(self.get_current_hid_device()) - return CredentialManagement(ctap2, client.client_pin.protocol, token) + client_pin = ClientPin(self.ctap2) + token = client_pin.get_pin_token(pin) + return CredentialManagement(self.ctap2, client_pin.protocol, token) def enter_solo_bootloader( self, @@ -137,14 +137,14 @@ def is_solo_bootloader( pass def program_kbd(self, cmd): - ctap2 = CTAP2(self.get_current_hid_device()) + ctap2 = Ctap2(self.get_current_hid_device()) return ctap2.send_cbor(0x51, cmd) def sign_hash(self, credential_id, dgst, pin): - ctap2 = CTAP2(self.get_current_hid_device()) - client = self.get_current_fido_client() + ctap2 = Ctap2(self.get_current_hid_device()) + client_pin = ClientPin(ctap2) if pin: - pin_token = client.client_pin.get_pin_token(pin) + pin_token = client_pin.get_pin_token(pin) pin_auth = hmac_sha256(pin_token, dgst)[:16] return ctap2.send_cbor( 0x50, diff --git a/solo/devices/solo_v1.py b/solo/devices/solo_v1.py index 5752f99..1d4224a 100644 --- a/solo/devices/solo_v1.py +++ b/solo/devices/solo_v1.py @@ -6,12 +6,13 @@ import time from threading import Event -from fido2.client import Fido2Client +from fido2.client import Fido2Client, UserInteraction from fido2.ctap import CtapError -from fido2.ctap1 import CTAP1 -from fido2.ctap2 import CTAP2 +from fido2.ctap1 import Ctap1 +from fido2.ctap2 import Ctap2 from fido2.hid import CTAPHID, CtapHidDevice from intelhex import IntelHex +from getpass import getpass import solo from solo import helpers @@ -19,6 +20,18 @@ from .base import SoloClient +# 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 + class Client(SoloClient): def __init__( @@ -66,14 +79,14 @@ def find_device(self, dev=None, solo_serial=None): dev = devices[0] self.dev = dev - self.ctap1 = CTAP1(dev) + self.ctap1 = Ctap1(dev) try: - self.ctap2 = CTAP2(dev) + self.ctap2 = Ctap2(dev) except CtapError: self.ctap2 = None try: - self.client = Fido2Client(dev, self.origin) + self.client = Fido2Client(dev, self.origin, user_interaction=CliInteraction()) except CtapError: print("Not using FIDO2 interface.") self.client = None diff --git a/solo/hmac_secret.py b/solo/hmac_secret.py index 1d44c38..50f4a1a 100644 --- a/solo/hmac_secret.py +++ b/solo/hmac_secret.py @@ -21,7 +21,6 @@ def make_credential( host="solokeys.dev", user_id="they", serial=None, - pin=None, prompt="Touch your authenticator to generate a credential...", output=True, udp=False, @@ -66,7 +65,6 @@ def simple_secret( host="solokeys.dev", user_id="they", serial=None, - pin=None, prompt="Touch your authenticator to generate a response...", output=True, udp=False, @@ -100,7 +98,6 @@ def simple_secret( "allowCredentials": allow_list, "extensions": {"hmacGetSecret": {"salt1": salt}}, }, - pin=pin, ).get_response(0) output = assertion.extension_results["hmacGetSecret"]["output1"] diff --git a/solo/solotool.py b/solo/solotool.py index ca01454..9c4cddb 100644 --- a/solo/solotool.py +++ b/solo/solotool.py @@ -32,8 +32,8 @@ from fido2.attestation import Attestation from fido2.client import ClientError, Fido2Client from fido2.ctap import CtapError -from fido2.ctap1 import CTAP1, ApduError -from fido2.ctap2 import CTAP2 +from fido2.ctap1 import Ctap1, ApduError +from fido2.ctap2 import Ctap2 from fido2.hid import CTAPHID, CtapHidDevice from intelhex import IntelHex