Skip to content

Commit

Permalink
initial working state for Nitrokey#60, updating to upstream fido2 lib
Browse files Browse the repository at this point in the history
  • Loading branch information
daringer committed Jun 3, 2021
1 parent c6a93da commit 8b77234
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 204 deletions.
224 changes: 114 additions & 110 deletions pynitrokey/cli/_patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,113 +11,117 @@

import sys

## Windows
if sys.platform.startswith("win32"):
import fido2._pyu2f.windows

oldDevAttrFunc = fido2._pyu2f.windows.FillDeviceAttributes
from ctypes import wintypes
import ctypes

fido2._pyu2f.windows.hid.HidD_GetSerialNumberString.restype = wintypes.BOOLEAN
fido2._pyu2f.windows.hid.HidD_GetSerialNumberString.argtypes = [
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_ulong,
]

def newDevAttrFunc(device, descriptor):
oldDevAttrFunc(device, descriptor)
buf_ser = ctypes.create_string_buffer(1024)
result = fido2._pyu2f.windows.hid.HidD_GetSerialNumberString(
device, buf_ser, 1024
)
if result:
descriptor.serial_number = ctypes.wstring_at(buf_ser)

fido2._pyu2f.windows.FillDeviceAttributes = newDevAttrFunc


## macOS
if sys.platform.startswith("darwin"):
import fido2._pyu2f.macos
from fido2._pyu2f import base
from fido2._pyu2f.macos import (
iokit,
IO_HID_DEVICE_REF,
GetDeviceIntProperty,
GetDevicePath,
GetDeviceStringProperty,
HID_DEVICE_PROPERTY_VENDOR_ID,
HID_DEVICE_PROPERTY_PRODUCT_ID,
HID_DEVICE_PROPERTY_PRODUCT,
HID_DEVICE_PROPERTY_PRIMARY_USAGE,
HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE,
HID_DEVICE_PROPERTY_REPORT_ID,
cf,
)

HID_DEVICE_PROPERTY_SERIAL_NUMBER = b"SerialNumber"

def newEnumerate():
"""See base class."""
# Init a HID manager
hid_mgr = iokit.IOHIDManagerCreate(None, None)
if not hid_mgr:
raise OSError("Unable to obtain HID manager reference")
iokit.IOHIDManagerSetDeviceMatching(hid_mgr, None)

# Get devices from HID manager
device_set_ref = iokit.IOHIDManagerCopyDevices(hid_mgr)
if not device_set_ref:
raise OSError("Failed to obtain devices from HID manager")

num = iokit.CFSetGetCount(device_set_ref)
devices = (IO_HID_DEVICE_REF * num)()
iokit.CFSetGetValues(device_set_ref, devices)

# Retrieve and build descriptor dictionaries for each device
descriptors = []
for dev in devices:
d = base.DeviceDescriptor()
d.vendor_id = GetDeviceIntProperty(dev, HID_DEVICE_PROPERTY_VENDOR_ID)
d.product_id = GetDeviceIntProperty(dev, HID_DEVICE_PROPERTY_PRODUCT_ID)
d.product_string = GetDeviceStringProperty(dev, HID_DEVICE_PROPERTY_PRODUCT)
d.serial_number = GetDeviceStringProperty(
dev, HID_DEVICE_PROPERTY_SERIAL_NUMBER
)
d.usage = GetDeviceIntProperty(dev, HID_DEVICE_PROPERTY_PRIMARY_USAGE)
d.usage_page = GetDeviceIntProperty(
dev, HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE
)
d.report_id = GetDeviceIntProperty(dev, HID_DEVICE_PROPERTY_REPORT_ID)
d.path = GetDevicePath(dev)
descriptors.append(d.ToPublicDict())

# Clean up CF objects
cf.CFRelease(device_set_ref)
cf.CFRelease(hid_mgr)

return descriptors

fido2._pyu2f.macos.MacOsHidDevice.Enumerate = newEnumerate


## Linux
if sys.platform.startswith("linux"):
import fido2._pyu2f.linux

oldnewParseUevent = fido2._pyu2f.linux.ParseUevent

def newParseUevent(uevent, desc):
oldnewParseUevent(uevent, desc)
lines = uevent.split(b"\n")
for line in lines:
line = line.strip()
if not line:
continue
k, v = line.split(b"=")
if k == b"HID_UNIQ":
desc.serial_number = v.decode("utf8")

fido2._pyu2f.linux.ParseUevent = newParseUevent
########################################################
# removed as fido._pyu2f is not part of fido2 anymore...
####################################################

