"""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 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 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 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