From 5673494fe9897a9e91261ec0281fdf416d84a4c4 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Mon, 12 Aug 2024 15:27:52 +0200 Subject: [PATCH] Error handling for restricted NFC --- ykman/_cli/__main__.py | 15 +++++++++++++-- ykman/util.py | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/ykman/_cli/__main__.py b/ykman/_cli/__main__.py index 2f25a5e1..0ef721f0 100644 --- a/ykman/_cli/__main__.py +++ b/ykman/_cli/__main__.py @@ -46,6 +46,7 @@ parse_private_key, parse_certificates, InvalidPasswordError, + is_nfc_restricted, ) from ..logging import init_logging from ..diagnostics import get_diagnostics, sys_info @@ -126,11 +127,21 @@ def require_reader(connection_types, reader): readers = list_ccid(reader) if len(readers) == 1: dev = readers[0] + nfc_restricted = False try: with dev.open_connection(SmartCardConnection) as conn: - info = read_info(conn, dev.pid) - return dev, info + try: + info = read_info(conn, dev.pid) + return dev, info + except ValueError: + nfc_restricted = is_nfc_restricted(conn) + raise # Re-raise to be handled in block below except Exception: + if nfc_restricted: + raise CliFail( + "YubiKey is in NFC restricted mode " + "(see: https://www.yubico.com/getting-started/)." + ) raise CliFail("Failed to connect to YubiKey.") elif len(readers) > 1: raise CliFail("Multiple external readers match name.") diff --git a/ykman/util.py b/ykman/util.py index 385d2f85..ccf77c74 100644 --- a/ykman/util.py +++ b/ykman/util.py @@ -26,6 +26,12 @@ # POSSIBILITY OF SUCH DAMAGE. from yubikit.core import Tlv, int2bytes +from yubikit.core.smartcard import ( + SmartCardConnection, + SmartCardProtocol, + ApduError, + ApplicationNotAvailableError, +) from cryptography.hazmat.primitives.serialization import pkcs12 from cryptography.hazmat.primitives import serialization from cryptography.hazmat.backends import default_backend @@ -192,3 +198,19 @@ def get_windows_version() -> Tuple[int, int, int]: osvi.dwOSVersionInfoSize = ctypes.sizeof(osvi) ctypes.windll.Ntdll.RtlGetVersion(ctypes.byref(osvi)) # type: ignore return osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber + + +_RESTRICTED_NDEF = bytes.fromhex("001FD1011B5504") + b"yubico.com/getting-started" + + +def is_nfc_restricted(connection: SmartCardConnection) -> bool: + """Check if the given SmartCardConnection over NFC is in restricted NFC mode.""" + try: + p = SmartCardProtocol(connection) + p.select(bytes.fromhex("D2760000850101")) + p.send_apdu(0x00, 0xA4, 0x00, 0x0C, bytes([0xE1, 0x04])) + ndef = p.send_apdu(0x00, 0xB0, 0x00, 0x00) + except (ApduError, ApplicationNotAvailableError): + ndef = None + + return ndef == _RESTRICTED_NDEF