SCP02: Only C-MAC/C-ENCRYPT APDUs whose CLA byte indicates GlobalPlatform
I'm not entirely sure if this is the right thing to do. For sure I do have cards which don't like SELECT with C-MAC appended... and GlobalPlatform clearly states SELECT is coded with CLA value that has the MSB not set (i.e. not a GlobalPlatform command). Change-Id: Ieda75c865a6ff2725fc3c8772bb274d96b8a5a43
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import abc
|
||||||
import logging
|
import logging
|
||||||
from Cryptodome.Cipher import DES3, DES
|
from Cryptodome.Cipher import DES3, DES
|
||||||
from Cryptodome.Util.strxor import strxor
|
from Cryptodome.Util.strxor import strxor
|
||||||
@@ -94,27 +95,21 @@ INS_INIT_UPDATE = 0x50
|
|||||||
INS_EXT_AUTH = 0x82
|
INS_EXT_AUTH = 0x82
|
||||||
CLA_SM = 0x04
|
CLA_SM = 0x04
|
||||||
|
|
||||||
class SCP(SecureChannel):
|
class SCP(SecureChannel, abc.ABC):
|
||||||
pass
|
"""Abstract base class containing some common interface + functionality for SCP protocols."""
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
def __init__(self, card_keys: 'GpCardKeyset', lchan_nr: int = 0):
|
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.lchan_nr = lchan_nr
|
||||||
self.card_keys = card_keys
|
self.card_keys = card_keys
|
||||||
self.sk = None
|
self.sk = None
|
||||||
self.mac_on_unmodified = False
|
self.mac_on_unmodified = False
|
||||||
self.security_level = None
|
self.security_level = 0x00
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
if self.security_level:
|
return "%s[%02x]" % (self.__class__.__name__, self.security_level)
|
||||||
return "%s[%02x]" % (self.__class__.__name__, self.security_level)
|
|
||||||
else:
|
|
||||||
return "%s[??]" % (self.__class__.__name__)
|
|
||||||
|
|
||||||
def _cla(self, sm: bool = False, b8: bool = True) -> int:
|
def _cla(self, sm: bool = False, b8: bool = True) -> int:
|
||||||
ret = 0x80 if b8 else 0x00
|
ret = 0x80 if b8 else 0x00
|
||||||
@@ -122,6 +117,26 @@ class SCP02(SCP):
|
|||||||
ret = ret | CLA_SM
|
ret = ret | CLA_SM
|
||||||
return ret + self.lchan_nr
|
return ret + self.lchan_nr
|
||||||
|
|
||||||
|
def wrap_cmd_apdu(self, apdu: bytes) -> bytes:
|
||||||
|
# only protect those APDUs that actually are global platform commands
|
||||||
|
if apdu[0] & 0x80:
|
||||||
|
return self._wrap_cmd_apdu(apdu)
|
||||||
|
else:
|
||||||
|
return apdu
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def _wrap_cmd_apdu(self, apdu: bytes, *args, **kwargs) -> bytes:
|
||||||
|
"""Method implementation to be provided by derived class."""
|
||||||
|
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):
|
def _compute_cryptograms(self, card_challenge: bytes, host_challenge: bytes):
|
||||||
logger.debug("host_challenge(%s), card_challenge(%s)", b2h(host_challenge), b2h(card_challenge))
|
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.host_cryptogram = self.sk.calc_mac_3des(self.sk.counter.to_bytes(2, 'big') + card_challenge + host_challenge)
|
||||||
@@ -156,7 +171,7 @@ class SCP02(SCP):
|
|||||||
mac = self.sk.calc_mac_1des(header + self.host_cryptogram, True)
|
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
|
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:
|
def _wrap_cmd_apdu(self, apdu: bytes) -> bytes:
|
||||||
"""Wrap Command APDU for SCP02: calculate MAC and encrypt."""
|
"""Wrap Command APDU for SCP02: calculate MAC and encrypt."""
|
||||||
lc = len(apdu) - 5
|
lc = len(apdu) - 5
|
||||||
assert len(apdu) >= 5, "Wrong APDU length: %d" % len(apdu)
|
assert len(apdu) >= 5, "Wrong APDU length: %d" % len(apdu)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from pySim.utils import b2h, h2b
|
|||||||
KIC = h2b('100102030405060708090a0b0c0d0e0f') # enc
|
KIC = h2b('100102030405060708090a0b0c0d0e0f') # enc
|
||||||
KID = h2b('101102030405060708090a0b0c0d0e0f') # MAC
|
KID = h2b('101102030405060708090a0b0c0d0e0f') # MAC
|
||||||
KIK = h2b('102102030405060708090a0b0c0d0e0f') # DEK
|
KIK = h2b('102102030405060708090a0b0c0d0e0f') # DEK
|
||||||
ck_3des_70 = GpCardKeyset(0x70, KIC, KID, KIK)
|
ck_3des_70 = GpCardKeyset(0x20, KIC, KID, KIK)
|
||||||
|
|
||||||
class SCP02_Auth_Test(unittest.TestCase):
|
class SCP02_Auth_Test(unittest.TestCase):
|
||||||
host_challenge = h2b('40A62C37FA6304F8')
|
host_challenge = h2b('40A62C37FA6304F8')
|
||||||
@@ -36,14 +36,14 @@ class SCP02_Auth_Test(unittest.TestCase):
|
|||||||
|
|
||||||
def test_mutual_auth_success(self):
|
def test_mutual_auth_success(self):
|
||||||
init_upd_cmd = self.scp02.gen_init_update_apdu(host_challenge=self.host_challenge)
|
init_upd_cmd = self.scp02.gen_init_update_apdu(host_challenge=self.host_challenge)
|
||||||
self.assertEqual(b2h(init_upd_cmd).upper(), '805070000840A62C37FA6304F8')
|
self.assertEqual(b2h(init_upd_cmd).upper(), '805020000840A62C37FA6304F8')
|
||||||
self.scp02.parse_init_update_resp(self.init_update_resp)
|
self.scp02.parse_init_update_resp(self.init_update_resp)
|
||||||
ext_auth_cmd = self.scp02.gen_ext_auth_apdu()
|
ext_auth_cmd = self.scp02.gen_ext_auth_apdu()
|
||||||
self.assertEqual(b2h(ext_auth_cmd).upper(), '8482010010BA6961667737C5BCEBECE14C7D6A4376')
|
self.assertEqual(b2h(ext_auth_cmd).upper(), '8482010010BA6961667737C5BCEBECE14C7D6A4376')
|
||||||
|
|
||||||
def test_mutual_auth_fail_card_cryptogram(self):
|
def test_mutual_auth_fail_card_cryptogram(self):
|
||||||
init_upd_cmd = self.scp02.gen_init_update_apdu(host_challenge=self.host_challenge)
|
init_upd_cmd = self.scp02.gen_init_update_apdu(host_challenge=self.host_challenge)
|
||||||
self.assertEqual(b2h(init_upd_cmd).upper(), '805070000840A62C37FA6304F8')
|
self.assertEqual(b2h(init_upd_cmd).upper(), '805020000840A62C37FA6304F8')
|
||||||
wrong_init_update_resp = self.init_update_resp.copy()
|
wrong_init_update_resp = self.init_update_resp.copy()
|
||||||
wrong_init_update_resp[-1:] = b'\xff'
|
wrong_init_update_resp[-1:] = b'\xff'
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
|
|||||||
Reference in New Issue
Block a user