diff options
| author | J08nY | 2020-06-13 01:59:22 +0200 |
|---|---|---|
| committer | J08nY | 2020-06-13 01:59:22 +0200 |
| commit | 4e17dfdb12707c814add7851c81eda4edb3dacde (patch) | |
| tree | 1f59c7adad2cc58e3a53d995b029c5a76f591411 /pyecsca/sca/target | |
| parent | 23b3638a496637c1810fb5a2bd610b63b1a72521 (diff) | |
| download | pyecsca-4e17dfdb12707c814add7851c81eda4edb3dacde.tar.gz pyecsca-4e17dfdb12707c814add7851c81eda4edb3dacde.tar.zst pyecsca-4e17dfdb12707c814add7851c81eda4edb3dacde.zip | |
Diffstat (limited to 'pyecsca/sca/target')
| -rw-r--r-- | pyecsca/sca/target/ISO7816.py | 2 | ||||
| -rw-r--r-- | pyecsca/sca/target/__init__.py | 2 | ||||
| -rw-r--r-- | pyecsca/sca/target/ectester.py | 272 |
3 files changed, 245 insertions, 31 deletions
diff --git a/pyecsca/sca/target/ISO7816.py b/pyecsca/sca/target/ISO7816.py index 8d5385a..3cf712f 100644 --- a/pyecsca/sca/target/ISO7816.py +++ b/pyecsca/sca/target/ISO7816.py @@ -48,7 +48,7 @@ class CommandAPDU(object): # pragma: no cover @dataclass class ResponseAPDU(object): """A response APDU that can be received from an ISO7816-4 target.""" - data: Optional[bytes] + data: bytes sw: int diff --git a/pyecsca/sca/target/__init__.py b/pyecsca/sca/target/__init__.py index a30ea25..f1555dd 100644 --- a/pyecsca/sca/target/__init__.py +++ b/pyecsca/sca/target/__init__.py @@ -18,7 +18,7 @@ except ImportError: # pragma: no cover pass try: - import pyscard + import smartcard has_pyscard = True except ImportError: # pragma: no cover diff --git a/pyecsca/sca/target/ectester.py b/pyecsca/sca/target/ectester.py index 31ff2a2..e69695a 100644 --- a/pyecsca/sca/target/ectester.py +++ b/pyecsca/sca/target/ectester.py @@ -1,16 +1,20 @@ 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 +from typing import Optional, Mapping, List, Union from public import public from smartcard.CardConnection import CardConnection from smartcard.Exceptions import CardConnectionException +from .ISO7816 import CommandAPDU, ResponseAPDU, ISO7816 from .PCSC import PCSCTarget -from .. import CommandAPDU, ResponseAPDU, ISO7816 +from ...ec.model import ShortWeierstrassModel +from ...ec.params import DomainParameters +from ...ec.point import Point class ShiftableFlag(IntFlag): @@ -28,6 +32,16 @@ class ShiftableFlag(IntFlag): 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): @@ -226,50 +240,69 @@ class Response(ABC): 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): + """A response to the KeyAgreement allocation command.""" def __init__(self, resp: ResponseAPDU): super().__init__(resp, 1, 0) +@public class AllocateSigResponse(Response): + """A response to the Signature allocation command.""" def __init__(self, resp: ResponseAPDU): super().__init__(resp, 1, 0) +@public class AllocateResponse(Response): + """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): + """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): + """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): + """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): + """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): + """A response to the Export command, contains the exported parameters/values.""" keypair: KeypairEnum key: KeyEnum parameters: ParameterEnum @@ -288,7 +321,7 @@ class ExportResponse(Response): param_count += 1 if param == ParameterEnum.K: break - param << 1 + 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 @@ -325,45 +358,63 @@ class ExportResponse(Response): 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): + """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: + 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): + """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: + 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): + """A response to the Cleanup command.""" def __init__(self, resp: ResponseAPDU): super().__init__(resp, 1, 0) +@public class RunModeResponse(Response): + """A response to the Set run mode command.""" def __init__(self, resp: ResponseAPDU): super().__init__(resp, 1, 0) class InfoResponse(Response): - sw: int + """A response to the Info command, contains all information about the applet version/environment.""" version: str base: AppletBaseEnum system_version: float @@ -376,9 +427,7 @@ class InfoResponse(Response): def __init__(self, resp: ResponseAPDU): super().__init__(resp, 1, 0) - offset = 0 - self.sw = int.from_bytes(resp.data[offset:offset + 2], "big") - offset += 2 + offset = 2 version_len = int.from_bytes(resp.data[offset:offset + 2], "big") offset += 2 self.version = resp.data[offset:offset + version_len].decode() @@ -402,9 +451,18 @@ class InfoResponse(Response): 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(PCSCTarget): + """ + A smartcard target which communicates with the `ECTester <https://github.com/crocs-muni/ECTester>`_ + applet 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 @@ -442,6 +500,7 @@ class ECTesterTarget(PCSCTarget): return resp def select_applet(self, latest_version: bytes = AID_CURRENT_VERSION): + """Select the *ECTester* applet, with a specified version or older.""" version_bytes = bytearray(latest_version) for i in range(10): aid_222 = self.AID_PREFIX + version_bytes + self.AID_SUFFIX_222 @@ -469,31 +528,104 @@ class ECTesterTarget(PCSCTarget): return False return True - def allocate_ka(self, ka_type: KeyAgreementEnum): + @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_apdu( CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ALLOCATE_KA, 0, 0, bytes([ka_type]))) return AllocateKaResponse(resp) - def allocate_sig(self, sig_type: SignatureEnum): + 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_apdu(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): + 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_apdu( 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): + def clear(self, keypair: KeypairEnum) -> ClearResponse: + """ + Send the Clear key command. + + :param keypair: Which keypair to clear. + :return: The response. + """ resp = self.send_apdu( 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): + 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.") @@ -517,25 +649,58 @@ class ECTesterTarget(PCSCTarget): return SetResponse(resp, keypair) def transform(self, keypair: KeypairEnum, key: KeyEnum, params: ParameterEnum, - transformation: TransformationEnum): + 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_apdu( 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): + def generate(self, keypair: KeypairEnum) -> GenerateResponse: + """ + Send the Generate command. + + :param keypair: Which keypair to generate. + :return: The response. + """ resp = self.send_apdu( CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_GENERATE, keypair, 0, None)) return GenerateResponse(resp, keypair) - def export(self, keypair: KeypairEnum, key: KeyEnum, params: ParameterEnum): + 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_apdu( 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): + 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_apdu( CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ECDH, pubkey, privkey, bytes([ExportEnum.from_bool(export)]) + transformation.to_bytes( @@ -543,7 +708,17 @@ class ECTesterTarget(PCSCTarget): return ECDHResponse(resp, export) def ecdh_direct(self, privkey: KeypairEnum, export: bool, transformation: TransformationEnum, - ka_type: KeyAgreementEnum, pubkey: bytes): + 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_apdu( CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ECDH_DIRECT, privkey, ExportEnum.from_bool(export), @@ -551,16 +726,33 @@ class ECTesterTarget(PCSCTarget): pubkey).to_bytes(2, "big") + pubkey)) return ECDHResponse(resp, export) - def ecdsa(self, keypair: KeypairEnum, export: bool, sig_type: SignatureEnum, data: bytes): + 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_apdu(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: Optional[bytes] = None): - if not data: - data = bytes() + 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_apdu( CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_ECDSA_SIGN, keypair, ExportEnum.from_bool(export), @@ -568,26 +760,48 @@ class ECTesterTarget(PCSCTarget): return ECDSAResponse(resp, export) def ecdsa_verify(self, keypair: KeypairEnum, sig_type: SignatureEnum, sig: bytes, - data: Optional[bytes] = None): - if not data: - data = 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_apdu(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): + def cleanup(self) -> CleanupResponse: + """ + Send the Cleanup command. + + :return: The response. + """ resp = self.send_apdu( CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_CLEANUP, 0, 0, None)) return CleanupResponse(resp) - def info(self): + def info(self) -> InfoResponse: + """ + Send the Info command. + + :return: The response. + """ resp = self.send_apdu( CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_GET_INFO, 0, 0, None)) return InfoResponse(resp) - def run_mode(self, run_mode: RunModeEnum): + def run_mode(self, run_mode: RunModeEnum) -> RunModeResponse: + """ + Send the Run mode command. + + :return: The response. + """ resp = self.send_apdu( CommandAPDU(self.CLA_ECTESTER, InstructionEnum.INS_SET_DRY_RUN_MODE, run_mode, 0, None)) |