# ## Windows
# if sys.platform.startswith("win32"):
# import fido2._pyu2f.windows

# oldDevAttrFunc = fido2._pyu2f.windows.FillDeviceAttributes
# from ctypes import wintypes
# import ctypes

# fido2._pyu2f.windows.hid.HidD_GetSerialNumberString.restype = wintypes.BOOLEAN
# fido2._pyu2f.windows.hid.HidD_GetSerialNumberString.argtypes = [
# ctypes.c_void_p,
# ctypes.c_void_p,
# ctypes.c_ulong,
# ]

# def newDevAttrFunc(device, descriptor):
# oldDevAttrFunc(device, descriptor)
# buf_ser = ctypes.create_string_buffer(1024)
# result = fido2._pyu2f.windows.hid.HidD_GetSerialNumberString(
# device, buf_ser, 1024
# )
# if result:
# descriptor.serial_number = ctypes.wstring_at(buf_ser)

# fido2._pyu2f.windows.FillDeviceAttributes = newDevAttrFunc


# ## macOS
# if sys.platform.startswith("darwin"):
# import fido2._pyu2f.macos
# from fido2._pyu2f import base
# from fido2._pyu2f.macos import (
# iokit,
# IO_HID_DEVICE_REF,
# GetDeviceIntProperty,
# GetDevicePath,
# GetDeviceStringProperty,
# HID_DEVICE_PROPERTY_VENDOR_ID,
# HID_DEVICE_PROPERTY_PRODUCT_ID,
# HID_DEVICE_PROPERTY_PRODUCT,
# HID_DEVICE_PROPERTY_PRIMARY_USAGE,
# HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE,
# HID_DEVICE_PROPERTY_REPORT_ID,
# cf,
# )

# HID_DEVICE_PROPERTY_SERIAL_NUMBER = b"SerialNumber"

# def newEnumerate():
# """See base class."""
# # Init a HID manager
# hid_mgr = iokit.IOHIDManagerCreate(None, None)
# if not hid_mgr:
# raise OSError("Unable to obtain HID manager reference")
# iokit.IOHIDManagerSetDeviceMatching(hid_mgr, None)

# # Get devices from HID manager
# device_set_ref = iokit.IOHIDManagerCopyDevices(hid_mgr)
# if not device_set_ref:
# raise OSError("Failed to obtain devices from HID manager")

# num = iokit.CFSetGetCount(device_set_ref)
# devices = (IO_HID_DEVICE_REF * num)()
# iokit.CFSetGetValues(device_set_ref, devices)

# # Retrieve and build descriptor dictionaries for each device
# descriptors = []
# for dev in devices:
# d = base.DeviceDescriptor()
# d.vendor_id = GetDeviceIntProperty(dev, HID_DEVICE_PROPERTY_VENDOR_ID)
# d.product_id = GetDeviceIntProperty(dev, HID_DEVICE_PROPERTY_PRODUCT_ID)
# d.product_string = GetDeviceStringProperty(dev, HID_DEVICE_PROPERTY_PRODUCT)
# d.serial_number = GetDeviceStringProperty(
# dev, HID_DEVICE_PROPERTY_SERIAL_NUMBER
# )
# d.usage = GetDeviceIntProperty(dev, HID_DEVICE_PROPERTY_PRIMARY_USAGE)
# d.usage_page = GetDeviceIntProperty(
# dev, HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE
# )
# d.report_id = GetDeviceIntProperty(dev, HID_DEVICE_PROPERTY_REPORT_ID)
# d.path = GetDevicePath(dev)
# descriptors.append(d.ToPublicDict())

# # Clean up CF objects
# cf.CFRelease(device_set_ref)
# cf.CFRelease(hid_mgr)

# return descriptors

# fido2._pyu2f.macos.MacOsHidDevice.Enumerate = newEnumerate


# ## Linux
# if sys.platform.startswith("linux"):
# import fido2._pyu2f.linux

# oldnewParseUevent = fido2._pyu2f.linux.ParseUevent

# def newParseUevent(uevent, desc):
# oldnewParseUevent(uevent, desc)
# lines = uevent.split(b"\n")
# for line in lines:
# line = line.strip()
# if not line:
# continue
# k, v = line.split(b"=")
# if k == b"HID_UNIQ":
# desc.serial_number = v.decode("utf8")

