Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

WIP: Update to fido2 library 1.1 API (fixes #157) #169

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
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
51 changes: 5 additions & 46 deletions solo/cli/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,58 +124,35 @@ 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"
)
@click.option(
"--prompt",
help="Prompt for user",
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):
"""Generate a credential.

Pass `--prompt ""` to output only the `credential_id` as hex.
"""

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,
serial=serial,
output=True,
prompt=prompt,
udp=udp,
pin=pin,
)


@click.command()
@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"
)
@click.option(
"--prompt",
help="Prompt for user",
default="Touch your authenticator to generate a reponse...",
show_default=True,
)
@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, 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`
Expand All @@ -187,27 +164,18 @@ def challenge_response(serial, host, user, prompt, credential_id, challenge, udp

If so desired, user and relying party can be changed from the defaults.

The prompt can be suppressed using `--prompt ""`.
"""

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,
challenge,
host=host,
user_id=user,
serial=serial,
prompt=prompt,
output=True,
udp=udp,
pin=pin,
)


Expand All @@ -234,10 +202,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":
Expand Down Expand Up @@ -349,18 +317,9 @@ def verify(pin, serial, udp):

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:
Expand Down
36 changes: 18 additions & 18 deletions solo/devices/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = ClientPin(self.ctap2)
client.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 = ClientPin(self.ctap2)
client.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"}
Expand All @@ -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 = ClientPin(self.ctap2)
token = client.get_pin_token(pin)
return CredentialManagement(self.ctap2, client.protocol, token)

def enter_solo_bootloader(
self,
Expand All @@ -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 = ClientPin(ctap2)
if pin:
pin_token = client.client_pin.get_pin_token(pin)
pin_token = client.get_pin_token(pin)
pin_auth = hmac_sha256(pin_token, dgst)[:16]
return ctap2.send_cbor(
0x50,
Expand Down
24 changes: 18 additions & 6 deletions solo/devices/solo_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,29 @@
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

from .. import exceptions, helpers
from ..commands import SoloBootloader, SoloExtension
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__(
Expand Down Expand Up @@ -64,14 +76,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
Expand Down
12 changes: 0 additions & 12 deletions solo/hmac_secret.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +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,
):
Expand All @@ -36,9 +34,6 @@ def make_credential(
user = {"id": user_id, "name": "A. User"}
challenge = secrets.token_bytes(32)

if prompt:
print(prompt)

attestation_object = client.make_credential(
{
"rp": rp,
Expand All @@ -50,7 +45,6 @@ def make_credential(
],
"extensions": {"hmacCreateSecret": True},
},
pin=pin,
).attestation_object

credential = attestation_object.auth_data.credential_data
Expand All @@ -67,8 +61,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,
):
Expand All @@ -91,17 +83,13 @@ def simple_secret(
h.update(secret_input.encode())
salt = h.digest()

if prompt:
print(prompt)

assertion = client.get_assertion(
{
"rpId": host,
"challenge": challenge,
"allowCredentials": allow_list,
"extensions": {"hmacGetSecret": {"salt1": salt}},
},
pin=pin,
).get_response(0)

output = assertion.extension_results["hmacGetSecret"]["output1"]
Expand Down
4 changes: 2 additions & 2 deletions solo/solotool.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down