diff --git a/helper/helper/__init__.py b/helper/helper/__init__.py index aa8527d9d..791002296 100644 --- a/helper/helper/__init__.py +++ b/helper/helper/__init__.py @@ -17,7 +17,7 @@ from queue import Queue from threading import Thread, Event -from typing import Callable, Dict, List +from typing import Callable import json import logging @@ -78,14 +78,14 @@ def _handle_incoming(event, recv, error, cmd_queue): def process( - send: Callable[[Dict], None], - recv: Callable[[], Dict], - handler: Callable[[str, List, Dict, Event, Callable[[str], None]], RpcResponse], + send: Callable[[dict], None], + recv: Callable[[], dict], + handler: Callable[[str, list, dict, Event, Callable[[str], None]], RpcResponse], ) -> None: - def error(status: str, message: str, body: Dict = {}): + def error(status: str, message: str, body: dict = {}): send(dict(kind="error", status=status, message=message, body=body)) - def signal(status: str, body: Dict = {}): + def signal(status: str, body: dict = {}): send(dict(kind="signal", status=status, body=body)) def success(response: RpcResponse): @@ -121,8 +121,8 @@ def success(response: RpcResponse): def run_rpc( - send: Callable[[Dict], None], - recv: Callable[[], Dict], + send: Callable[[dict], None], + recv: Callable[[], dict], ) -> None: process(send, recv, RootNode()) diff --git a/helper/helper/base.py b/helper/helper/base.py index c894b10ff..3429e142e 100644 --- a/helper/helper/base.py +++ b/helper/helper/base.py @@ -15,6 +15,7 @@ from yubikit.core import InvalidPinError from functools import partial +import inspect import logging logger = logging.getLogger(__name__) @@ -127,7 +128,13 @@ def __call__(self, action, target, params, event, signal, traversed=None): action, target[1:], params, event, signal, traversed ) elif action in self.list_actions(): - response = self.get_action(action)(params, event, signal) + action_f = self.get_action(action) + args = inspect.signature(action_f).parameters + if "event" in args: + params["event"] = event + if "signal" in args: + params["signal"] = signal + response = action_f(**params) elif action in self.list_children(): traversed += [action] response = self.get_child(action)( @@ -224,7 +231,7 @@ def get_child(self, name): return self._child @action - def get(self, params, event, signal): + def get(self): return dict( data=self.get_data(), actions=self.list_actions(), diff --git a/helper/helper/device.py b/helper/helper/device.py index 846fbc077..c72294b3c 100644 --- a/helper/helper/device.py +++ b/helper/helper/device.py @@ -55,7 +55,7 @@ from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey from hashlib import sha256 from dataclasses import asdict -from typing import Mapping, Tuple +from typing import Mapping import os import sys @@ -113,19 +113,19 @@ def nfc(self): return self._readers @action - def diagnose(self, *ignored): + def diagnose(self): return dict(diagnostics=get_diagnostics()) @action(closes_child=False) - def logging(self, params, event, signal): - level = LOG_LEVEL[params["level"].upper()] - set_log_level(level) - logger.info(f"Log level set to: {level.name}") + def logging(self, level: str): + lvl = LOG_LEVEL[level.upper()] + set_log_level(lvl) + logger.info(f"Log level set to: {lvl.name}") return dict() @action(closes_child=False) - def qr(self, params, event, signal): - return dict(result=scan_qr(params.get("image"))) + def qr(self, image: str | None = None): + return dict(result=scan_qr(image)) def _id_from_fingerprint(fp): @@ -142,7 +142,7 @@ def __init__(self): self._reader_mapping = {} @action(closes_child=False) - def scan(self, *ignored): + def scan(self): return self.list_children() def list_children(self): @@ -173,7 +173,7 @@ def create_child(self, name): class _ScanDevices: def __init__(self): - self._state: Tuple[Mapping[PID, int], int] = ({}, 0) + self._state: tuple[Mapping[PID, int], int] = ({}, 0) self._caching = False def __call__(self): @@ -225,7 +225,7 @@ def close(self): super().close() @action(closes_child=False) - def scan(self, *ignored): + def scan(self): return self.get_data() def get_data(self): @@ -460,8 +460,8 @@ def _refresh_data(self): return dict(present=False, status="no-card") @action(closes_child=False) - def get(self, params, event, signal): - return super().get(params, event, signal) + def get(self): + return super().get() @child def ccid(self): diff --git a/helper/helper/fido.py b/helper/helper/fido.py index 284c6e4bb..05a9b78bc 100644 --- a/helper/helper/fido.py +++ b/helper/helper/fido.py @@ -177,7 +177,7 @@ def _prepare_reset_usb(device, event, signal): raise TimeoutException() @action - def reset(self, params, event, signal): + def reset(self, event, signal): target = _ctap_id(self.ctap) device = self.ctap.device if isinstance(device, CtapPcscDevice): @@ -206,8 +206,7 @@ def reset(self, params, event, signal): return RpcResponse(dict(), ["device_info", "device_closed"]) @action(condition=lambda self: self._info.options["clientPin"]) - def unlock(self, params, event, signal): - pin = params.pop("pin") + def unlock(self, pin: str): permissions = ClientPin.PERMISSION(0) if CredentialManagement.is_supported(self._info): permissions |= ClientPin.PERMISSION.CREDENTIAL_MGMT @@ -227,25 +226,21 @@ def unlock(self, params, event, signal): return _handle_pin_error(e, self.client_pin) @action - def set_pin(self, params, event, signal): + def set_pin(self, new_pin: str, pin: str | None = None): has_pin = self.ctap.get_info().options["clientPin"] try: if has_pin: - self.client_pin.change_pin( - params.pop("pin"), - params.pop("new_pin"), - ) + assert pin # nosec + self.client_pin.change_pin(pin, new_pin) else: - self.client_pin.set_pin( - params.pop("new_pin"), - ) + self.client_pin.set_pin(new_pin) self._info = self.ctap.get_info() return RpcResponse(dict(), ["device_info"]) except CtapError as e: return _handle_pin_error(e, self.client_pin) @action(condition=lambda self: Config.is_supported(self._info)) - def enable_ep_attestation(self, params, event, signal): + def enable_ep_attestation(self): if self._info.options["clientPin"] and not self._token: raise AuthRequiredException() config = Config(self.ctap, self.client_pin.protocol, self._token) @@ -343,7 +338,7 @@ def get_data(self): return self.data @action - def delete(self, params, event, signal): + def delete(self): self.credman.delete_cred(self.data["credential_id"]) self.refresh_rps() return dict() @@ -378,8 +373,7 @@ def create_child(self, name): return super().create_child(name) @action - def add(self, params, event, signal): - name = params.get("name", None) + def add(self, event, signal, name: str | None = None): enroller = self.bio.enroll() template_id = None while template_id is None: @@ -410,15 +404,14 @@ def get_data(self): return dict(template_id=self.template_id, name=self.name) @action - def rename(self, params, event, signal): - name = params.pop("name") + def rename(self, name: str): self.bio.set_name(self.template_id, name) self.name = name self.refresh() return dict() @action - def delete(self, params, event, signal): + def delete(self): self.bio.remove_enrollment(self.template_id) self.refresh() return dict() diff --git a/helper/helper/management.py b/helper/helper/management.py index 5474f8e21..cccfee3f2 100644 --- a/helper/helper/management.py +++ b/helper/helper/management.py @@ -13,11 +13,23 @@ # limitations under the License. from .base import RpcResponse, RpcNode, action -from yubikit.core import require_version, NotSupportedError, TRANSPORT, Connection +from yubikit.core import ( + require_version, + NotSupportedError, + TRANSPORT, + USB_INTERFACE, + Connection, +) from yubikit.core.smartcard import SmartCardConnection from yubikit.core.otp import OtpConnection from yubikit.core.fido import FidoConnection -from yubikit.management import ManagementSession, DeviceConfig, Mode, CAPABILITY +from yubikit.management import ( + ManagementSession, + DeviceConfig, + Mode, + CAPABILITY, + DEVICE_FLAG, +) from ykman.device import list_all_devices from dataclasses import asdict from time import sleep @@ -75,18 +87,26 @@ def _await_reboot(self, serial, usb_enabled): logger.warning("Timed out waiting for device") @action - def configure(self, params, event, signal): - reboot = params.pop("reboot", False) - cur_lock_code = bytes.fromhex(params.pop("cur_lock_code", "")) or None - new_lock_code = bytes.fromhex(params.pop("new_lock_code", "")) or None + def configure( + self, + reboot: bool = False, + cur_lock_code: str = "", + new_lock_code: str = "", + enabled_capabilities: dict = {}, + auto_eject_timeout: int | None = None, + challenge_response_timeout: int | None = None, + device_flags: int | None = None, + ): + cur_code = bytes.fromhex(cur_lock_code) or None + new_code = bytes.fromhex(new_lock_code) or None config = DeviceConfig( - params.pop("enabled_capabilities", {}), - params.pop("auto_eject_timeout", None), - params.pop("challenge_response_timeout", None), - params.pop("device_flags", None), + enabled_capabilities, + auto_eject_timeout, + challenge_response_timeout, + DEVICE_FLAG(device_flags) if device_flags else None, ) serial = self.session.read_device_info().serial - self.session.write_device_config(config, reboot, cur_lock_code, new_lock_code) + self.session.write_device_config(config, reboot, cur_code, new_code) flags = ["device_info"] if reboot: enabled = config.enabled_capabilities.get(TRANSPORT.USB) @@ -95,17 +115,22 @@ def configure(self, params, event, signal): return RpcResponse(dict(), flags) @action - def set_mode(self, params, event, signal): + def set_mode( + self, + interfaces: int, + challenge_response_timeout: int = 0, + auto_eject_timeout: int | None = None, + ): self.session.set_mode( - Mode(params["interfaces"]), - params.pop("challenge_response_timeout", 0), - params.pop("auto_eject_timeout"), + Mode(USB_INTERFACE(interfaces)), + challenge_response_timeout, + auto_eject_timeout, ) return dict() @action( condition=lambda self: issubclass(self._connection_type, SmartCardConnection) ) - def device_reset(self, params, event, signal): + def device_reset(self): self.session.device_reset() return RpcResponse(dict(), ["device_info"]) diff --git a/helper/helper/oath.py b/helper/helper/oath.py index c56efbbfb..c54016af2 100644 --- a/helper/helper/oath.py +++ b/helper/helper/oath.py @@ -118,11 +118,11 @@ def get_data(self): ) @action - def derive(self, params, event, signal): - return dict(key=self.session.derive_key(params.pop("password"))) + def derive(self, password: str): + return dict(key=self.session.derive_key(password)) @action - def forget(self, params, event, signal): + def forget(self): keys = self._get_keys() del keys[self.session.device_id] keys.write() @@ -142,15 +142,13 @@ def _remember_key(self, key): else: return False - def _get_key(self, params): - has_key = "key" in params - has_pw = "password" in params - if has_key and has_pw: + def _get_key(self, key: str | None, password: str | None): + if key and password: raise ValueError("Only one of 'key' and 'password' can be provided.") - if has_pw: - return self.session.derive_key(params.pop("password")) - if has_key: - return decode_bytes(params.pop("key")) + if password: + return self.session.derive_key(password) + if key: + return decode_bytes(key) raise ValueError("One of 'key' and 'password' must be provided.") def _set_key_verifier(self, key): @@ -163,12 +161,16 @@ def _do_validate(self, key): self._set_key_verifier(key) @action - def validate(self, params, event, signal): - remember = params.pop("remember", False) - key = self._get_key(params) + def validate( + self, + key: str | None = None, + password: str | None = None, + remember: bool = False, + ): + access_key = self._get_key(key, password) if self.session.locked: try: - self._do_validate(key) + self._do_validate(access_key) valid = True except ApduError as e: if e.sw == SW.INCORRECT_PARAMETERS: @@ -177,34 +179,38 @@ def validate(self, params, event, signal): raise e elif self._key_verifier: salt, digest = self._key_verifier - verify = hmac.new(salt, key, "sha256").digest() + verify = hmac.new(salt, access_key, "sha256").digest() valid = hmac.compare_digest(digest, verify) else: valid = False if valid and remember: - remembered = self._remember_key(key) + remembered = self._remember_key(access_key) else: remembered = False return dict(valid=valid, remembered=remembered) @action - def set_key(self, params, event, signal): - remember = params.pop("remember", False) - key = self._get_key(params) - self.session.set_key(key) - self._set_key_verifier(key) - remember &= self._remember_key(key if remember else None) + def set_key( + self, + key: str | None = None, + password: str | None = None, + remember: bool = False, + ): + access_key = self._get_key(key, password) + self.session.set_key(access_key) + self._set_key_verifier(access_key) + remember &= self._remember_key(access_key if remember else None) return RpcResponse(dict(remembered=remember), ["device_info"]) @action(condition=lambda self: self.session.has_key) - def unset_key(self, params, event, signal): + def unset_key(self): self.session.unset_key() self._key_verifier = None self._remember_key(None) return dict() @action - def reset(self, params, event, signal): + def reset(self): self.session.reset() self._key_verifier = None self._remember_key(None) @@ -240,8 +246,7 @@ def create_child(self, name): return super().create_child(name) @action - def calculate_all(self, params, event, signal): - timestamp = params.pop("timestamp", None) + def calculate_all(self, timestamp: int | None = None): result = self.session.calculate_all(timestamp) return dict( entries=[ @@ -251,19 +256,22 @@ def calculate_all(self, params, event, signal): ) @action - def put(self, params, event, signal): - require_touch = params.pop("require_touch", False) - if "uri" in params: - data = CredentialData.parse_uri(params.pop("uri")) - if params: + def put( + self, + require_touch: bool = False, + **kwargs, + ): + if "uri" in kwargs: + data = CredentialData.parse_uri(kwargs.pop("uri")) + if kwargs: raise ValueError("Unsupported parameters present") else: data = CredentialData( - params.pop("name"), - OATH_TYPE[params.pop("oath_type").upper()], - HASH_ALGORITHM[params.pop("hash", "sha1".upper())], - decode_bytes(params.pop("secret")), - **params, + kwargs.pop("name"), + OATH_TYPE[kwargs.pop("oath_type").upper()], + HASH_ALGORITHM[kwargs.pop("hash", "sha1".upper())], + decode_bytes(kwargs.pop("secret")), + **kwargs, ) if data.get_id() in self._creds: @@ -322,32 +330,29 @@ def on_timeout(): timer.cancel() @action - def code(self, params, event, signal): - timestamp = params.pop("timestamp", None) + def code(self, signal, timestamp: int | None = None): code = self._do_with_touch( signal, lambda: self.session.calculate_code(self.credential, timestamp) ) return asdict(code) @action - def calculate(self, params, event, signal): - challenge = decode_bytes(params.pop("challenge")) + def calculate(self, signal, challenge: str): response = self._do_with_touch( - signal, lambda: self.session.calculate(self.credential.id, challenge) + signal, + lambda: self.session.calculate(self.credential.id, decode_bytes(challenge)), ) return dict(response=response) @action - def delete(self, params, event, signal): + def delete(self): self.session.delete_credential(self.credential.id) self.refresh() self.credential = None return dict() @action(condition=lambda self: self._require_version(5, 3, 1)) - def rename(self, params, event, signal): - name = params.pop("name") - issuer = params.pop("issuer", None) + def rename(self, name: str, issuer: str | None = None): try: new_id = self.session.rename_credential(self.credential.id, name, issuer) self.refresh() diff --git a/helper/helper/piv.py b/helper/helper/piv.py index 7045343b9..0ad166fcd 100644 --- a/helper/helper/piv.py +++ b/helper/helper/piv.py @@ -178,9 +178,7 @@ def _authenticate(self, key, signal): self._authenticated = True @action - def verify_pin(self, params, event, signal): - pin = params.pop("pin") - + def verify_pin(self, signal, pin: str): self.session.verify_pin(pin) key = None @@ -203,10 +201,9 @@ def verify_pin(self, params, event, signal): return dict(status=True, authenticated=self._authenticated) @action - def authenticate(self, params, event, signal): - key = bytes.fromhex(params.pop("key")) + def authenticate(self, signal, key: str): try: - self._authenticate(key, signal) + self._authenticate(bytes.fromhex(key), signal) return dict(status=True) except ApduError as e: if e.sw == SW.SECURITY_CONDITION_NOT_SATISFIED: @@ -214,38 +211,41 @@ def authenticate(self, params, event, signal): raise @action(condition=lambda self: self._authenticated) - def set_key(self, params, event, signal): - key_type = MANAGEMENT_KEY_TYPE(params.pop("key_type", MANAGEMENT_KEY_TYPE.TDES)) - key = bytes.fromhex(params.pop("key")) - store_key = params.pop("store_key", False) - pivman_set_mgm_key(self.session, key, key_type, False, store_key) + def set_key( + self, + params, + key: str, + key_type: int = MANAGEMENT_KEY_TYPE.TDES, + store_key: bool = False, + ): + pivman_set_mgm_key( + self.session, + bytes.fromhex(key), + MANAGEMENT_KEY_TYPE(key_type), + False, + store_key, + ) self._pivman_data = get_pivman_data(self.session) return RpcResponse(dict(), ["device_info"]) @action - def change_pin(self, params, event, signal): - old_pin = params.pop("pin") - new_pin = params.pop("new_pin") + def change_pin(self, pin: str, new_pin: str): try: - pivman_change_pin(self.session, old_pin, new_pin) + pivman_change_pin(self.session, pin, new_pin) return RpcResponse(dict(), ["device_info"]) except Exception as e: _handle_pin_puk_error(e) @action - def change_puk(self, params, event, signal): - old_puk = params.pop("puk") - new_puk = params.pop("new_puk") + def change_puk(self, puk: str, new_puk: str): try: - self.session.change_puk(old_puk, new_puk) + self.session.change_puk(puk, new_puk) return RpcResponse(dict(), ["device_info"]) except Exception as e: _handle_pin_puk_error(e) @action - def unblock_pin(self, params, event, signal): - puk = params.pop("puk") - new_pin = params.pop("new_pin") + def unblock_pin(self, puk: str, new_pin: str): try: self.session.unblock_pin(puk, new_pin) return RpcResponse(dict(), ["device_info"]) @@ -253,7 +253,7 @@ def unblock_pin(self, params, event, signal): _handle_pin_puk_error(e) @action - def reset(self, params, event, signal): + def reset(self): self.session.reset() self._authenticated = False self._pivman_data = get_pivman_data(self.session) @@ -264,11 +264,9 @@ def slots(self): return SlotsNode(self.session) @action(closes_child=False) - def examine_file(self, params, event, signal): - data = bytes.fromhex(params.pop("data")) - password = params.pop("password", None) + def examine_file(self, data: str, password: str | None = None): try: - private_key, certs = _parse_file(data, password) + private_key, certs = _parse_file(bytes.fromhex(data), password) certificate = _choose_cert(certs) return dict( @@ -286,9 +284,9 @@ def examine_file(self, params, event, signal): return dict(status=False) @action(closes_child=False) - def validate_rfc4514(self, params, event, signal): + def validate_rfc4514(self, data: str): try: - parse_rfc4514_string(params.pop("data")) + parse_rfc4514_string(data) return dict(status=True) except ValueError: return dict(status=False) @@ -431,10 +429,7 @@ def get_data(self): ) @action(condition=lambda self: self.certificate or self.metadata) - def delete(self, params, event, signal): - delete_cert = params.pop("delete_cert", False) - delete_key = params.pop("delete_key", False) - + def delete(self, delete_cert: bool = False, delete_key: bool = False): if not delete_cert and not delete_key: raise ValueError("Missing delete option") @@ -448,19 +443,17 @@ def delete(self, params, event, signal): return dict() @action(condition=lambda self: self.metadata) - def move_key(self, params, event, signal): - destination = params.pop("destination") - overwrite_key = params.pop("overwrite_key") - include_certificate = params.pop("include_certificate") - + def move_key( + self, destination: str, overwrite_key: bool, include_certificate: bool + ): if include_certificate: source_object = self.session.get_object(OBJECT_ID.from_slot(self.slot)) - destination = SLOT(int(destination, base=16)) + dest = SLOT(int(destination, base=16)) if overwrite_key: - self.session.delete_key(destination) - self.session.move_key(self.slot, destination) + self.session.delete_key(dest) + self.session.move_key(self.slot, dest) if include_certificate: - self.session.put_object(OBJECT_ID.from_slot(destination), source_object) + self.session.put_object(OBJECT_ID.from_slot(dest), source_object) self.session.delete_certificate(self.slot) self.session.put_object(OBJECT_ID.CHUID, generate_chuid()) self.certificate = None @@ -468,12 +461,9 @@ def move_key(self, params, event, signal): return dict() @action - def import_file(self, params, event, signal): - data = bytes.fromhex(params.pop("data")) - password = params.pop("password", None) - + def import_file(self, data: str, password: str | None = None, **kwargs): try: - private_key, certs = _parse_file(data, password) + private_key, certs = _parse_file(bytes.fromhex(data), password) except InvalidPasswordError: logger.debug("Invalid or missing password", exc_info=True) raise ValueError("Wrong/Missing password") @@ -484,9 +474,9 @@ def import_file(self, params, event, signal): metadata = None if private_key: - pin_policy = PIN_POLICY(params.pop("pin_policy", PIN_POLICY.DEFAULT)) + pin_policy = PIN_POLICY(kwargs.pop("pin_policy", PIN_POLICY.DEFAULT)) touch_policy = TOUCH_POLICY( - params.pop("touch_policy", TOUCH_POLICY.DEFAULT) + kwargs.pop("touch_policy", TOUCH_POLICY.DEFAULT) ) self.session.put_key(self.slot, private_key, pin_policy, touch_policy) try: @@ -522,16 +512,23 @@ def import_file(self, params, event, signal): return RpcResponse(response, ["device_info"]) @action - def generate(self, params, event, signal): - key_type = KEY_TYPE(params.pop("key_type")) - pin_policy = PIN_POLICY(params.pop("pin_policy", PIN_POLICY.DEFAULT)) - touch_policy = TOUCH_POLICY(params.pop("touch_policy", TOUCH_POLICY.DEFAULT)) - subject = params.pop("subject") - generate_type = GENERATE_TYPE( - params.pop("generate_type", GENERATE_TYPE.CERTIFICATE) - ) + def generate( + self, + signal, + key_type: int, + pin_policy: int = PIN_POLICY.DEFAULT, + touch_policy: int = TOUCH_POLICY.DEFAULT, + generate_type: str = GENERATE_TYPE.CERTIFICATE, + subject: str | None = None, + pin: str | None = None, + **kwargs, + ): + generate_type = GENERATE_TYPE(generate_type) public_key = self.session.generate_key( - self.slot, key_type, pin_policy, touch_policy + self.slot, + KEY_TYPE(key_type), + PIN_POLICY(pin_policy), + TOUCH_POLICY(touch_policy), ) public_key_pem = public_key.public_bytes( encoding=Encoding.PEM, format=PublicFormat.SubjectPublicKeyInfo @@ -539,35 +536,37 @@ def generate(self, params, event, signal): if pin_policy != PIN_POLICY.NEVER: # TODO: Check if verified? - pin = params.pop("pin") self.session.verify_pin(pin) if touch_policy in (TOUCH_POLICY.ALWAYS, TOUCH_POLICY.CACHED): signal("touch") - if generate_type == GENERATE_TYPE.PUBLIC_KEY: - result = public_key_pem - elif generate_type == GENERATE_TYPE.CSR: - csr = generate_csr(self.session, self.slot, public_key, subject) - result = csr.public_bytes(encoding=Encoding.PEM).decode() - elif generate_type == GENERATE_TYPE.CERTIFICATE: - now = datetime.datetime.utcnow() - then = now + datetime.timedelta(days=365) - valid_from = params.pop("valid_from", now.strftime(_date_format)) - valid_to = params.pop("valid_to", then.strftime(_date_format)) - cert = generate_self_signed_certificate( - self.session, - self.slot, - public_key, - subject, - datetime.datetime.strptime(valid_from, _date_format), - datetime.datetime.strptime(valid_to, _date_format), - ) - result = cert.public_bytes(encoding=Encoding.PEM).decode() - self.session.put_certificate(self.slot, cert) - self.session.put_object(OBJECT_ID.CHUID, generate_chuid()) - else: - raise ValueError(f"Unsupported GENERATE_TYPE: {generate_type}") + match GENERATE_TYPE(generate_type): + case GENERATE_TYPE.PUBLIC_KEY: + result = public_key_pem + case GENERATE_TYPE.CSR: + assert subject # nosec + csr = generate_csr(self.session, self.slot, public_key, subject) + result = csr.public_bytes(encoding=Encoding.PEM).decode() + case GENERATE_TYPE.CERTIFICATE: + assert subject # nosec + now = datetime.datetime.utcnow() + then = now + datetime.timedelta(days=365) + valid_from = kwargs.pop("valid_from", now.strftime(_date_format)) + valid_to = kwargs.pop("valid_to", then.strftime(_date_format)) + cert = generate_self_signed_certificate( + self.session, + self.slot, + public_key, + subject, + datetime.datetime.strptime(valid_from, _date_format), + datetime.datetime.strptime(valid_to, _date_format), + ) + result = cert.public_bytes(encoding=Encoding.PEM).decode() + self.session.put_certificate(self.slot, cert) + self.session.put_object(OBJECT_ID.CHUID, generate_chuid()) + case other: + raise ValueError(f"Unsupported GENERATE_TYPE: {other}") self._refresh() diff --git a/helper/helper/yubiotp.py b/helper/helper/yubiotp.py index 5da9d057c..87ffacd1b 100644 --- a/helper/helper/yubiotp.py +++ b/helper/helper/yubiotp.py @@ -30,7 +30,6 @@ from yubikit.oath import parse_b32_key from ykman.scancodes import KEYBOARD_LAYOUT, encode -from typing import Dict import struct _FAIL_MSG = ( @@ -46,7 +45,7 @@ def __init__(self, connection, scp_params=None): def get_data(self): state = self.session.get_config_state() - data: Dict[str, bool] = {} + data: dict[str, bool] = {} try: data.update( slot1_configured=state.is_configured(SLOT.ONE), @@ -64,7 +63,7 @@ def get_data(self): return data @action - def swap(self, params, event, signal): + def swap(self): try: self.session.swap_slots() except CommandError: @@ -80,27 +79,33 @@ def two(self): return SlotNode(self.session, SLOT.TWO) @action(closes_child=False) - def serial_modhex(self, params, event, signal): - serial = params["serial"] + def serial_modhex(self, serial: int): return dict(encoded=modhex_encode(b"\xff\x00" + struct.pack(b">I", serial))) @action(closes_child=False) - def generate_static(self, params, event, signal): - layout, length = params["layout"], int(params["length"]) + def generate_static(self, length: int, layout: str): return dict(password=generate_static_pw(length, KEYBOARD_LAYOUT[layout])) @action(closes_child=False) - def keyboard_layouts(self, params, event, signal): + def keyboard_layouts(self): return {layout.name: [sc for sc in layout.value] for layout in KEYBOARD_LAYOUT} @action(closes_child=False) - def format_yubiotp_csv(self, params, even, signal): - serial = params["serial"] - public_id = modhex_decode(params["public_id"]) - private_id = bytes.fromhex(params["private_id"]) - key = bytes.fromhex(params["key"]) - - return dict(csv=format_csv(serial, public_id, private_id, key)) + def format_yubiotp_csv( + self, + serial: int, + public_id: str, + private_id: str, + key: str, + ): + return dict( + csv=format_csv( + serial, + modhex_decode(public_id), + bytes.fromhex(private_id), + bytes.fromhex(key), + ) + ) _CONFIG_TYPES = dict( @@ -121,7 +126,7 @@ def __init__(self, session, slot): def get_data(self): self._state = self.session.get_config_state() - data: Dict[str, bool] = {} + data: dict[str, bool] = {} try: data.update(is_configured=self._state.is_configured(self.slot)) data.update(is_touch_triggered=self._state.is_touch_triggered(self.slot)) @@ -149,19 +154,19 @@ def _can_calculate(self, slot): return False @action(condition=lambda self: self._maybe_configured(self.slot)) - def delete(self, params, event, signal): + def delete(self, curr_acc_code: str | None = None): try: - access_code = params.pop("curr_acc_code", None) - access_code = bytes.fromhex(access_code) if access_code else None + access_code = bytes.fromhex(curr_acc_code) if curr_acc_code else None self.session.delete_slot(self.slot, access_code) return dict() except CommandError: raise ValueError(_FAIL_MSG) @action(condition=lambda self: self._can_calculate(self.slot)) - def calculate(self, params, event, signal): - challenge = bytes.fromhex(params.pop("challenge")) - response = self.session.calculate_hmac_sha1(self.slot, challenge, event) + def calculate(self, event, challenge: str): + response = self.session.calculate_hmac_sha1( + self.slot, bytes.fromhex(challenge), event + ) return dict(response=response) def _apply_options(self, config, options): @@ -221,14 +226,11 @@ def _get_config(self, type, **kwargs): return config @action - def put(self, params, event, signal): - type = params.pop("type") - options = params.pop("options", {}) - access_code = params.pop("curr_acc_code", None) - access_code = bytes.fromhex(access_code) if access_code else None - args = params - - config = self._get_config(type, **args) + def put( + self, type: str, options: dict = {}, curr_acc_code: str | None = None, **kwargs + ): + access_code = bytes.fromhex(curr_acc_code) if curr_acc_code else None + config = self._get_config(type, **kwargs) self._apply_options(config, options) try: self.session.put_configuration( @@ -245,13 +247,19 @@ def put(self, params, event, signal): condition=lambda self: self._state.version >= (2, 2, 0) and self._maybe_configured(self.slot) ) - def update(self, params, event, signal): + def update( + self, + params, + acc_code: str | None = None, + curr_acc_code: str | None = None, + **kwargs + ): config = UpdateConfiguration() - self._apply_options(config, params) + self._apply_options(config, kwargs) self.session.update_configuration( self.slot, config, - params.pop("acc_code", None), - params.pop("cur_acc_code", None), + bytes.fromhex(acc_code) if acc_code else None, + bytes.fromhex(curr_acc_code) if curr_acc_code else None, ) return dict() diff --git a/helper/poetry.lock b/helper/poetry.lock index f95858e1a..c90ae8638 100644 --- a/helper/poetry.lock +++ b/helper/poetry.lock @@ -212,43 +212,25 @@ pcsc = ["pyscard (>=1.9,<3)"] [[package]] name = "importlib-metadata" -version = "8.4.0" +version = "8.5.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1"}, - {file = "importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"}, + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, ] [package.dependencies] -zipp = ">=0.5" - -[package.extras] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] - -[[package]] -name = "importlib-resources" -version = "6.4.5" -description = "Read resources from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, - {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, -] - -[package.dependencies] -zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} +zipp = ">=3.20" [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] -test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -300,21 +282,25 @@ test = ["portend", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-c [[package]] name = "jaraco-functools" -version = "4.0.2" +version = "4.1.0" description = "Functools like those found in stdlib" optional = false python-versions = ">=3.8" files = [ - {file = "jaraco.functools-4.0.2-py3-none-any.whl", hash = "sha256:c9d16a3ed4ccb5a889ad8e0b7a343401ee5b2a71cee6ed192d3f68bc351e94e3"}, - {file = "jaraco_functools-4.0.2.tar.gz", hash = "sha256:3460c74cd0d32bf82b9576bbb3527c4364d5b27a21f5158a62aed6c4b42e23f5"}, + {file = "jaraco.functools-4.1.0-py3-none-any.whl", hash = "sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649"}, + {file = "jaraco_functools-4.1.0.tar.gz", hash = "sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d"}, ] [package.dependencies] more-itertools = "*" [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["jaraco.classes", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["jaraco.classes", "pytest (>=6,!=8.1.*)"] +type = ["pytest-mypy"] [[package]] name = "jeepney" @@ -333,18 +319,17 @@ trio = ["async_generator", "trio"] [[package]] name = "keyring" -version = "25.3.0" +version = "25.4.1" description = "Store and access your passwords safely." optional = false python-versions = ">=3.8" files = [ - {file = "keyring-25.3.0-py3-none-any.whl", hash = "sha256:8d963da00ccdf06e356acd9bf3b743208878751032d8599c6cc89eb51310ffae"}, - {file = "keyring-25.3.0.tar.gz", hash = "sha256:8d85a1ea5d6db8515b59e1c5d1d1678b03cf7fc8b8dcfb1651e8c4a524eb42ef"}, + {file = "keyring-25.4.1-py3-none-any.whl", hash = "sha256:5426f817cf7f6f007ba5ec722b1bcad95a75b27d780343772ad76b17cb47b0bf"}, + {file = "keyring-25.4.1.tar.gz", hash = "sha256:b07ebc55f3e8ed86ac81dd31ef14e81ace9dd9c3d4b5d77a6e9a2016d0d71a1b"}, ] [package.dependencies] importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} -importlib-resources = {version = "*", markers = "python_version < \"3.9\""} "jaraco.classes" = "*" "jaraco.context" = "*" "jaraco.functools" = "*" @@ -353,9 +338,13 @@ pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] completion = ["shtab (>=1.1.0)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["pyfakefs", "pytest (>=6,!=8.1.*)"] +type = ["pygobject-stubs", "pytest-mypy", "shtab", "types-pywin32"] [[package]] name = "macholib" @@ -623,7 +612,6 @@ files = [ [package.dependencies] altgraph = "*" -importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""} packaging = ">=22.0" pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""} @@ -647,7 +635,6 @@ files = [ ] [package.dependencies] -importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} packaging = ">=22.0" setuptools = ">=42.0.0" @@ -680,13 +667,13 @@ pyro = ["Pyro"] [[package]] name = "pytest" -version = "8.3.2" +version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, - {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [package.dependencies] @@ -702,25 +689,29 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments [[package]] name = "pywin32" -version = "306" +version = "307" description = "Python for Window Extensions" optional = false python-versions = "*" files = [ - {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, - {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, - {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, - {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, - {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, - {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, - {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, - {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, - {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, - {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, - {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, - {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, - {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, - {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, + {file = "pywin32-307-cp310-cp310-win32.whl", hash = "sha256:f8f25d893c1e1ce2d685ef6d0a481e87c6f510d0f3f117932781f412e0eba31b"}, + {file = "pywin32-307-cp310-cp310-win_amd64.whl", hash = "sha256:36e650c5e5e6b29b5d317385b02d20803ddbac5d1031e1f88d20d76676dd103d"}, + {file = "pywin32-307-cp310-cp310-win_arm64.whl", hash = "sha256:0c12d61e0274e0c62acee79e3e503c312426ddd0e8d4899c626cddc1cafe0ff4"}, + {file = "pywin32-307-cp311-cp311-win32.whl", hash = "sha256:fec5d27cc893178fab299de911b8e4d12c5954e1baf83e8a664311e56a272b75"}, + {file = "pywin32-307-cp311-cp311-win_amd64.whl", hash = "sha256:987a86971753ed7fdd52a7fb5747aba955b2c7fbbc3d8b76ec850358c1cc28c3"}, + {file = "pywin32-307-cp311-cp311-win_arm64.whl", hash = "sha256:fd436897c186a2e693cd0437386ed79f989f4d13d6f353f8787ecbb0ae719398"}, + {file = "pywin32-307-cp312-cp312-win32.whl", hash = "sha256:07649ec6b01712f36debf39fc94f3d696a46579e852f60157a729ac039df0815"}, + {file = "pywin32-307-cp312-cp312-win_amd64.whl", hash = "sha256:00d047992bb5dcf79f8b9b7c81f72e0130f9fe4b22df613f755ab1cc021d8347"}, + {file = "pywin32-307-cp312-cp312-win_arm64.whl", hash = "sha256:b53658acbfc6a8241d72cc09e9d1d666be4e6c99376bc59e26cdb6223c4554d2"}, + {file = "pywin32-307-cp313-cp313-win32.whl", hash = "sha256:ea4d56e48dc1ab2aa0a5e3c0741ad6e926529510516db7a3b6981a1ae74405e5"}, + {file = "pywin32-307-cp313-cp313-win_amd64.whl", hash = "sha256:576d09813eaf4c8168d0bfd66fb7cb3b15a61041cf41598c2db4a4583bf832d2"}, + {file = "pywin32-307-cp313-cp313-win_arm64.whl", hash = "sha256:b30c9bdbffda6a260beb2919f918daced23d32c79109412c2085cbc513338a0a"}, + {file = "pywin32-307-cp37-cp37m-win32.whl", hash = "sha256:5101472f5180c647d4525a0ed289ec723a26231550dbfd369ec19d5faf60e511"}, + {file = "pywin32-307-cp37-cp37m-win_amd64.whl", hash = "sha256:05de55a7c110478dc4b202230e98af5e0720855360d2b31a44bb4e296d795fba"}, + {file = "pywin32-307-cp38-cp38-win32.whl", hash = "sha256:13d059fb7f10792542082f5731d5d3d9645320fc38814759313e5ee97c3fac01"}, + {file = "pywin32-307-cp38-cp38-win_amd64.whl", hash = "sha256:7e0b2f93769d450a98ac7a31a087e07b126b6d571e8b4386a5762eb85325270b"}, + {file = "pywin32-307-cp39-cp39-win32.whl", hash = "sha256:55ee87f2f8c294e72ad9d4261ca423022310a6e79fb314a8ca76ab3f493854c6"}, + {file = "pywin32-307-cp39-cp39-win_amd64.whl", hash = "sha256:e9d5202922e74985b037c9ef46778335c102b74b95cec70f629453dbe7235d87"}, ] [[package]] @@ -751,18 +742,18 @@ jeepney = ">=0.6" [[package]] name = "setuptools" -version = "74.1.2" +version = "75.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-74.1.2-py3-none-any.whl", hash = "sha256:5f4c08aa4d3ebcb57a50c33b1b07e94315d7fc7230f7115e47fc99776c8ce308"}, - {file = "setuptools-74.1.2.tar.gz", hash = "sha256:95b40ed940a1c67eb70fc099094bd6e99c6ee7c23aa2306f4d2697ba7916f9c6"}, + {file = "setuptools-75.1.0-py3-none-any.whl", hash = "sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2"}, + {file = "setuptools-75.1.0.tar.gz", hash = "sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538"}, ] [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] @@ -771,13 +762,13 @@ type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11 [[package]] name = "tomli" -version = "2.0.1" +version = "2.0.2" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, ] [[package]] @@ -823,13 +814,13 @@ pywin32 = {version = ">=223", markers = "sys_platform == \"win32\""} [[package]] name = "zipp" -version = "3.20.1" +version = "3.20.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.20.1-py3-none-any.whl", hash = "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064"}, - {file = "zipp-3.20.1.tar.gz", hash = "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b"}, + {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, + {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, ] [package.extras] @@ -877,5 +868,5 @@ files = [ [metadata] lock-version = "2.0" -python-versions = "^3.8" -content-hash = "123356b2b1ed4b00f453721795be4c7c10a3583c7d7b42368127f4c46461e50c" +python-versions = "^3.10" +content-hash = "57036990f6dc7ee966799e8a5fa309d10ce6e6cb9d1cdceeba1f95bf929b6c3f" diff --git a/helper/pyproject.toml b/helper/pyproject.toml index 188751162..3a68053e8 100644 --- a/helper/pyproject.toml +++ b/helper/pyproject.toml @@ -9,7 +9,7 @@ packages = [ [tool.poetry.dependencies] -python = "^3.8" +python = "^3.10" yubikey-manager = "^5.5" mss = "^9.0.1" Pillow = "^10.2.0"