Source code for pyecsca.sca.target.ectester

"""Provides an `ECTester <https://github.com/crocs-muni/ECTester/>`_ target class."""
from abc import ABC
from binascii import hexlify
from enum import IntEnum, IntFlag
from functools import reduce
from math import ceil, log
from operator import or_
from typing import Optional, Mapping, List, Union

from public import public

from .ISO7816 import CommandAPDU, ResponseAPDU, ISO7816, ISO7816Target, CardProtocol, CardConnectionException
from . import has_leia, has_pyscard
from ...ec.model import ShortWeierstrassModel
from ...ec.params import DomainParameters
from ...ec.point import Point


class ShiftableFlag(IntFlag):  # pragma: no cover
    def __lshift__(self, other):
        val = int(self) << other
        for e in self.__class__:
            if val == e.value:
                return e
        raise ValueError

    def __rshift__(self, other):
        val = int(self) >> other
        for e in self.__class__:
            if val == e.value:
                return e
        raise ValueError

    def __iter__(self):
        val = int(self)
        for e in self.__class__:
            i = int(e)
            if i & val == i:
                while i % 2 == 0 and i != 0:
                    i //= 2
                if i == 1:
                    yield e


[docs] @public class KeypairEnum(ShiftableFlag): # pragma: no cover """ECTester's KeyPair type.""" KEYPAIR_LOCAL = 0x01 KEYPAIR_REMOTE = 0x02 KEYPAIR_BOTH = KEYPAIR_LOCAL | KEYPAIR_REMOTE
[docs] @public class InstructionEnum(IntEnum): # pragma: no cover """ECTester's instruction (INS).""" INS_ALLOCATE = 0x5A INS_CLEAR = 0x5B INS_SET = 0x5C INS_TRANSFORM = 0x5D INS_GENERATE = 0x5E INS_EXPORT = 0x5F INS_ECDH = 0x70 INS_ECDH_DIRECT = 0x71 INS_ECDSA = 0x72 INS_ECDSA_SIGN = 0x73 INS_ECDSA_VERIFY = 0x74 INS_CLEANUP = 0x75 INS_ALLOCATE_KA = 0x76 INS_ALLOCATE_SIG = 0x77 INS_GET_INFO = 0x78 INS_SET_DRY_RUN_MODE = 0x79 INS_BUFFER = 0x7A INS_PERFORM = 0x7B
[docs] @public class KeyBuildEnum(IntEnum): # pragma: no cover """ECTester's key builder type.""" BUILD_KEYPAIR = 0x01 BUILD_KEYBUILDER = 0x02
[docs] @public class ExportEnum(IntEnum): # pragma: no cover """ECTester's export boolean.""" EXPORT_TRUE = 0xFF EXPORT_FALSE = 0x00
[docs] @classmethod def from_bool(cls, val: bool): return cls.EXPORT_TRUE if val else cls.EXPORT_FALSE
[docs] @public class RunModeEnum(IntEnum): # pragma: no cover """ECTester's run mode.""" MODE_NORMAL = 0xAA MODE_DRY_RUN = 0xBB
[docs] @public class KeyEnum(ShiftableFlag): # pragma: no cover """ECTester's key enum.""" PUBLIC = 0x01 PRIVATE = 0x02 BOTH = PRIVATE | PUBLIC
[docs] @public class AppletBaseEnum(IntEnum): # pragma: no cover """ECTester's JavaCard applet base version.""" BASE_221 = 0x0221 BASE_222 = 0x0222
[docs] @public class KeyClassEnum(IntEnum): # pragma: no cover """JavaCard EC-based key class.""" ALG_EC_F2M = 4 ALG_EC_FP = 5
[docs] @public class KeyAgreementEnum(IntEnum): # pragma: no cover """JavaCard `KeyAgreement` type values.""" ALG_EC_SVDP_DH = 1 ALG_EC_SVDP_DH_KDF = 1 ALG_EC_SVDP_DHC = 2 ALG_EC_SVDP_DHC_KDF = 2 ALG_EC_SVDP_DH_PLAIN = 3 ALG_EC_SVDP_DHC_PLAIN = 4 ALG_EC_PACE_GM = 5 ALG_EC_SVDP_DH_PLAIN_XY = 6
[docs] @public class SignatureEnum(IntEnum): # pragma: no cover """JavaCard `Signature` type values.""" ALG_ECDSA_SHA = 17 ALG_ECDSA_SHA_224 = 37 ALG_ECDSA_SHA_256 = 33 ALG_ECDSA_SHA_384 = 34 ALG_ECDSA_SHA_512 = 38
[docs] @public class TransformationEnum(ShiftableFlag): # pragma: no cover """ECTester's point/value transformation types.""" NONE = 0x00 FIXED = 0x01 FULLRANDOM = 0x02 ONEBYTERANDOM = 0x04 ZERO = 0x08 ONE = 0x10 MAX = 0x20 INCREMENT = 0x40 INFINITY = 0x80 COMPRESS = 0x0100 COMPRESS_HYBRID = 0x0200 MASK_04 = 0x0400
[docs] @public class FormatEnum(IntEnum): # pragma: no cover """ECTester's point format types.""" UNCOMPRESSED = 0 COMPRESSED = 1 HYBRID = 2
[docs] @public class CurveEnum(IntEnum): # pragma: no cover """ECTester's curve constants.""" default = 0x00 external = 0xFF secp112r1 = 0x01 secp128r1 = 0x02 secp160r1 = 0x03 secp192r1 = 0x04 secp224r1 = 0x05 secp256r1 = 0x06 secp384r1 = 0x07 secp521r1 = 0x08 sect163r1 = 0x09 sect233r1 = 0x0A sect283r1 = 0x0B sect409r1 = 0x0C sect571r1 = 0x0D
[docs] @public class ParameterEnum(ShiftableFlag): # pragma: no cover """ECTester's parameter ids.""" NONE = 0x00 FP = 0x01 F2M = 0x02 A = 0x04 B = 0x08 G = 0x10 R = 0x20 K = 0x40 W = 0x80 S = 0x0100 DOMAIN_FP = FP | A | B | G | R | K DOMAIN_F2M = F2M | A | B | G | R | K KEYPAIR = W | S ALL = FP | F2M | A | B | G | R | K | W | S
[docs] @public class ChunkingException(Exception): # pragma: no cover """An exception that is raised if an error happened during the chunking process of a large APDU.""" pass
class Response(ABC): # pragma: no cover """An abstract base class of a response APDU.""" resp: ResponseAPDU sws: List[int] params: List[bytes] success: bool = True error: bool = False def __init__(self, resp: ResponseAPDU, num_sw: int, num_params: int): self.resp = resp self.sws = [0 for _ in range(num_sw)] self.params = [bytes() for _ in range(num_params)] offset = 0 for i in range(num_sw): if len(resp.data) >= offset + 2: self.sws[i] = int.from_bytes(resp.data[offset: offset + 2], "big") offset += 2 if self.sws[i] != ISO7816.SW_NO_ERROR: self.success = False else: self.success = False self.error = True if self.resp.sw != ISO7816.SW_NO_ERROR: self.success = False self.error = False for i in range(num_params): if len(resp.data) < offset + 2: self.success = False self.error = True break param_len = int.from_bytes(resp.data[offset: offset + 2], "big") offset += 2 if len(resp.data) < offset + param_len: self.success = False self.error = True break self.params[i] = resp.data[offset: offset + param_len] offset += param_len def __repr__(self): return f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, success={self.success}, error={self.error})"
[docs] @public class AllocateKaResponse(Response): # pragma: no cover """A response to the KeyAgreement allocation command.""" def __init__(self, resp: ResponseAPDU): super().__init__(resp, 1, 0)
[docs] @public class AllocateSigResponse(Response): # pragma: no cover """A response to the Signature allocation command.""" def __init__(self, resp: ResponseAPDU): super().__init__(resp, 1, 0)
[docs] @public class AllocateResponse(Response): # pragma: no cover """A response to the KeyPair allocation command.""" def __init__(self, resp: ResponseAPDU, keypair: KeypairEnum): super().__init__(resp, 2 if keypair == KeypairEnum.KEYPAIR_BOTH else 1, 0)
[docs] @public class ClearResponse(Response): # pragma: no cover """A response to the Clear key command.""" def __init__(self, resp: ResponseAPDU, keypair: KeypairEnum): super().__init__(resp, 2 if keypair == KeypairEnum.KEYPAIR_BOTH else 1, 0)
[docs] @public class SetResponse(Response): # pragma: no cover """A response to the Set command.""" def __init__(self, resp: ResponseAPDU, keypair: KeypairEnum): super().__init__(resp, 2 if keypair == KeypairEnum.KEYPAIR_BOTH else 1, 0)
[docs] @public class TransformResponse(Response): # pragma: no cover """A response to the Transform command.""" def __init__(self, resp: ResponseAPDU, keypair: KeypairEnum): super().__init__(resp, 2 if keypair == KeypairEnum.KEYPAIR_BOTH else 1, 0)
[docs] @public class GenerateResponse(Response): # pragma: no cover """A response to the Generate command.""" def __init__(self, resp: ResponseAPDU, keypair: KeypairEnum): super().__init__(resp, 2 if keypair == KeypairEnum.KEYPAIR_BOTH else 1, 0)
[docs] @public class ExportResponse(Response): # pragma: no cover """A response to the Export command, contains the exported parameters/values.""" keypair: KeypairEnum key: KeyEnum parameters: ParameterEnum def __init__( self, resp: ResponseAPDU, keypair: KeypairEnum, key: KeyEnum, params: ParameterEnum, ): self.keypair = keypair self.key = key self.parameters = params exported = 2 if keypair == KeypairEnum.KEYPAIR_BOTH else 1 keys = 2 if key == KeyEnum.BOTH else 1 param_count = 0 param = ParameterEnum.FP while True: if param & params: param_count += 1 if param == ParameterEnum.K: break param <<= 1 other = 0 other += 1 if key & KeyEnum.PUBLIC and params & ParameterEnum.W else 0 other += 1 if key & KeyEnum.PRIVATE and params & ParameterEnum.S else 0 super().__init__( resp, exported, exported * keys * param_count + exported * other )
[docs] def get_index(self, keypair: KeypairEnum, param: ParameterEnum) -> Optional[int]: pair = KeypairEnum.KEYPAIR_LOCAL index = 0 while True: mask = ParameterEnum.FP while True: if pair == keypair and param == mask: return index if self.parameters & mask and self.keypair & pair: if mask == ParameterEnum.W: if self.key & KeyEnum.PUBLIC: index += 1 elif mask == ParameterEnum.S: if self.key & KeyEnum.PRIVATE: index += 1 else: index += 1 if mask == ParameterEnum.S: break mask <<= 1 if pair == KeypairEnum.KEYPAIR_REMOTE: break pair <<= 1 return None
[docs] def get_param(self, keypair: KeypairEnum, param: ParameterEnum) -> Optional[bytes]: index = self.get_index(keypair, param) if index is not None: return self.params[index] return None
def __repr__(self): return ( f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, success={self.success}, error={self.error}, " f"keypair={self.keypair.name}, key={self.key.name}, params={self.parameters.name})" )
[docs] @public class ECDHResponse(Response): # pragma: no cover """A response to the ECDH and ECDH_direct KeyAgreement commands.""" def __init__(self, resp: ResponseAPDU, export: bool): super().__init__(resp, 1, 1 if export else 0) @property def secret(self): if len(self.params) != 0: return self.params[0] return None def __repr__(self): return f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, success={self.success}, error={self.error}, secret={hexlify(self.secret).decode() if self.secret else ''})"
[docs] @public class ECDSAResponse(Response): # pragma: no cover """A response to the ECDSA and ECDSA sign and ECDSA verify commands.""" def __init__(self, resp: ResponseAPDU, export: bool): super().__init__(resp, 1, 1 if export else 0) @property def signature(self): if len(self.params) != 0: return self.params[0] return None def __repr__(self): return f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, success={self.success}, error={self.error}, sig={hexlify(self.signature).decode() if self.signature else ''})"
[docs] @public class CleanupResponse(Response): # pragma: no cover """A response to the Cleanup command.""" def __init__(self, resp: ResponseAPDU): super().__init__(resp, 1, 0)
[docs] @public class RunModeResponse(Response): # pragma: no cover """A response to the Set run mode command.""" def __init__(self, resp: ResponseAPDU): super().__init__(resp, 1, 0)
class InfoResponse(Response): # pragma: no cover """A response to the Info command, contains all information about the applet version/environment.""" version: str base: AppletBaseEnum system_version: float object_deletion_supported: bool buf_len: int ram1_len: int ram2_len: int apdu_len: int def __init__(self, resp: ResponseAPDU): super().__init__(resp, 1, 0) offset = 2 version_len = int.from_bytes(resp.data[offset: offset + 2], "big") offset += 2 self.version = resp.data[offset: offset + version_len].decode() offset += version_len self.base = AppletBaseEnum( int.from_bytes(resp.data[offset: offset + 2], "big") ) offset += 2 system_version = int.from_bytes(resp.data[offset: offset + 2], "big") system_major = system_version >> 8 system_minor = system_version & 0xFF minor_size = 1 if system_minor == 0 else ceil(log(system_minor, 10)) self.system_version = system_major + system_minor / (minor_size * 10) offset += 2 self.object_deletion_supported = ( int.from_bytes(resp.data[offset: offset + 2], "big") == 1 ) offset += 2 self.buf_len = int.from_bytes(resp.data[offset: offset + 2], "big") offset += 2 self.ram1_len = int.from_bytes(resp.data[offset: offset + 2], "big") offset += 2 self.ram2_len = int.from_bytes(resp.data[offset: offset + 2], "big") offset += 2 self.apdu_len = int.from_bytes(resp.data[offset: offset + 2], "big") offset += 2 def __repr__(self): return ( f"{self.__class__.__name__}(sws=[{', '.join(list(map(hex, self.sws)))}], sw={hex(self.resp.sw)}, " f"success={self.success}, error={self.error}, version={self.version}, base={self.base.name}, system_version={self.system_version}, " f"object_deletion_supported={self.object_deletion_supported}, buf_len={self.buf_len}, ram1_len={self.ram1_len}, ram2_len={self.ram2_len}, apdu_len={self.apdu_len})" )
[docs] @public class ECTesterTarget(ISO7816Target, ABC): # pragma: no cover """Smartcard target which communicates with the `ECTester <https://github.com/crocs-muni/ECTester>`_ sapplet on smartcards of the JavaCard platform using PCSC.""" CLA_ECTESTER = 0xB0 AID_PREFIX = bytes([0x45, 0x43, 0x54, 0x65, 0x73, 0x74, 0x65, 0x72]) AID_CURRENT_VERSION = bytes([0x30, 0x33, 0x33]) # Version v0.3.3 AID_SUFFIX_221 = bytes([0x62]) AID_SUFFIX_222 = bytes([0x78]) AID_SUFFIX_304 = bytes([0x94]) chunking: bool
[docs] def connect(self, protocol: Optional[CardProtocol] = None): self.chunking = False try: super().connect(CardProtocol.T1) except CardConnectionException: super().connect(CardProtocol.T0) self.chunking = True
[docs] def send(self, apdu: CommandAPDU) -> ResponseAPDU: if self.chunking: data = bytes(apdu) num_chunks = (len(data) + 254) // 255 for i in range(num_chunks): chunk_start = i * 255 chunk_length = 255 if chunk_start + chunk_length > len(data): chunk_length = len(data) - chunk_start chunk = data[chunk_start: chunk_start + chunk_length] chunk_apdu = CommandAPDU( self.CLA_ECTESTER, InstructionEnum.INS_BUFFER, 0, 0, chunk ) resp = self.send_apdu(chunk_apdu) if resp.sw != 0x9000: raise ChunkingException() apdu = CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_PERFORM, 0, 0) resp = self.send_apdu(apdu) if resp.sw & 0xFF00 == ISO7816.SW_BYTES_REMAINING_00: resp = self.send_apdu( CommandAPDU(0x00, 0xC0, 0x00, 0x00, None, resp.sw & 0xFF) ) return resp
[docs] def select_applet( self, latest_version: bytes = AID_CURRENT_VERSION, count_back: int = 10 ) -> bool: """ Select the *ECTester* applet, with a specified version or older. :param latest_version: The latest version to try. :param count_back: How many versions back to try. :return: Whether an applet was successfully selected. """ version_bytes = bytearray(latest_version) for _ in range(count_back): for aid_suffix in (self.AID_SUFFIX_304, self.AID_SUFFIX_222, self.AID_SUFFIX_221): aid = self.AID_PREFIX + version_bytes + aid_suffix if self.select(aid): return True # Count down by versions if version_bytes[2] == 0x30: if version_bytes[1] == 0x30: if version_bytes[0] == 0x30: return False else: version_bytes[0] -= 1 version_bytes[1] = 0x39 version_bytes[2] = 0x39 else: version_bytes[1] -= 1 version_bytes[2] = 0x39 else: version_bytes[2] -= 1 return False
[docs] @staticmethod def encode_parameters( params: ParameterEnum, obj: Union[DomainParameters, Point, int] ) -> Mapping[ParameterEnum, bytes]: """Encode values from `obj` into the byte parameters that the **ECTester** applet expects.""" def convert_int(obj: int) -> bytes: ilen = (obj.bit_length() + 7) // 8 return obj.to_bytes(ilen, "big") def convert_point(obj: Point) -> bytes: return bytes(obj) result = {} if isinstance(obj, DomainParameters) and isinstance( obj.curve.model, ShortWeierstrassModel ): for param in params & ParameterEnum.DOMAIN_FP: if param == ParameterEnum.G: result[param] = convert_point(obj.generator.to_affine()) elif param == ParameterEnum.FP: result[param] = convert_int(obj.curve.prime) elif param == ParameterEnum.A: result[param] = convert_int(obj.curve.parameters["a"].x) elif param == ParameterEnum.B: result[param] = convert_int(obj.curve.parameters["b"].x) elif param == ParameterEnum.R: result[param] = convert_int(obj.order) elif param == ParameterEnum.K: result[param] = convert_int(obj.cofactor) elif isinstance(obj, Point): for param in params & (ParameterEnum.G | ParameterEnum.W): result[param] = convert_point(obj) elif isinstance(obj, int): for param in params & ( (ParameterEnum.DOMAIN_FP ^ ParameterEnum.G) | ParameterEnum.S ): result[param] = convert_int(obj) else: raise TypeError return result
[docs] def allocate_ka(self, ka_type: KeyAgreementEnum) -> AllocateKaResponse: """ Send the Allocate KeyAgreement command. :param ka_type: Which KeyAgreement type to allocate. :return: The response. """ resp = self.send( CommandAPDU( self.CLA_ECTESTER, InstructionEnum.INS_ALLOCATE_KA, 0, 0, bytes([ka_type]), ) ) return AllocateKaResponse(resp)
[docs] def allocate_sig(self, sig_type: SignatureEnum) -> AllocateSigResponse: """ Send the Allocate Signature command. :param sig_type: Which Signature type to allocate. :return: The response. """ resp = self.send( CommandAPDU( self.CLA_ECTESTER, InstructionEnum.INS_ALLOCATE_SIG, 0, 0, bytes([sig_type]), ) ) return AllocateSigResponse(resp)
[docs] def allocate( self, keypair: KeypairEnum, builder: KeyBuildEnum, key_length: int, key_class: KeyClassEnum, ) -> AllocateResponse: """ Send the Allocate KeyPair command. :param keypair: Which keypair to allocate. :param builder: Which builder to use to allocate the keypair. :param key_length: Bit-size of the allocated keypair. :param key_class: Type of the allocated keypair. :return: The response. """ resp = self.send( CommandAPDU( self.CLA_ECTESTER, InstructionEnum.INS_ALLOCATE, keypair, builder, key_length.to_bytes(2, "big") + bytes([key_class]), ) ) return AllocateResponse(resp, keypair)
[docs] def clear(self, keypair: KeypairEnum) -> ClearResponse: """ Send the Clear key command. :param keypair: Which keypair to clear. :return: The response. """ resp = self.send( CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_CLEAR, keypair, 0, None) ) return ClearResponse(resp, keypair)
[docs] def set( self, keypair: KeypairEnum, curve: CurveEnum, params: ParameterEnum, values: Optional[Mapping[ParameterEnum, bytes]] = None, ) -> SetResponse: """ Send the Set command. :param keypair: Which keypair to set values on. :param curve: Which pre-set curve to use to set values, or default or external. :param params: Which parameters to set on the keypair. :param values: External values to set on the keypair. :return: The response. """ if curve == CurveEnum.external and values is not None: if params != reduce(or_, values.keys()): raise ValueError("Params and values need to have the same keys.") payload = params.to_bytes(2, "big") e = ParameterEnum.FP while True: if e in values: payload += len(values[e]).to_bytes(2, "big") + values[e] if e == ParameterEnum.S: break e <<= 1 resp = self.send( CommandAPDU( self.CLA_ECTESTER, InstructionEnum.INS_SET, keypair, curve, payload ) ) elif values is not None: raise ValueError("Values should be specified only if curve is external.") else: resp = self.send( CommandAPDU( self.CLA_ECTESTER, InstructionEnum.INS_SET, keypair, curve, params.to_bytes(2, "big"), ) ) return SetResponse(resp, keypair)
[docs] def transform( self, keypair: KeypairEnum, key: KeyEnum, params: ParameterEnum, transformation: TransformationEnum, ) -> TransformResponse: """ Send the Transform command. :param keypair: Which keypair to transform. :param key: Which key to apply the transform to. :param params: Which parameters to transform. :param transformation: What transformation to apply. :return: The response. """ resp = self.send( CommandAPDU( self.CLA_ECTESTER, InstructionEnum.INS_TRANSFORM, keypair, key, params.to_bytes(2, "big") + transformation.to_bytes(2, "big"), ) ) return TransformResponse(resp, keypair)
[docs] def generate(self, keypair: KeypairEnum) -> GenerateResponse: """ Send the Generate command. :param keypair: Which keypair to generate. :return: The response. """ resp = self.send( CommandAPDU( self.CLA_ECTESTER, InstructionEnum.INS_GENERATE, keypair, 0, None ) ) return GenerateResponse(resp, keypair)
[docs] def export( self, keypair: KeypairEnum, key: KeyEnum, params: ParameterEnum ) -> ExportResponse: """ Send the Export command. :param keypair: Which keypair to export from. :param key: Which key to export from. :param params: Which parameters to export. :return: The response, containing the exported parameters. """ resp = self.send( CommandAPDU( self.CLA_ECTESTER, InstructionEnum.INS_EXPORT, keypair, key, params.to_bytes(2, "big"), ) ) return ExportResponse(resp, keypair, key, params)
[docs] def ecdh( self, pubkey: KeypairEnum, privkey: KeypairEnum, export: bool, transformation: TransformationEnum, ka_type: KeyAgreementEnum, ) -> ECDHResponse: """ Send the ECDH command. :param pubkey: Which keypair to use the pubkey from, in the key-agreement. :param privkey: Which keypair to use the privkey from, in the key-agreement. :param export: Whether to export the shared secret. :param transformation: The transformation to apply to the pubkey before key-agreement. :param ka_type: The key-agreement type to use. :return: The response. """ resp = self.send( CommandAPDU( self.CLA_ECTESTER, InstructionEnum.INS_ECDH, pubkey, privkey, bytes([ExportEnum.from_bool(export)]) + transformation.to_bytes(2, "big") + bytes([ka_type]), ) ) return ECDHResponse(resp, export)
[docs] def ecdh_direct( self, privkey: KeypairEnum, export: bool, transformation: TransformationEnum, ka_type: KeyAgreementEnum, pubkey: bytes, ) -> ECDHResponse: """ Send the ECDH direct command. :param privkey: Which keypair to use the privkey from, in the key-agreement. :param export: Whether to export the shared secret. :param transformation: The transformation to apply to the pubkey before key-agreement. :param ka_type: The key-agreement type to use. :param pubkey: The raw bytes that will be used as a pubkey in the key-agreement. :return: The response. """ resp = self.send( CommandAPDU( self.CLA_ECTESTER, InstructionEnum.INS_ECDH_DIRECT, privkey, ExportEnum.from_bool(export), transformation.to_bytes(2, "big") + bytes([ka_type]) + len(pubkey).to_bytes(2, "big") + pubkey, ) ) return ECDHResponse(resp, export)
[docs] def ecdsa( self, keypair: KeypairEnum, export: bool, sig_type: SignatureEnum, data: bytes ) -> ECDSAResponse: """ Send the ECDSA command. :param keypair: The keypair to use. :param export: Whether to export the signature. :param sig_type: The Signature type to use. :param data: The data to sign and verify. :return: The response. """ resp = self.send( CommandAPDU( self.CLA_ECTESTER, InstructionEnum.INS_ECDSA, keypair, ExportEnum.from_bool(export), bytes([sig_type]) + len(data).to_bytes(2, "big") + data, ) ) return ECDSAResponse(resp, export)
[docs] def ecdsa_sign( self, keypair: KeypairEnum, export: bool, sig_type: SignatureEnum, data: bytes ) -> ECDSAResponse: """ Send the ECDSA sign command. :param keypair: The keypair to use to sign. :param export: Whether to export the signature. :param sig_type: The Signature type to use. :param data: The data to sign. :return: The response. """ resp = self.send( CommandAPDU( self.CLA_ECTESTER, InstructionEnum.INS_ECDSA_SIGN, keypair, ExportEnum.from_bool(export), bytes([sig_type]) + len(data).to_bytes(2, "big") + data, ) ) return ECDSAResponse(resp, export)
[docs] def ecdsa_verify( self, keypair: KeypairEnum, sig_type: SignatureEnum, sig: bytes, data: bytes ) -> ECDSAResponse: """ Send the ECDSA verify command. :param keypair: The keypair to use to verify. :param sig_type: The Signature type to use. :param sig: The signature to verify. :param data: The data. :return: The response. """ resp = self.send( CommandAPDU( self.CLA_ECTESTER, InstructionEnum.INS_ECDSA_VERIFY, keypair, sig_type, len(data).to_bytes(2, "big") + data + len(sig).to_bytes(2, "big") + sig, ) ) return ECDSAResponse(resp, False)
[docs] def cleanup(self) -> CleanupResponse: """ Send the Cleanup command. :return: The response. """ resp = self.send( CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_CLEANUP, 0, 0, None) ) return CleanupResponse(resp)
[docs] def info(self) -> InfoResponse: """ Send the Info command. :return: The response. """ resp = self.send( CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_GET_INFO, 0, 0, None) ) return InfoResponse(resp)
[docs] def run_mode(self, run_mode: RunModeEnum) -> RunModeResponse: """ Send the Run mode command. :return: The response. """ resp = self.send( CommandAPDU( self.CLA_ECTESTER, InstructionEnum.INS_SET_DRY_RUN_MODE, run_mode, 0, None, ) ) return RunModeResponse(resp)
if has_pyscard: from .PCSC import PCSCTarget
[docs] @public class ECTesterTargetPCSC(ECTesterTarget, PCSCTarget): """An ECTester-applet-based target that is connected via a PCSC-compatible reader.""" pass
if has_leia: from .leia import LEIATarget
[docs] @public class ECTesterTargetLEIA(ECTesterTarget, LEIATarget): """An ECTester-applet-based target that is connected via the LEIA board.""" pass