laforge has submitted this change. (
https://gerrit.osmocom.org/c/pysim/+/35462?usp=email
)
Change subject: Introduce GlobalPlatform SCP02 implementation
......................................................................
Introduce GlobalPlatform SCP02 implementation
This implementation of GlobalPlatform SCP02 currently only supports
C-MAC and C-ENC, but no R-MAC or R-ENC yet.
The patch also introduces the notion of having a SCP instance associated
with a SimCardCommands instance. It also adds the establish_scp0w and
release_scp shell commands to all GlobalPlatform Security Domains.
Change-Id: I56020382b9dfe8ba0f7c1c9f71eb1a9746bc5a27
---
M docs/shell.rst
M pySim-shell.py
M pySim/commands.py
M pySim/global_platform/__init__.py
A pySim/global_platform/scp02.py
A pySim/secure_channel.py
A tests/test_globalplatform.py
7 files changed, 453 insertions(+), 4 deletions(-)
Approvals:
Jenkins Builder: Verified
laforge: Looks good to me, approved
diff --git a/docs/shell.rst b/docs/shell.rst
index a917f8f..dff6cd1 100644
--- a/docs/shell.rst
+++ b/docs/shell.rst
@@ -959,6 +959,16 @@
:module: pySim.global_platform
:func: ADF_SD.AddlShellCommands.put_key_parser
+establish_scp02
+~~~~~~~~~~~~~~~
+.. argparse::
+ :module: pySim.global_platform
+ :func: ADF_SD.AddlShellCommands.est_scp02_parser
+
+release_scp
+~~~~~~~~~~~
+Release any previously established SCP (Secure Channel Protocol)
+
eUICC ISD-R commands
--------------------
diff --git a/pySim-shell.py b/pySim-shell.py
index 70eaee2..abe0b5f 100755
--- a/pySim-shell.py
+++ b/pySim-shell.py
@@ -205,7 +205,11 @@
def update_prompt(self):
if self.lchan:
path_str = self.lchan.selected_file.fully_qualified_path_str(not
self.numeric_path)
- self.prompt = 'pySIM-shell (%02u:%s)> ' % (self.lchan.lchan_nr,
path_str)
+ scp = self.lchan.scc.scp
+ if scp:
+ self.prompt = 'pySIM-shell (%s:%02u:%s)> ' % (str(scp),
self.lchan.lchan_nr, path_str)
+ else:
+ self.prompt = 'pySIM-shell (%02u:%s)> ' %
(self.lchan.lchan_nr, path_str)
else:
if self.card:
self.prompt = 'pySIM-shell (no card profile)> '
@@ -258,6 +262,8 @@
def do_reset(self, opts):
"""Reset the Card."""
atr = self.card.reset()
+ if self.lchan and self.lchan.scc.scp:
+ self.lchan.scc.scp = None
self.poutput('Card ATR: %s' % i2h(atr))
self.update_prompt()
diff --git a/pySim/commands.py b/pySim/commands.py
index 2d0736e..81edf82 100644
--- a/pySim/commands.py
+++ b/pySim/commands.py
@@ -69,6 +69,7 @@
self.lchan_nr = lchan_nr
# invokes the setter below
self.cla_byte = "a0"
+ self.scp = None # Secure Channel Protocol
def fork_lchan(self, lchan_nr: int) -> 'SimCardCommands':
"""Fork a per-lchan specific SimCardCommands instance off the
current instance."""
@@ -110,7 +111,10 @@
data : string (in hex) of returned data (ex.
"074F4EFFFF")
sw : string (in hex) of status word (ex. "9000")
"""
- return self._tp.send_apdu(pdu)
+ if self.scp:
+ return self.scp.send_apdu_wrapper(self._tp.send_apdu, pdu)
+ else:
+ return self._tp.send_apdu(pdu)
def send_apdu_checksw(self, pdu: Hexstr, sw: SwMatchstr = "9000") ->
ResTuple:
"""Sends an APDU and check returned SW
@@ -124,7 +128,10 @@
data : string (in hex) of returned data (ex.
"074F4EFFFF")
sw : string (in hex) of status word (ex. "9000")
"""
- return self._tp.send_apdu_checksw(pdu, sw)
+ if self.scp:
+ return self.scp.send_apdu_wrapper(self._tp.send_apdu_checksw, pdu, sw)
+ else:
+ return self._tp.send_apdu_checksw(pdu, sw)
def send_apdu_constr(self, cla: Hexstr, ins: Hexstr, p1: Hexstr, p2: Hexstr,
cmd_constr: Construct,
cmd_data: Hexstr, resp_constr: Construct) -> Tuple[dict,
SwHexstr]:
diff --git a/pySim/global_platform/__init__.py b/pySim/global_platform/__init__.py
index 25b0d02..ad193ee 100644
--- a/pySim/global_platform/__init__.py
+++ b/pySim/global_platform/__init__.py
@@ -1,7 +1,7 @@
# coding=utf-8
"""Partial Support for GlobalPLatform Card Spec (currently 2.1.1)
-(C) 2022-2023 by Harald Welte <laforge(a)osmocom.org>
+(C) 2022-2024 by Harald Welte <laforge(a)osmocom.org>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -21,6 +21,8 @@
from construct import Optional as COptional
from construct import *
from bidict import bidict
+from Cryptodome.Random import get_random_bytes
+from pySim.global_platform.scp02 import SCP02
from pySim.construct import *
from pySim.utils import *
from pySim.filesystem import *
@@ -582,6 +584,48 @@
p2 |= 0x01
return grd_list
+ est_scp02_parser = argparse.ArgumentParser()
+ est_scp02_parser.add_argument('--key-ver', type=auto_uint8,
required=True,
+ help='Key Version Number (KVN)')
+ est_scp02_parser.add_argument('--key-enc', type=is_hexstr,
required=True,
+ help='Secure Channel Encryption Key')
+ est_scp02_parser.add_argument('--key-mac', type=is_hexstr,
required=True,
+ help='Secure Channel MAC Key')
+ est_scp02_parser.add_argument('--key-dek', type=is_hexstr,
required=True,
+ help='Data Encryption Key')
+ est_scp02_parser.add_argument('--host-challenge', type=is_hexstr,
+ help='Hard-code the host challenge; default:
random')
+ est_scp02_parser.add_argument('--security-level', type=auto_uint8,
default=0x01,
+ help='Security Level. Default: 0x01 (C-MAC
only)')
+
+ @cmd2.with_argparser(est_scp02_parser)
+ def do_establish_scp02(self, opts):
+ """Establish a secure channel using the GlobalPlatform SCP02
protocol. It can be released
+ again by using `release_scp`."""
+ if self._cmd.lchan.scc.scp:
+ self._cmd.poutput("Cannot establish SCP02 as this lchan already has
a SCP instance!")
+ return
+ host_challenge = h2b(opts.host_challenge) if opts.host_challenge else
get_random_bytes(8)
+ kset = GpCardKeyset(opts.key_ver, h2b(opts.key_enc), h2b(opts.key_mac),
h2b(opts.key_dek))
+ scp02 = SCP02(card_keys=kset)
+ init_update_apdu = scp02.gen_init_update_apdu(host_challenge=host_challenge)
+ init_update_resp, sw =
self._cmd.lchan.scc.send_apdu_checksw(b2h(init_update_apdu))
+ scp02.parse_init_update_resp(h2b(init_update_resp))
+ ext_auth_apdu = scp02.gen_ext_auth_apdu(opts.security_level)
+ ext_auth_resp, sw =
self._cmd.lchan.scc.send_apdu_checksw(b2h(ext_auth_apdu))
+ self._cmd.poutput("Successfully established a SCP02 secure
channel")
+ # store a reference to the SCP instance
+ self._cmd.lchan.scc.scp = scp02
+ self._cmd.update_prompt()
+
+ def do_release_scp(self, opts):
+ """Release a previously establiehed secure
channel."""
+ if not self._cmd.lchan.scc.scp:
+ self._cmd.poutput("Cannot release SCP as none is established")
+ return
+ self._cmd.lchan.scc.scp = None
+ self._cmd.update_prompt()
+
# Card Application of a Security Domain
class CardApplicationSD(CardApplication):
@@ -601,3 +645,22 @@
#
# def __init__(self, name='GlobalPlatform'):
# super().__init__(name, desc='GlobalPlatfomr 2.1.1',
cla=['00','80','84'], sw=sw_table)
+
+
+class GpCardKeyset:
+ """A single set of GlobalPlatform card keys and the associated
KVN."""
+ def __init__(self, kvn: int, enc: bytes, mac: bytes, dek: bytes):
+ assert kvn >= 0 and kvn < 256
+ assert len(enc) == len(mac) == len(dek)
+ self.kvn = kvn
+ self.enc = enc
+ self.mac = mac
+ self.dek = dek
+
+ @classmethod
+ def from_single_key(cls, kvn: int, base_key: bytes) -> 'GpCardKeyset':
+ return cls(int, base_key, base_key, base_key)
+
+ def __str__(self):
+ return "%s(KVN=%u, ENC=%s, MAC=%s, DEK=%s)" %
(self.__class__.__name__,
+ self.kvn, b2h(self.enc), b2h(self.mac), b2h(self.dek))
diff --git a/pySim/global_platform/scp02.py b/pySim/global_platform/scp02.py
new file mode 100644
index 0000000..eec1180
--- /dev/null
+++ b/pySim/global_platform/scp02.py
@@ -0,0 +1,242 @@
+# Global Platform SCP02 (Secure Channel Protocol) implementation
+#
+# (C) 2023-2024 by Harald Welte <laforge(a)osmocom.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import abc
+import logging
+from Cryptodome.Cipher import DES3, DES
+from Cryptodome.Util.strxor import strxor
+from construct import *
+from pySim.utils import b2h
+from pySim.secure_channel import SecureChannel
+from typing import Optional
+
+logger = logging.getLogger(__name__)
+
+def scp02_key_derivation(constant: bytes, counter: int, base_key: bytes) -> bytes:
+ assert(len(constant) == 2)
+ assert(counter >= 0 and counter <= 65535)
+ assert(len(base_key) == 16)
+
+ derivation_data = constant + counter.to_bytes(2, 'big') + b'\x00' *
12
+ cipher = DES3.new(base_key, DES.MODE_CBC, b'\x00' * 8)
+ return cipher.encrypt(derivation_data)
+
+# FIXME: overlap with BspAlgoCryptAES128
+def pad80(s: bytes, BS=8) -> bytes:
+ """ Pad bytestring s: add '\x80' and '\0'* so the
result to be multiple of BS."""
+ l = BS-1 - len(s) % BS
+ return s + b'\x80' + b'\0'*l
+
+class Scp02SessionKeys:
+ """A single set of GlobalPlatform session keys."""
+ DERIV_CONST_CMAC = b'\x01\x01'
+ DERIV_CONST_RMAC = b'\x01\x02'
+ DERIV_CONST_ENC = b'\x01\x82'
+ DERIV_CONST_DENC = b'\x01\x81'
+
+ def calc_mac_1des(self, data: bytes, reset_icv: bool = False) -> bytes:
+ """Pad and calculate MAC according to B.1.2.2 - Single DES plus
final 3DES"""
+ e = DES.new(self.c_mac[:8], DES.MODE_ECB)
+ d = DES.new(self.c_mac[8:], DES.MODE_ECB)
+ padded_data = pad80(data, 8)
+ q = len(padded_data) // 8
+ icv = b'\x00' * 8 if reset_icv else self.icv
+ h = icv
+ for i in range(q):
+ h = e.encrypt(strxor(h, bytes(padded_data[8*i:8*(i+1)])))
+ h = d.decrypt(h)
+ h = e.encrypt(h)
+ logger.debug("mac_1des(%s,icv=%s) -> %s", b2h(data), b2h(icv),
b2h(h))
+ if self.des_icv_enc:
+ self.icv = self.des_icv_enc.encrypt(h)
+ else:
+ self.icv = h
+ return h
+
+ def calc_mac_3des(self, data: bytes) -> bytes:
+ e = DES3.new(self.enc, DES.MODE_ECB)
+ padded_data = pad80(data, 8)
+ q = len(padded_data) // 8
+ h = b'\x00' * 8
+ for i in range(q):
+ h = e.encrypt(strxor(h, bytes(padded_data[8*i:8*(i+1)])))
+ logger.debug("mac_3des(%s) -> %s", b2h(data), b2h(h))
+ return h
+
+ def __init__(self, counter: int, card_keys: 'GpCardKeyset',
icv_encrypt=True):
+ self.icv = None
+ self.counter = counter
+ self.card_keys = card_keys
+ self.c_mac = scp02_key_derivation(self.DERIV_CONST_CMAC, self.counter,
card_keys.mac)
+ self.r_mac = scp02_key_derivation(self.DERIV_CONST_RMAC, self.counter,
card_keys.mac)
+ self.enc = scp02_key_derivation(self.DERIV_CONST_ENC, self.counter,
card_keys.enc)
+ self.data_enc = scp02_key_derivation(self.DERIV_CONST_DENC, self.counter,
card_keys.dek)
+ self.des_icv_enc = DES.new(self.c_mac[:8], DES.MODE_ECB) if icv_encrypt else
None
+
+ def __str__(self) -> str:
+ return "%s(CTR=%u, ICV=%s, ENC=%s, D-ENC=%s, MAC-C=%s, MAC-R=%s)" % (
+ self.__class__.__name__, self.counter, b2h(self.icv) if self.icv else
"None",
+ b2h(self.enc), b2h(self.data_enc), b2h(self.c_mac), b2h(self.r_mac))
+
+INS_INIT_UPDATE = 0x50
+INS_EXT_AUTH = 0x82
+CLA_SM = 0x04
+
+class SCP(SecureChannel, abc.ABC):
+ """Abstract base class containing some common interface +
functionality for SCP protocols."""
+ def __init__(self, card_keys: 'GpCardKeyset', lchan_nr: int = 0):
+ if hasattr(self, 'kvn_range'):
+ if not card_keys.kvn in range(self.kvn_range[0], self.kvn_range[1]+1):
+ raise ValueError('%s cannot be used with KVN outside range
0x%02x..0x%02x' %
+ (self.__class__.__name__, self.kvn_range[0],
self.kvn_range[1]))
+ self.lchan_nr = lchan_nr
+ self.card_keys = card_keys
+ self.sk = None
+ self.mac_on_unmodified = False
+ self.security_level = 0x00
+
+ @property
+ def do_cmac(self) -> bool:
+ """Should we perform C-MAC?"""
+ return self.security_level & 0x01
+
+ @property
+ def do_rmac(self) -> bool:
+ """Should we perform R-MAC?"""
+ return self.security_level & 0x10
+
+ @property
+ def do_cenc(self) -> bool:
+ """Should we perform C-ENC?"""
+ return self.security_level & 0x02
+
+ @property
+ def do_renc(self) -> bool:
+ """Should we perform R-ENC?"""
+ return self.security_level & 0x20
+
+ def __str__(self) -> str:
+ return "%s[%02x]" % (self.__class__.__name__, self.security_level)
+
+ def _cla(self, sm: bool = False, b8: bool = True) -> int:
+ ret = 0x80 if b8 else 0x00
+ if sm:
+ ret = ret | CLA_SM
+ return ret + self.lchan_nr
+
+ def wrap_cmd_apdu(self, apdu: bytes, *args, **kwargs) -> bytes:
+ # Generic handling of GlobalPlatform SCP, implements SecureChannel.wrap_cmd_apdu
+ # only protect those APDUs that actually are global platform commands
+ if apdu[0] & 0x80:
+ return self._wrap_cmd_apdu(apdu, *args, **kwargs)
+ else:
+ return apdu
+
+ @abc.abstractmethod
+ def _wrap_cmd_apdu(self, apdu: bytes, *args, **kwargs) -> bytes:
+ """Method implementation to be provided by derived
class."""
+ pass
+
+ @abc.abstractmethod
+ def gen_init_update_apdu(self, host_challenge: Optional[bytes]) -> bytes:
+ pass
+
+ @abc.abstractmethod
+ def parse_init_update_resp(self, resp_bin: bytes):
+ pass
+
+ @abc.abstractmethod
+ def gen_ext_auth_apdu(self, security_level: int = 0x01) -> bytes:
+ pass
+
+
+class SCP02(SCP):
+ """An instance of the GlobalPlatform SCP02 secure channel
protocol."""
+
+ constr_iur = Struct('key_div_data'/Bytes(10), 'key_ver'/Int8ub,
Const(b'\x02'),
+ 'seq_counter'/Int16ub, 'card_challenge'/Bytes(6),
'card_cryptogram'/Bytes(8))
+ kvn_range = [0x20, 0x2f]
+
+ def _compute_cryptograms(self, card_challenge: bytes, host_challenge: bytes):
+ logger.debug("host_challenge(%s), card_challenge(%s)",
b2h(host_challenge), b2h(card_challenge))
+ self.host_cryptogram = self.sk.calc_mac_3des(self.sk.counter.to_bytes(2,
'big') + card_challenge + host_challenge)
+ self.card_cryptogram = self.sk.calc_mac_3des(self.host_challenge +
self.sk.counter.to_bytes(2, 'big') + card_challenge)
+ logger.debug("host_cryptogram(%s), card_cryptogram(%s)",
b2h(self.host_cryptogram), b2h(self.card_cryptogram))
+
+ def gen_init_update_apdu(self, host_challenge: bytes = b'\x00'*8) ->
bytes:
+ """Generate INITIALIZE UPDATE APDU."""
+ self.host_challenge = host_challenge
+ return bytes([self._cla(), INS_INIT_UPDATE, self.card_keys.kvn, 0, 8]) +
self.host_challenge
+
+ def parse_init_update_resp(self, resp_bin: bytes):
+ """Parse response to INITIALZIE UPDATE."""
+ resp = self.constr_iur.parse(resp_bin)
+ self.card_challenge = resp['card_challenge']
+ self.sk = Scp02SessionKeys(resp['seq_counter'], self.card_keys)
+ logger.debug(self.sk)
+ self._compute_cryptograms(self.card_challenge, self.host_challenge)
+ if self.card_cryptogram != resp['card_cryptogram']:
+ raise ValueError("card cryptogram doesn't match")
+
+ def gen_ext_auth_apdu(self, security_level: int = 0x01) -> bytes:
+ """Generate EXTERNAL AUTHENTICATE APDU."""
+ if security_level & 0xf0:
+ raise NotImplementedError('R-MAC/R-ENC for SCP02 not implemented
yet.')
+ self.security_level = security_level
+ if self.mac_on_unmodified:
+ header = bytes([self._cla(), INS_EXT_AUTH, self.security_level, 0, 8])
+ else:
+ header = bytes([self._cla(True), INS_EXT_AUTH, self.security_level, 0, 16])
+ #return self.wrap_cmd_apdu(header + self.host_cryptogram)
+ mac = self.sk.calc_mac_1des(header + self.host_cryptogram, True)
+ return bytes([self._cla(True), INS_EXT_AUTH, self.security_level, 0, 16]) +
self.host_cryptogram + mac
+
+ def _wrap_cmd_apdu(self, apdu: bytes) -> bytes:
+ """Wrap Command APDU for SCP02: calculate MAC and
encrypt."""
+ lc = len(apdu) - 5
+ assert len(apdu) >= 5, "Wrong APDU length: %d" % len(apdu)
+ assert len(apdu) == 5 or apdu[4] == lc, "Lc differs from length of data: %d
vs %d" % (apdu[4], lc)
+
+ logger.debug("wrap_cmd_apdu(%s)", b2h(apdu))
+
+ cla = apdu[0]
+ b8 = cla & 0x80
+ if cla & 0x03 or cla & CLA_SM:
+ # nonzero logical channel in APDU, check that are the same
+ assert cla == self._cla(False, b8), "CLA mismatch"
+ # CLA without log. channel can be 80 or 00 only
+ if self.do_cmac:
+ if self.mac_on_unmodified:
+ mlc = lc
+ clac = cla
+ else: # CMAC on modified APDU
+ mlc = lc + 8
+ clac = cla | CLA_SM
+ mac = self.sk.calc_mac_1des(bytes([clac]) + apdu[1:4] + bytes([mlc]) +
apdu[5:])
+ if self.do_cenc:
+ k = DES3.new(self.sk.enc, DES.MODE_CBC, b'\x00'*8)
+ data = k.encrypt(pad80(apdu[5:], 8))
+ lc = len(data)
+ else:
+ data = apdu[5:]
+ lc += 8
+ apdu = bytes([self._cla(True, b8)]) + apdu[1:4] + bytes([lc]) + data + mac
+ return apdu
+
+ def unwrap_rsp_apdu(self, sw: bytes, apdu: bytes) -> bytes:
+ # TODO: Implement R-MAC / R-ENC
+ return apdu
diff --git a/pySim/secure_channel.py b/pySim/secure_channel.py
new file mode 100644
index 0000000..974780e
--- /dev/null
+++ b/pySim/secure_channel.py
@@ -0,0 +1,37 @@
+# Generic code related to Secure Channel processing
+#
+# (C) 2023-2024 by Harald Welte <laforge(a)osmocom.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import abc
+from pySim.utils import b2h, h2b, ResTuple, Hexstr
+
+class SecureChannel(abc.ABC):
+ @abc.abstractmethod
+ def wrap_cmd_apdu(self, apdu: bytes) -> bytes:
+ """Wrap Command APDU according to specific Secure Channel
Protocol."""
+ pass
+
+ @abc.abstractmethod
+ def unwrap_rsp_apdu(self, sw: bytes, rsp_apdu: bytes) -> bytes:
+ """UnWrap Response-APDU according to specific Secure Channel
Protocol."""
+ pass
+
+ def send_apdu_wrapper(self, send_fn: callable, pdu: Hexstr, *args, **kwargs) ->
ResTuple:
+ """Wrapper function to wrap command APDU and unwrap repsonse APDU
around send_apdu callable."""
+ pdu_wrapped = b2h(self.wrap_cmd_apdu(h2b(pdu)))
+ res, sw = send_fn(pdu_wrapped, *args, **kwargs)
+ res_unwrapped = b2h(self.unwrap_rsp_apdu(h2b(sw), h2b(res)))
+ return res_unwrapped, sw
diff --git a/tests/test_globalplatform.py b/tests/test_globalplatform.py
new file mode 100644
index 0000000..280199f
--- /dev/null
+++ b/tests/test_globalplatform.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+
+# (C) 2023-2024 by Harald Welte <laforge(a)osmocom.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+import logging
+
+from pySim.global_platform import *
+from pySim.global_platform.scp02 import SCP02
+from pySim.utils import b2h, h2b
+
+KIC = h2b('100102030405060708090a0b0c0d0e0f') # enc
+KID = h2b('101102030405060708090a0b0c0d0e0f') # MAC
+KIK = h2b('102102030405060708090a0b0c0d0e0f') # DEK
+ck_3des_70 = GpCardKeyset(0x20, KIC, KID, KIK)
+
+class SCP02_Auth_Test(unittest.TestCase):
+ host_challenge = h2b('40A62C37FA6304F8')
+ init_update_resp =
h2b('00000000000000000000700200016B4524ABEE7CF32EA3838BC148F3')
+
+ def setUp(self):
+ self.scp02 = SCP02(card_keys=ck_3des_70)
+
+ def test_mutual_auth_success(self):
+ init_upd_cmd =
self.scp02.gen_init_update_apdu(host_challenge=self.host_challenge)
+ self.assertEqual(b2h(init_upd_cmd).upper(),
'805020000840A62C37FA6304F8')
+ self.scp02.parse_init_update_resp(self.init_update_resp)
+ ext_auth_cmd = self.scp02.gen_ext_auth_apdu()
+ self.assertEqual(b2h(ext_auth_cmd).upper(),
'8482010010BA6961667737C5BCEBECE14C7D6A4376')
+
+ def test_mutual_auth_fail_card_cryptogram(self):
+ init_upd_cmd =
self.scp02.gen_init_update_apdu(host_challenge=self.host_challenge)
+ self.assertEqual(b2h(init_upd_cmd).upper(),
'805020000840A62C37FA6304F8')
+ wrong_init_update_resp = self.init_update_resp.copy()
+ wrong_init_update_resp[-1:] = b'\xff'
+ with self.assertRaises(ValueError):
+ self.scp02.parse_init_update_resp(wrong_init_update_resp)
+
+
+class SCP02_Test(unittest.TestCase):
+ host_challenge = h2b('40A62C37FA6304F8')
+ init_update_resp =
h2b('00000000000000000000700200016B4524ABEE7CF32EA3838BC148F3')
+
+ def setUp(self):
+ self.scp02 = SCP02(card_keys=ck_3des_70)
+ init_upd_cmd =
self.scp02.gen_init_update_apdu(host_challenge=self.host_challenge)
+ self.scp02.parse_init_update_resp(self.init_update_resp)
+ ext_auth_cmd = self.scp02.gen_ext_auth_apdu()
+
+ def test_mac_command(self):
+ wrapped = self.scp02.wrap_cmd_apdu(h2b('80f28002024f00'))
+ self.assertEqual(b2h(wrapped).upper(), '84F280020A4F00B21AAFA3EB2D1672')
+
+if __name__ == "__main__":
+ unittest.main()
--
To view, visit
https://gerrit.osmocom.org/c/pysim/+/35462?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I56020382b9dfe8ba0f7c1c9f71eb1a9746bc5a27
Gerrit-Change-Number: 35462
Gerrit-PatchSet: 10
Gerrit-Owner: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: dexter <pmaier(a)sysmocom.de>
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-MessageType: merged