aboutsummaryrefslogtreecommitdiff
path: root/pyecsca/sca/target
diff options
context:
space:
mode:
authorJ08nY2020-06-13 01:59:22 +0200
committerJ08nY2020-06-13 01:59:22 +0200
commit4e17dfdb12707c814add7851c81eda4edb3dacde (patch)
tree1f59c7adad2cc58e3a53d995b029c5a76f591411 /pyecsca/sca/target
parent23b3638a496637c1810fb5a2bd610b63b1a72521 (diff)
downloadpyecsca-4e17dfdb12707c814add7851c81eda4edb3dacde.tar.gz
pyecsca-4e17dfdb12707c814add7851c81eda4edb3dacde.tar.zst
pyecsca-4e17dfdb12707c814add7851c81eda4edb3dacde.zip
Diffstat (limited to 'pyecsca/sca/target')
-rw-r--r--pyecsca/sca/target/ISO7816.py2
-rw-r--r--pyecsca/sca/target/__init__.py2
-rw-r--r--pyecsca/sca/target/ectester.py272
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))