Source code for cryptnox_sdk_py.reader

# -*- coding: utf-8 -*-
"""
Module that handles different card reader types and their drivers.
"""

import abc
import logging

from typing import List, Tuple

logger = logging.getLogger(__name__)

NFC_AVAILABLE = True
SMARTCARD_AVAILABLE = True

try:
    # Requires xantares/nfc-binding
    import nfc
except ImportError:
    NFC_AVAILABLE = False

try:
    from smartcard.System import readers
    from smartcard.CardConnection import CardConnection
    import smartcard
except ImportError:
    SMARTCARD_AVAILABLE = False


[docs] class ReaderException(Exception): """ Reader hasn't been found or other reader related issues """
[docs] class CardException(Exception): """ The reader is present but there is an issue in connecting to the card """
[docs] class ConnectionException(Exception): """ An issue has occurred in the communication with the card. """
[docs] class Reader(metaclass=abc.ABCMeta): """ Abstract class describing methods to be implemented. Holds the connection. """
[docs] def __init__(self): self._connection = None
def __del__(self): if self._connection: del self._connection
[docs] @abc.abstractmethod def connect(self) -> None: """ Connect to the card found in the selected reader. :return: None """
[docs] @abc.abstractmethod def send(self, apdu: List[int]) -> Tuple[List[str], int, int]: """ Send APDU to the reader and card and retrieve the result with status codes. :param List[int] apdu: Command to be sent :return: Return the result of the query and two status codes :rtype: Tuple[List[str], int, int] """
[docs] def bool(self) -> bool: """ Is there an active connection :rtype: Is there an active connection :return: bool """ return self._connection is not None
[docs] def disconnect(self) -> None: """ Disconnect from the card. :return: None """ if self._connection: self._connection = None
@classmethod def __subclasshook__(cls, c): if cls is Reader: attrs = set(dir(c)) if set(cls.__abstractmethods__) <= attrs: return True return NotImplemented
[docs] class NfcReader(Reader): """ Specialized reader using xantares/nfc-binding """
[docs] def __init__(self): super().__init__() nfc_context = nfc.init() self._connection = nfc.open(nfc_context) if not self._connection: raise ReaderException("Card reader not found.")
[docs] def connect(self): nfc.initiator_init(self._connection) link_mode = nfc.modulation() link_mode.nmt = nfc.NMT_ISO14443A link_mode.nbr = nfc.NBR_106 target = nfc.target() nfc.initiator_select_passive_target(self._connection, link_mode, 0, 0, target)
[docs] def send(self, apdu: List[int]) -> Tuple[List[str], int, int]: message_ba = bytearray(apdu) ret = nfc.initiator_transceive_bytes(self._connection, message_ba, len(message_ba), 256, 0) if ret[0] < 0: logger.error("RFID transceive error: %s", ret[0]) return [], 0x99, 0x99 length = ret[0] return list(ret[1][:length - 2]), int(ret[1][length - 2]), \ int(ret[1][length - 1])
[docs] class SmartCard(Reader): """ Generic smart card reader class :param int index: Index of the reader to initialize. """
[docs] def __init__(self, index: int = 0): super().__init__() try: found_readers = list(filter(lambda x: not str(x).startswith("Yubico"), readers())) except smartcard.pcsc.PCSCExceptions.EstablishContextException as error: raise ReaderException("Readers not detected on the system") \ from error try: found_reader = found_readers[index] self._connection = found_reader.createConnection() except IndexError as error: raise ReaderException(f"Reader with index {index} not found.") \ from error
[docs] def connect(self) -> None: try: self._connection.connect(CardConnection.T1_protocol) except smartcard.Exceptions.NoCardException as error: raise CardException("The reader has no card inserted") from error except smartcard.Exceptions.CardConnectionException as error: raise CardException("The reader has no card inserted") from error
[docs] def send(self, apdu: List[int]) -> Tuple[List[str], int, int]: try: return self._connection.transmit(apdu) except smartcard.Exceptions.CardConnectionException as error: raise ConnectionException("Connection issue") from error
[docs] def disconnect(self) -> None: if self._connection: try: self._connection.disconnect() except smartcard.Exceptions.CardConnectionException: # Ignore disconnect errors: connection is being torn down anyway. self._connection = None else: self._connection = None
[docs] def get(index: int = 0) -> Reader: """ Get the reader that can be found on the given position. :param int index: Index of reader to be initialized and used :return: Reader object that can be used. :rtype: Reader """ if SMARTCARD_AVAILABLE: return SmartCard(index) if NFC_AVAILABLE: return NfcReader() raise ReaderException("No readers available")