"""Provides an `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 pyecsca.sca.target.ISO7816 import ( CommandAPDU, ResponseAPDU, ISO7816, ISO7816Target, CardProtocol, CardConnectionException, ) from pyecsca.sca.target import has_leia, has_pyscard from pyecsca.ec.model import ShortWeierstrassModel from pyecsca.ec.params import DomainParameters from pyecsca.ec.point import Point class ShiftableFlag(IntFlag): # pragma: no cover """An `IntFlag` that can be shifted.""" 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 @public class KeypairEnum(ShiftableFlag): # pragma: no cover """ECTester's KeyPair type.""" KEYPAIR_LOCAL = 0x01 KEYPAIR_REMOTE = 0x02 KEYPAIR_BOTH = KEYPAIR_LOCAL | KEYPAIR_REMOTE @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 @public class KeyBuildEnum(IntEnum): # pragma: no cover """ECTester's key builder type.""" BUILD_KEYPAIR = 0x01 BUILD_KEYBUILDER = 0x02 @public class ExportEnum(IntEnum): # pragma: no cover """ECTester's export boolean.""" EXPORT_TRUE = 0xFF EXPORT_FALSE = 0x00 @classmethod def from_bool(cls, val: bool): """Construct an `ExportEnum` from a boolean.""" return cls.EXPORT_TRUE if val else cls.EXPORT_FALSE @public class RunModeEnum(IntEnum): # pragma: no cover """ECTester's run mode.""" MODE_NORMAL = 0xAA MODE_DRY_RUN = 0xBB @public class KeyEnum(ShiftableFlag): # pragma: no cover """ECTester's key enum.""" PUBLIC = 0x01 PRIVATE = 0x02 BOTH = PRIVATE | PUBLIC @public class AppletBaseEnum(IntEnum): # pragma: no cover """ECTester's JavaCard applet base version.""" BASE_221 = 0x0221 BASE_222 = 0x0222 @public class KeyClassEnum(IntEnum): # pragma: no cover """JavaCard EC-based key class.""" ALG_EC_F2M = 4 ALG_EC_FP = 5 @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 @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 @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 @public class FormatEnum(IntEnum): # pragma: no cover """ECTester's point format types.""" UNCOMPRESSED = 0 COMPRESSED = 1 HYBRID = 2 @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 @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 @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})" @public class AllocateKaResponse(Response): # pragma: no cover """A response to the KeyAgreement allocation command.""" def __init__(self, resp: ResponseAPDU): super().__init__(resp, 1, 0) @public class AllocateSigResponse(Response): # pragma: no cover """A response to the Signature allocation command.""" def __init__(self, resp: ResponseAPDU): super().__init__(resp, 1, 0) @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) @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) @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) @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) @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) @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 ) 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 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})" ) @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 ''})" @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 ''})" @public class CleanupResponse(Response): # pragma: no cover """A response to the Cleanup command.""" def __init__(self, resp: ResponseAPDU): super().__init__(resp, 1, 0) @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})" ) @public class ECTesterTarget(ISO7816Target, ABC): # pragma: no cover """Smartcard target which communicates with the `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([0x20, 0x32, 0x32, 0x31]) AID_SUFFIX_222 = bytes([0x20, 0x32, 0x32, 0x32]) AID_SUFFIX_305 = bytes([0x20, 0x33, 0x30, 0x35]) chunking: bool def connect(self, protocol: Optional[CardProtocol] = None): self.chunking = False try: super().connect(CardProtocol.T1) # type: ignore except CardConnectionException: super().connect(CardProtocol.T0) # type: ignore self.chunking = True 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 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_305, 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 @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 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) 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) 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) 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) 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) 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) 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) 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) 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) 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) 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) 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) 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) 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) 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) 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 pyecsca.sca.target.PCSC import PCSCTarget @public class ECTesterTargetPCSC(ECTesterTarget, PCSCTarget): """An ECTester-applet-based target that is connected via a PCSC-compatible reader.""" pass if has_leia: from pyecsca.sca.target.leia import LEIATarget @public class ECTesterTargetLEIA(ECTesterTarget, LEIATarget): """An ECTester-applet-based target that is connected via the LEIA board.""" pass