diff --git a/pynitrokey/cli/__init__.py b/pynitrokey/cli/__init__.py index d704fe9e..ad468bd0 100644 --- a/pynitrokey/cli/__init__.py +++ b/pynitrokey/cli/__init__.py @@ -31,8 +31,6 @@ from pynitrokey.confconsts import LOG_FN, LOG_FORMAT from pynitrokey.helpers import filter_sensitive_parameters, local_critical -# from . import _patches # noqa (since otherwise "unused") - logger = logging.getLogger(__name__) diff --git a/pynitrokey/cli/_patches.py b/pynitrokey/cli/_patches.py deleted file mode 100644 index b2a311ed..00000000 --- a/pynitrokey/cli/_patches.py +++ /dev/null @@ -1,125 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2019 SoloKeys Developers -# -# Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -# copied, modified, or distributed except according to those terms. - -"""Monkey patch FIDO2 backend to get serial number.""" - -######################################################## -# 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 diff --git a/pynitrokey/fido2/__init__.py b/pynitrokey/fido2/__init__.py index 16d2fe09..d9fae802 100644 --- a/pynitrokey/fido2/__init__.py +++ b/pynitrokey/fido2/__init__.py @@ -5,33 +5,9 @@ from fido2.hid import CtapHidDevice from pynitrokey.exceptions import NoSoloFoundError - -# from pynitrokey.fido2 import hmac_secret from pynitrokey.fido2.client import NKFido2Client -def hot_patch_windows_libusb() -> None: - # hot patch for windows libusb backend - olddel = usb._objfinalizer._AutoFinalizedObjectBase.__del__ - - def newdel(self): # type: ignore - try: - olddel(self) - except OSError: - pass - - usb._objfinalizer._AutoFinalizedObjectBase.__del__ = newdel - - -# @todo: remove this, HidOverUDP is not available anymore! -def _UDP_InternalPlatformSwitch( - funcname: str, *args: tuple[Any, Any], **kwargs: dict[Any, Any] -) -> None: - if funcname == "__init__": - return HidOverUDP(*args, **kwargs) # type: ignore - return getattr(HidOverUDP, funcname)(*args, **kwargs) # type: ignore - - def find( solo_serial: Optional[str] = None, retries: int = 5, diff --git a/pynitrokey/fido2/client.py b/pynitrokey/fido2/client.py index 4e6bb006..b951ef2b 100644 --- a/pynitrokey/fido2/client.py +++ b/pynitrokey/fido2/client.py @@ -423,20 +423,6 @@ def is_bootloader(self) -> bool: pass return False - def enter_st_dfu(self) -> None: - """ - If Nitrokey is configured as Nitrokey hacker or something similar, - this command will tell the token to boot directly to the st DFU - so it can be reprogrammed. Warning, you could brick your device. - """ - boot = self.is_bootloader() - - if boot or self.exchange == self.exchange_u2f: - req = NKFido2Client.format_request(SoloBootloader.st_dfu) - self.send_only_hid(SoloBootloader.HIDCommandBoot, req) - else: - self.send_only_hid(SoloBootloader.HIDCommandEnterSTBoot, b"") - def program_file(self, name: str) -> bytes: def parseField(f: str) -> bytes: return base64.b64decode(helpers.from_websafe(f).encode()) diff --git a/pynitrokey/fido2/commands.py b/pynitrokey/fido2/commands.py index f14fd01d..b12be03d 100644 --- a/pynitrokey/fido2/commands.py +++ b/pynitrokey/fido2/commands.py @@ -8,12 +8,6 @@ # copied, modified, or distributed except according to those terms. -class STM32L4: - class options: - nBOOT0 = 1 << 27 - nSWBOOT0 = 1 << 26 - - class SoloExtension: version = 0x14 rng = 0x15 @@ -38,38 +32,3 @@ class SoloBootloader: HIDCommandStatus = 0x71 TAG = b"\x8C\x27\x90\xf6" - - -class DFU: - class type: - SEND = 0x21 - RECEIVE = 0xA1 - - class bmReq: - DETACH = 0x00 - DNLOAD = 0x01 - UPLOAD = 0x02 - GETSTATUS = 0x03 - CLRSTATUS = 0x04 - GETSTATE = 0x05 - ABORT = 0x06 - - class state: - APP_IDLE = 0x00 - APP_DETACH = 0x01 - IDLE = 0x02 - DOWNLOAD_SYNC = 0x03 - DOWNLOAD_BUSY = 0x04 - DOWNLOAD_IDLE = 0x05 - MANIFEST_SYNC = 0x06 - MANIFEST = 0x07 - MANIFEST_WAIT_RESET = 0x08 - UPLOAD_IDLE = 0x09 - ERROR = 0x0A - - class status: - def __init__(self, s: bytes): - self.status = s[0] - self.timeout = s[1] + (s[2] << 8) + (s[3] << 16) - self.state = s[4] - self.istring = s[5] diff --git a/pynitrokey/fido2/dfu.py b/pynitrokey/fido2/dfu.py deleted file mode 100644 index d6154719..00000000 --- a/pynitrokey/fido2/dfu.py +++ /dev/null @@ -1,263 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2019 SoloKeys Developers -# -# Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -# copied, modified, or distributed except according to those terms. - -import struct -import time -from typing import List, Optional - -import usb._objfinalizer -import usb.core -import usb.util - -import pynitrokey.exceptions -from pynitrokey.fido2.commands import DFU, STM32L4 - -# @fixme: remove for 0.5 -# hotpatch windows stuff extracted to __init__ - - -def find( - dfu_serial: Optional[str] = None, - attempts: int = 8, - raw_device: Optional[str] = None, - altsetting: int = 1, -) -> DFUDevice: - """dfu_serial is the ST bootloader serial number. - - It is not directly the ST chip identifier, but related via - https://github.com/libopencm3/libopencm3/blob/master/lib/stm32/desig.c#L68 - """ - for i in range(attempts): - dfu = DFUDevice() - try: - dfu.find(ser=dfu_serial, dev=raw_device, altsetting=altsetting) - return dfu - except RuntimeError: - time.sleep(0.25) - - # return None - raise Exception("no DFU found") - - -def find_all() -> List[DFUDevice]: - st_dfus = usb.core.find(idVendor=0x0483, idProduct=0xDF11, find_all=True) - return [find(raw_device=st_dfu) for st_dfu in st_dfus] - - -class DFUDevice: - def __init__(self) -> None: - pass - - @staticmethod - def addr2list(a: int) -> List[int]: - return [a & 0xFF, (a >> 8) & 0xFF, (a >> 16) & 0xFF, (a >> 24) & 0xFF] - - @staticmethod - def addr2block(addr: int, size: int) -> int: - addr -= 0x08000000 - addr //= size - addr += 2 - return addr - - @staticmethod - def block2addr(addr: int, size: int) -> int: - addr -= 2 - addr *= size - addr += 0x08000000 - return addr - - def find( - self, - altsetting: int = 0, - ser: Optional[str] = None, - dev: Optional[usb.core.Device] = None, - ) -> usb.core.Device: - - if dev is not None: - self.dev = dev - else: - if ser: - devs = usb.core.find(idVendor=0x0483, idProduct=0xDF11, find_all=True) - eligible = [ - d for d in devs if ser == usb.util.get_string(d, d.iSerialNumber) - ] - if len(eligible) > 1: - raise pynitrokey.exceptions.NonUniqueDeviceError - if len(eligible) == 0: - raise RuntimeError("No ST DFU devices found.") - - self.dev = eligible[0] - print("connecting to ", ser) - else: - eligible = list( - usb.core.find(idVendor=0x0483, idProduct=0xDF11, find_all=True) - ) - if len(eligible) > 1: - raise pynitrokey.exceptions.NonUniqueDeviceError - if len(eligible) == 0: - raise RuntimeError("No ST DFU devices found.") - self.dev = eligible[0] - - if self.dev is None: - raise RuntimeError("No ST DFU devices found.") - self.dev.set_configuration() - - for cfg in self.dev: - for intf in cfg: - if intf.bAlternateSetting == altsetting: - intf.set_altsetting() - self.intf = intf - self.intNum = intf.bInterfaceNumber - return self.dev - - raise RuntimeError("No ST DFU alternate-%d found." % altsetting) - - # Main memory == 0 - # option bytes == 1 - def set_alt(self, alt: str) -> None: - for cfg in self.dev: - for intf in cfg: - # print(intf, intf.bAlternateSetting) - if intf.bAlternateSetting == alt: - intf.set_altsetting() - self.intf = intf - self.intNum = intf.bInterfaceNumber - # return self.dev - - def init(self) -> None: - if self.state() == DFU.state.ERROR: - self.clear_status() - - def close(self) -> None: - pass - - def get_status(self) -> DFU.status: - # bmReqType, bmReq, wValue, wIndex, data/size - s = self.dev.ctrl_transfer( - DFU.type.RECEIVE, DFU.bmReq.GETSTATUS, 0, self.intNum, 6 - ) - return DFU.status(s) - - def state(self) -> int: # DFU.state: - return self.get_status().state - - def clear_status(self) -> None: - # bmReqType, bmReq, wValue, wIndex, data/size - self.dev.ctrl_transfer(DFU.type.SEND, DFU.bmReq.CLRSTATUS, 0, self.intNum, None) - - def upload(self, block: int, size: int) -> List[int]: - """ - address is ((block – 2) × size) + 0x08000000 - """ - # bmReqType, bmReq, wValue, wIndex, data/size - return self.dev.ctrl_transfer( # type: ignore[no-any-return] - DFU.type.RECEIVE, DFU.bmReq.UPLOAD, block, self.intNum, size - ) - - def set_addr(self, addr: int) -> int: - # must get_status after to take effect - return self.dnload(0x0, [0x21] + DFUDevice.addr2list(addr)) - - def dnload(self, block: int, data: List[int]) -> int: - # bmReqType, bmReq, wValue, wIndex, data/size - return self.dev.ctrl_transfer( # type: ignore[no-any-return] - DFU.type.SEND, DFU.bmReq.DNLOAD, block, self.intNum, data - ) - - def erase(self, a: int) -> int: - d = [0x41, a & 0xFF, (a >> 8) & 0xFF, (a >> 16) & 0xFF, (a >> 24) & 0xFF] - return self.dnload(0x0, d) - - def mass_erase(self) -> None: - # self.set_addr(0x08000000) - # self.block_on_state(DFU.state.DOWNLOAD_BUSY) - # assert(DFU.state.DOWNLOAD_IDLE == self.state()) - self.dnload(0x0, [0x41]) - self.block_on_state(DFU.state.DOWNLOAD_BUSY) - assert DFU.state.DOWNLOAD_IDLE == self.state() - - def write_page(self, addr: int, data: List[int]) -> None: - if self.state() not in (DFU.state.IDLE, DFU.state.DOWNLOAD_IDLE): - self.clear_status() - self.clear_status() - if self.state() not in (DFU.state.IDLE, DFU.state.DOWNLOAD_IDLE): - raise RuntimeError("DFU device not in correct state for writing memory.") - - addr = DFUDevice.addr2block(addr, len(data)) - # print('flashing %d bytes to block %d/%08x...' % (len(data), addr,oldaddr)) - - self.dnload(addr, data) - self.block_on_state(DFU.state.DOWNLOAD_BUSY) - assert DFU.state.DOWNLOAD_IDLE == self.state() - - def read_mem(self, addr: int, size: int) -> List[int]: - addr = DFUDevice.addr2block(addr, size) - - if self.state() not in (DFU.state.IDLE, DFU.state.UPLOAD_IDLE): - self.clear_status() - self.clear_status() - if self.state() not in (DFU.state.IDLE, DFU.state.UPLOAD_IDLE): - raise RuntimeError("DFU device not in correct state for reading memory.") - - return self.upload(addr, size) - - def block_on_state(self, state: int) -> None: - s = self.get_status() - while s.state == state: - time.sleep(s.timeout / 1000.0) - s = self.get_status() - - def read_option_bytes(self) -> List[int]: - ptr = 0x1FFF7800 # option byte address for STM32l432 - self.set_addr(ptr) - self.block_on_state(DFU.state.DOWNLOAD_BUSY) - m = self.read_mem(0, 16) - return m - - def write_option_bytes(self, m: List[int]) -> None: - self.block_on_state(DFU.state.DOWNLOAD_BUSY) - try: - self.write_page(0, m) - self.block_on_state(DFU.state.DOWNLOAD_BUSY) - except OSError: - print("Warning: OSError with write_page") - - def prepare_options_bytes_detach(self) -> None: - - # Necessary to prevent future errors... - m = self.read_mem(0, 16) - self.write_option_bytes(m) - # - - m = self.read_option_bytes() - - # unneccessary, but mypy... - _m = b"".join(map(lambda x: x.to_bytes(1, "big"), m)) - op = struct.unpack(" DFU.status: - if self.state() not in (DFU.state.IDLE, DFU.state.DOWNLOAD_IDLE): - self.clear_status() - self.clear_status() - if self.state() not in (DFU.state.IDLE, DFU.state.DOWNLOAD_IDLE): - raise RuntimeError("DFU device not in correct state for detaching.") - # self.set_addr(0x08000000) - # self.block_on_state(DFU.state.DOWNLOAD_BUSY) - # assert(DFU.state.DOWNLOAD_IDLE == self.state()) - self.dnload(0x0, []) - return self.get_status() - # return self.dev.ctrl_transfer(DFU.type.SEND, DFU.bmReq.DETACH, 0, self.intNum, None)