laforge has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/35821?usp=email )
Change subject: pylint: transport/modem_atcmd.py
......................................................................
pylint: transport/modem_atcmd.py
pySim/transport/modem_atcmd.py:70:0: C0325: Unnecessary parens after 'assert' keyword (superfluous-parens)
pySim/transport/modem_atcmd.py:28:0: W0401: Wildcard import pySim.exceptions (wildcard-import)
pySim/transport/modem_atcmd.py:60:22: C0123: Use isinstance() rather than type() for a typecheck. (unidiomatic-typecheck)
pySim/transport/modem_atcmd.py:72:12: W0707: Consider explicitly re-raising using 'except Exception as exc' and 'raise ReaderError('Failed to send AT command: %s' % cmd) from exc' (raise-missing-from)
pySim/transport/modem_atcmd.py:120:12: R1705: Unnecessary "elif" after "return", remove the leading "el" from "elif" (no-else-return)
pySim/transport/modem_atcmd.py:138:8: W1201: Use lazy % formatting in logging functions (logging-not-lazy)
pySim/transport/modem_atcmd.py:170:12: W0707: Consider explicitly re-raising using 'except Exception as exc' and 'raise ReaderError('Failed to parse response from modem: %s' % rsp) from exc' (raise-missing-from)
pySim/transport/modem_atcmd.py:168:13: W0612: Unused variable 'rsp_pdu_len' (unused-variable)
pySim/transport/modem_atcmd.py:21:0: C0411: standard import "import time" should be placed before "import serial" (wrong-import-order)
pySim/transport/modem_atcmd.py:22:0: C0411: standard import "import re" should be placed before "import serial" (wrong-import-order)
pySim/transport/modem_atcmd.py:23:0: C0411: standard import "import argparse" should be placed before "import serial" (wrong-import-order)
pySim/transport/modem_atcmd.py:24:0: C0411: standard import "from typing import Optional" should be placed before "import serial" (wrong-import-order)
pySim/transport/modem_atcmd.py:28:0: W0614: Unused import(s) NoCardError and SwMatchError from wildcard import of pySim.exceptions (unused-wildcard-import)
Change-Id: I2c8994eabd973b65132af1030429b1021d0c20df
---
M pySim/transport/modem_atcmd.py
1 file changed, 36 insertions(+), 15 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/21/35821/1
diff --git a/pySim/transport/modem_atcmd.py b/pySim/transport/modem_atcmd.py
index a05d6c4..5943e3a 100644
--- a/pySim/transport/modem_atcmd.py
+++ b/pySim/transport/modem_atcmd.py
@@ -17,15 +17,15 @@
#
import logging as log
-import serial
import time
import re
import argparse
from typing import Optional
+import serial
from pySim.utils import Hexstr, ResTuple
from pySim.transport import LinkBase
-from pySim.exceptions import *
+from pySim.exceptions import ReaderError, ProtocolError
# HACK: if somebody needs to debug this thing
# log.root.setLevel(log.DEBUG)
@@ -57,7 +57,7 @@
def send_at_cmd(self, cmd, timeout=0.2, patience=0.002):
# Convert from string to bytes, if needed
- bcmd = cmd if type(cmd) is bytes else cmd.encode()
+ bcmd = cmd if isinstance(cmd, bytes) else cmd.encode()
bcmd += b'\r'
# Clean input buffer from previous/unexpected data
@@ -67,9 +67,9 @@
log.debug('Sending AT command: %s', cmd)
try:
wlen = self._sl.write(bcmd)
- assert(wlen == len(bcmd))
- except:
- raise ReaderError('Failed to send AT command: %s' % cmd)
+ assert wlen == len(bcmd)
+ except Exception as exc:
+ raise ReaderError('Failed to send AT command: %s' % cmd) from exc
rsp = b''
its = 1
@@ -91,8 +91,7 @@
break
time.sleep(patience)
its += 1
- log.debug('Command took %0.6fs (%d cycles a %fs)',
- time.time() - t_start, its, patience)
+ log.debug('Command took %0.6fs (%d cycles a %fs)', time.time() - t_start, its, patience)
if self._echo:
# Skip echo chars
@@ -120,11 +119,10 @@
if result[-1] == b'OK':
self._echo = False
return
- elif result[-1] == b'AT\r\r\nOK':
+ if result[-1] == b'AT\r\r\nOK':
self._echo = True
return
- raise ReaderError(
- 'Interface \'%s\' does not respond to \'AT\' command' % self._device)
+ raise ReaderError('Interface \'%s\' does not respond to \'AT\' command' % self._device)
def reset_card(self):
# Reset the modem, just to be sure
@@ -135,7 +133,7 @@
if self.send_at_cmd('AT+CSIM=?') != [b'OK']:
raise ReaderError('The modem does not seem to support SIM access')
- log.info('Modem at \'%s\' is ready!' % self._device)
+ log.info('Modem at \'%s\' is ready!', self._device)
def connect(self):
pass # Nothing to do really ...
@@ -165,9 +163,9 @@
# Make sure that the response has format: b'+CSIM: %d,\"%s\"'
try:
result = re.match(b'\+CSIM: (\d+),\"([0-9A-F]+)\"', rsp)
- (rsp_pdu_len, rsp_pdu) = result.groups()
- except:
- raise ReaderError('Failed to parse response from modem: %s' % rsp)
+ (_rsp_pdu_len, rsp_pdu) = result.groups()
+ except Exception as exc:
+ raise ReaderError('Failed to parse response from modem: %s' % rsp) from exc
# TODO: make sure we have at least SW
data = rsp_pdu[:-4].decode().lower()
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/35821?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: I2c8994eabd973b65132af1030429b1021d0c20df
Gerrit-Change-Number: 35821
Gerrit-PatchSet: 1
Gerrit-Owner: laforge <laforge(a)osmocom.org>
Gerrit-MessageType: newchange
laforge has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/35808?usp=email )
Change subject: global_platform: Add DEK (key) encryption support
......................................................................
global_platform: Add DEK (key) encryption support
Change-Id: I940cc2e16a1d3e3cdef4ebcf3f15fc2c8de21284
---
M pySim/global_platform/__init__.py
M pySim/global_platform/scp.py
2 files changed, 71 insertions(+), 2 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/08/35808/1
diff --git a/pySim/global_platform/__init__.py b/pySim/global_platform/__init__.py
index 6d69796..6f72bdc 100644
--- a/pySim/global_platform/__init__.py
+++ b/pySim/global_platform/__init__.py
@@ -544,7 +544,13 @@
else:
kcv_bin = compute_kcv(opts.key_type[i], h2b(opts.key_data[i])) or b''
kcv = b2h(kcv_bin)
- kdb.append({'key_type': opts.key_type[i], 'kcb': opts.key_data[i], 'kcv': kcv})
+ if self._cmd.lchan.scc.scp:
+ # encrypte key data with DEK of current SCP
+ kcb = b2h(self._cmd.lchan.scc.scp.card_keys.encrypt_key(h2b(opts.key_data[i])))
+ else:
+ # (for example) during personalization, DEK might not be required)
+ kcb = opts.key_data[i]
+ kdb.append({'key_type': opts.key_type[i], 'kcb': kcb, 'kcv': kcv})
p2 = opts.key_id
if len(opts.key_type) > 1:
p2 |= 0x80
diff --git a/pySim/global_platform/scp.py b/pySim/global_platform/scp.py
index b8ac122..4e64e41 100644
--- a/pySim/global_platform/scp.py
+++ b/pySim/global_platform/scp.py
@@ -22,7 +22,7 @@
from Cryptodome.Util.strxor import strxor
from construct import Struct, Bytes, Int8ub, Int16ub, Const
from construct import Optional as COptional
-from pySim.utils import b2h
+from pySim.utils import b2h, bertlv_parse_len, bertlv_encode_len
from pySim.secure_channel import SecureChannel
from typing import Optional
@@ -59,6 +59,7 @@
DERIV_CONST_RMAC = b'\x01\x02'
DERIV_CONST_ENC = b'\x01\x82'
DERIV_CONST_DENC = b'\x01\x81'
+ blocksize = 8
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"""
@@ -175,6 +176,43 @@
def gen_ext_auth_apdu(self, security_level: int = 0x01) -> bytes:
pass
+ def encrypt_key(self, key: bytes) -> bytes:
+ """Encrypt a key with the DEK."""
+ num_pad = len(key) % self.sk.blocksize
+ if num_pad:
+ return bertlv_encode_len(len(key)) + self.dek_encrypt(key + b'\x00'*num_pad)
+ else:
+ return self.dek_encrypt(key)
+
+ def decrypt_key(self, encrypted_key:bytes) -> bytes:
+ """Decrypt a key with the DEK."""
+ if len(encrypted_key) % self.sk.blocksize:
+ # If the length of the Key Component Block is not a multiple of the block size of the encryption #
+ # algorithm (i.e. 8 bytes for DES, 16 bytes for AES), then it shall be assumed that the key
+ # component value was right-padded prior to encryption and that the Key Component Block was
+ # formatted as described in Table 11-70. In this case, the first byte(s) of the Key Component
+ # Block provides the actual length of the key component value, which allows recovering the
+ # clear-text key component value after decryption of the encrypted key component value and removal
+ # of padding bytes.
+ decrypted = self.dek_decrypt(encrypted_key)
+ key_len, remainder = bertlv_parse_len(decrypted)
+ return remainder[:key_len]
+ else:
+ # If the length of the Key Component Block is a multiple of the block size of the encryption
+ # algorithm (i.e. 8 bytes for DES, 16 bytes for AES), then it shall be assumed that no padding
+ # bytes were added before encrypting the key component value and that the Key Component Block is
+ # only composed of the encrypted key component value (as shown in Table 11-71). In this case, the
+ # clear-text key component value is simply recovered by decrypting the Key Component Block.
+ return self.dek_decrypt(encrypted_key)
+
+ @abc.abstractmethod
+ def dek_encrypt(self, plaintext:bytes) -> bytes:
+ pass
+
+ @abc.abstractmethod
+ def dek_decrypt(self, ciphertext:bytes) -> bytes:
+ pass
+
class SCP02(SCP):
"""An instance of the GlobalPlatform SCP02 secure channel protocol."""
@@ -183,6 +221,14 @@
'seq_counter'/Int16ub, 'card_challenge'/Bytes(6), 'card_cryptogram'/Bytes(8))
kvn_range = [0x20, 0x2f]
+ def dek_encrypt(self, plaintext:bytes) -> bytes:
+ cipher = DES.new(self.card_keys.dek, DES.MODE_ECB)
+ return cipher.encrypt(plaintext)
+
+ def dek_decrypt(self, ciphertext:bytes) -> bytes:
+ cipher = DES.new(self.card_keys.dek, DES.MODE_ECB)
+ return cipher.decrypt(ciphertext)
+
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)
@@ -369,6 +415,14 @@
self.s_mode = kwargs.pop('s_mode', 8)
super().__init__(*args, **kwargs)
+ def dek_encrypt(self, plaintext:bytes) -> bytes:
+ cipher = AES.new(self.card_keys.dek, AES.MODE_CBC, b'\x00'*16)
+ return cipher.encrypt(plaintext)
+
+ def dek_decrypt(self, ciphertext:bytes) -> bytes:
+ cipher = AES.new(self.card_keys.dek, AES.MODE_CBC, b'\x00'*16)
+ return cipher.decrypt(ciphertext)
+
def _compute_cryptograms(self):
logger.debug("host_challenge(%s), card_challenge(%s)", b2h(self.host_challenge), b2h(self.card_challenge))
# Card + Host Authentication Cryptogram: Section 6.2.2.2 + 6.2.2.3
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/35808?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: I940cc2e16a1d3e3cdef4ebcf3f15fc2c8de21284
Gerrit-Change-Number: 35808
Gerrit-PatchSet: 1
Gerrit-Owner: laforge <laforge(a)osmocom.org>
Gerrit-MessageType: newchange
laforge has submitted this change. ( https://gerrit.osmocom.org/c/pysim/+/35803?usp=email )
Change subject: global_platform: KCV support for PUT KEY
......................................................................
global_platform: KCV support for PUT KEY
GlobalPlatform requires the use of the KCV for DES + AES keys. Let's
implement that.
(11.8.2.3.3: "For all key types described in section B.6, the Key Check
Value shall be present.")
Change-Id: Ief168a66dee58b56f4126db12829b3a98906c8db
---
M pySim/global_platform/__init__.py
M tests/test_globalplatform.py
2 files changed, 55 insertions(+), 1 deletion(-)
Approvals:
Jenkins Builder: Verified
laforge: Looks good to me, approved
diff --git a/pySim/global_platform/__init__.py b/pySim/global_platform/__init__.py
index b6d2343..494cfe1 100644
--- a/pySim/global_platform/__init__.py
+++ b/pySim/global_platform/__init__.py
@@ -539,7 +539,8 @@
if opts.key_check and len(opts.key_check) > i:
kcv = opts.key_check[i]
else:
- kcv = ''
+ kcv_bin = compute_kcv(opts.key_type[i], h2b(opts.key_data[i])) or b''
+ kcv = b2h(kcv_bin)
kdb.append({'key_type': opts.key_type[i], 'kcb': opts.key_data[i], 'kcv': kcv})
p2 = opts.key_id
if len(opts.key_type) > 1:
@@ -770,3 +771,35 @@
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))
+
+from Cryptodome.Cipher import DES, DES3, AES
+
+def compute_kcv_des(key:bytes) -> bytes:
+ # GP Card Spec B.6: For a DES key, the key check value is computed by encrypting 8 bytes, each with
+ # value '00', with the key to be checked and retaining the 3 highest-order bytes of the encrypted
+ # result.
+ plaintext = b'\x00' * 8
+ cipher = DES3.new(key, DES.MODE_ECB)
+ return cipher.encrypt(plaintext)
+
+def compute_kcv_aes(key:bytes) -> bytes:
+ # GP Card Spec B.6: For a AES key, the key check value is computed by encrypting 16 bytes, each with
+ # value '01', with the key to be checked and retaining the 3 highest-order bytes of the encrypted
+ # result.
+ plaintext = b'\x01' * 16
+ cipher = AES.new(key, AES.MODE_ECB)
+ return cipher.encrypt(plaintext)
+
+# dict is keyed by the string name of the KeyType enum above in this file
+KCV_CALCULATOR = {
+ 'aes': compute_kcv_aes,
+ 'des': compute_kcv_des,
+ }
+
+def compute_kcv(key_type: str, key: bytes) -> Optional[bytes]:
+ """Compute the KCV (Key Check Value) for given key type and key."""
+ kcv_calculator = KCV_CALCULATOR.get(key_type)
+ if not kcv_calculator:
+ return None
+ else:
+ return kcv_calculator(key)[:3]
diff --git a/tests/test_globalplatform.py b/tests/test_globalplatform.py
index 8496018..ba4ad76 100644
--- a/tests/test_globalplatform.py
+++ b/tests/test_globalplatform.py
@@ -214,6 +214,12 @@
# FIXME: test auth with random (0x60) vs pseudo-random (0x70) challenge
+class SCP03_KCV_Test(unittest.TestCase):
+ def test_kcv(self):
+ self.assertEqual(compute_kcv('aes', KEYSET_AES128.enc), h2b('C35280'))
+ self.assertEqual(compute_kcv('aes', KEYSET_AES128.mac), h2b('013808'))
+ self.assertEqual(compute_kcv('aes', KEYSET_AES128.dek), h2b('840DE5'))
+
if __name__ == "__main__":
unittest.main()
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/35803?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: Ief168a66dee58b56f4126db12829b3a98906c8db
Gerrit-Change-Number: 35803
Gerrit-PatchSet: 3
Gerrit-Owner: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-MessageType: merged
laforge has posted comments on this change. ( https://gerrit.osmocom.org/c/pysim/+/35803?usp=email )
Change subject: global_platform: KCV support for PUT KEY
......................................................................
Patch Set 3: Code-Review+2
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/35803?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: Ief168a66dee58b56f4126db12829b3a98906c8db
Gerrit-Change-Number: 35803
Gerrit-PatchSet: 3
Gerrit-Owner: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Comment-Date: Sun, 04 Feb 2024 23:58:20 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes
Gerrit-MessageType: comment
laforge has posted comments on this change. ( https://gerrit.osmocom.org/c/pysim/+/35802?usp=email )
Change subject: Be more conservative in our imports
......................................................................
Patch Set 5: Code-Review+2
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/35802?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: Iba749d18e1863ded88ba2d2183e2e8d718b2d612
Gerrit-Change-Number: 35802
Gerrit-PatchSet: 5
Gerrit-Owner: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Comment-Date: Sun, 04 Feb 2024 23:58:13 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes
Gerrit-MessageType: comment