Skip to content

Commit

Permalink
Introduce GlobalPlatform SCP02 implementation
Browse files Browse the repository at this point in the history
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
  • Loading branch information
laf0rge committed Feb 4, 2024
1 parent 762a72b commit 41a7379
Show file tree
Hide file tree
Showing 7 changed files with 437 additions and 4 deletions.
10 changes: 10 additions & 0 deletions docs/shell.rst
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,16 @@ put_key
: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
--------------------
Expand Down
8 changes: 7 additions & 1 deletion pySim-shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,11 @@ def trace_response(self, cmd, sw, resp):
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)> '
Expand Down Expand Up @@ -258,6 +262,8 @@ def do_apdu(self, opts):
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()

Expand Down
11 changes: 9 additions & 2 deletions pySim/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def __init__(self, transport: LinkBase, lchan_nr: int = 0):
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."""
Expand Down Expand Up @@ -110,7 +111,10 @@ def send_apdu(self, pdu: Hexstr) -> ResTuple:
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
Expand All @@ -124,7 +128,10 @@ def send_apdu_checksw(self, pdu: Hexstr, sw: SwMatchstr = "9000") -> ResTuple:
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]:
Expand Down
65 changes: 64 additions & 1 deletion pySim/global_platform/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# coding=utf-8
"""Partial Support for GlobalPLatform Card Spec (currently 2.1.1)
(C) 2022-2023 by Harald Welte <[email protected]>
(C) 2022-2024 by Harald Welte <[email protected]>
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
Expand All @@ -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 *
Expand Down Expand Up @@ -582,6 +584,48 @@ def get_status(self, subset:str, aid_search_qualifier:Hexstr = '') -> List[GpReg
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):
Expand All @@ -601,3 +645,22 @@ def __init__(self, aid='a000000003000000'):
#
# 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))
Loading

0 comments on commit 41a7379

Please sign in to comment.