# fido2._pyu2f.linux.ParseUevent = newParseUevent
8 changes: 2 additions & 6 deletions pynitrokey/cli/fido2.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,12 +281,11 @@ def feedkernel(count, serial):
show_default=True,
)
def make_credential(serial, host, user, udp, prompt):
"""(EXPERIMENTAL) Generate a credential.
"""Generate a credential.
Pass `--prompt ""` to output only the `credential_id` as hex.
"""

local_print("EXPERIMENTAL: use with care, not a fully supported function")
nkfido2.hmac_secret.make_credential(
host=host, user_id=user, serial=serial, output=True, prompt=prompt, udp=udp
)
Expand All @@ -309,7 +308,7 @@ def make_credential(serial, host, user, udp, prompt):
@click.argument("credential-id")
@click.argument("challenge")
def challenge_response(serial, host, user, prompt, credential_id, challenge, udp):
"""(EXPERIMENTAL) Uses `hmac-secret` to implement a challenge-response mechanism.
"""Uses `hmac-secret` to implement a challenge-response mechanism.
We abuse hmac-secret, which gives us `HMAC(K, hash(challenge))`, where `K`
is a secret tied to the `credential_id`. We hash the challenge first, since
Expand All @@ -323,9 +322,6 @@ def challenge_response(serial, host, user, prompt, credential_id, challenge, udp
The prompt can be suppressed using `--prompt ""`.
"""

local_print("EXPERIMENTAL: Currently disabled: challenge-response")
return

nkfido2.hmac_secret.simple_secret(
credential_id,
challenge,
Expand Down
53 changes: 0 additions & 53 deletions pynitrokey/fido2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@
import socket
import usb


import fido2._pyu2f
import fido2._pyu2f.base

from pynitrokey.fido2 import hmac_secret
from pynitrokey.fido2.client import NKFido2Client
from pynitrokey.exceptions import NoSoloFoundError
Expand All @@ -32,60 +28,11 @@ def _UDP_InternalPlatformSwitch(funcname, *args, **kwargs):
return getattr(HidOverUDP, funcname)(*args, **kwargs)


def force_udp_backend():
fido2._pyu2f.InternalPlatformSwitch = _UDP_InternalPlatformSwitch


class HidOverUDP(fido2._pyu2f.base.HidDevice):
@staticmethod
def Enumerate():
a = [
{
"vendor_id": 0x1234,
"product_id": 0x5678,
"product_string": "software test interface",
"serial_number": "12345678",
"usage": 0x01,
"usage_page": 0xF1D0,
"path": "localhost:8111",
}
]
return a

def __init__(self, path):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.bind(("127.0.0.1", 7112))
addr, port = path.split(":")
port = int(port)
self.token = (addr, port)
self.sock.settimeout(1.0)

def GetInReportDataLength(self):
return 64

def GetOutReportDataLength(self):
return 64

def Write(self, packet):
self.sock.sendto(bytearray(packet), self.token)

def Read(self):
msg = [0] * 64
pkt, _ = self.sock.recvfrom(64)
for i, v in enumerate(pkt):
try:
msg[i] = ord(v)
except TypeError:
msg[i] = v
return msg


def find(solo_serial=None, retries=5, raw_device=None, udp=False):
if udp:
force_udp_backend()



p = NKFido2Client()

# This... is not the right way to do it yet
Expand Down
26 changes: 17 additions & 9 deletions pynitrokey/fido2/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,18 +185,20 @@ def wink(self,):
def reset(self,):
self.ctap2.reset()

# @todo: unneeded, remove this...
def make_credential(self, pin=None):
client = self.get_current_fido_client()
rp = {"id": self.host, "name": "example site"}
user = {"id": self.user_id, "name": "example user"}
challenge = "Y2hhbGxlbmdl"
attest, data = self.client.make_credential({
"rp": rp,
"user": user,
"challenge": challenge.encode("utf8"),
"pubKeyCredParams": [{"type": "public-key", "alg": -7}],
}, pin=pin)

challenge = b"Y2hhbGxlbmdl"
options = PublicKeyCredentialCreationOptions(
rp,
user,
challenge,
[{"type": "public-key", "alg": -8}, {"type": "public-key", "alg": -7}],
)
result = client.make_credential(options, pin=pin)
attest = result.attestation_object
data = result.client_data
try:
attest.verify(data.hash)
except AttributeError:
Expand All @@ -208,6 +210,12 @@ def make_credential(self, pin=None):

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)

def enter_solo_bootloader(self,):
"""
If Nitrokey is configured as Nitrokey hacker or something similar,
Expand Down
Loading

0 comments on commit 8b77234

Please sign in to comment.