Files
pysim/contrib/rcp/usage_example/rcp_module.py
Philipp Maier d9eef6fce7 WIP: Remote Card Procedure Framework
Problem: When UICC/eUICC cards are deployed into the field it is often
difficult to perform modifications to those cards. One important factor
that makes after-deployment modifications often difficult is that the
key material needed to perform the task must not be handed to the card
holder due to security requirements.

The presented Remote Card Procedure Framework solves this problem. It
provides a so called Remote Card Procedure Client (RCPC), which is a
lightwight software client which can be run by the card holder on the
remote machine.

With the RCPC, the card holder can access a so called Remote Card
Procedure Server (RCPC), to which so called Remote Card Procedure
Modules (RCPM) can subscribe and publish their functionality. With
the RCPC, the card holder can browse the functionality offered by
those connected modules and eventually the card holder may execute
a certain procedure by passing a command to the RCPS.

When a procedure is carried out, the RCPS automatically retrieves the
required key material from a database or CSV file and passes those
keys on to the selected RCPM. The RCPM can then use the key material
to establish a secure channel to carry out the procedure. The procedure
is then protected by a secure channel and the key material is never
disclosed towards the card holder on the remote end.

The framework is desinged in such a way that existing pySim APIs and
functions can be used from the RCPM API user code. Also only minimal
boilerplate code is required. The implementation also ships with a
comprehensive example.

Related: SYS#6959
2026-05-07 17:08:35 +02:00

127 lines
5.3 KiB
Python
Executable File

#!/usr/bin/env python3
# (C) 2026 by sysmocom - s.f.m.c. GmbH
# All Rights Reserved
#
# Author: Philipp Maier
#
# 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 logging
from pathlib import Path
from pySim.log import PySimLogger
from argparse import Namespace
from rcp_module_utils import rcpm_setup_argparse, rcpm_run_module, RcpModule, RcpmCmdSrvConnHdlr
from pySim.global_platform import GpCardKeyset, SCP02, establish_scp, release_scp, install, store_data
from Cryptodome.Random import get_random_bytes
from osmocom.utils import h2b, b2h
log = PySimLogger.get(Path(__file__).stem)
class ExmpleModule(RcpModule):
name = Path(__file__).stem
cmd_descr = [{'name' : 'reset',
'help': 'reset the card',
'args' : []},
{'name' : 'read_binary',
'help': 'read binary data from a transparent file.',
'args' : [{ 'name' : '--fid',
'spec' : {'required' : True,
'help' : 'File identifier to of the file to read',
'action' : 'append',
'pytype' : 'str'},
}
]},
{'name' : 'read_record',
'help': 'read binary data from a transparent file.',
'args' : [{ 'name' : '--fid',
'spec' : {'required' : True,
'help' : 'File identifier to of the file to read',
'action' : 'append',
'pytype' : 'str'},
},
{ 'name' : '--record',
'spec' : {'required' : True,
'help' : 'File record to read',
'default' : 1,
'pytype' : 'int'},
}
]},
{'name' : 'unlock_aram',
'help': 'unlock a locked ARA-M applet on a sysmoISIM-SJA5',
'args' : [],
'get_keys' : {'uicc' : ['KIC', 'KID', 'KIK']}}
]
suitable_for = [{'atr' : '3b9f96801f878031e073fe211b674a357530350265f8'}]
retrieve_uicc_keys = ['KIC', 'KID', 'KIK']
def cmd_reset(self, hdlr: RcpmCmdSrvConnHdlr) -> int:
hdlr.print("resetting UICC/eUICC ...")
hdlr.card._scc.reset_card()
hdlr.print("ATR is: %s" % hdlr.card._scc.get_atr())
return 0
def cmd_read_binary(self, hdlr: RcpmCmdSrvConnHdlr) -> int:
fid = hdlr.cmd_args.fid
hdlr.print("reading transparent file: %s ..." % fid)
(res, _) = hdlr.card._scc.read_binary(fid)
hdlr.print("file content is: %s" % res)
return 0
def cmd_read_record(self, hdlr: RcpmCmdSrvConnHdlr) -> int:
fid = hdlr.cmd_args.fid
record = hdlr.cmd_args.record
hdlr.print("reading linear-fixed file: %s ..." % fid)
(res, _) = hdlr.card._scc.read_record(fid, record)
hdlr.print("file content is: %s" % res)
return 0
def cmd_unlock_aram(self, hdlr: RcpmCmdSrvConnHdlr) -> int:
# Select ADF.ISD
hdlr.print("Selecting ADF.ISD ...")
hdlr.lchan.scc.send_apdu_checksw("00a4040408a00000000300000000")
# Estabish secure channel
hdlr.print("Establishing secure channel ...")
key_ver = 112
key_enc = hdlr.keys_uicc['KIC']
key_mac = hdlr.keys_uicc['KID']
key_dek = hdlr.keys_uicc['KIK']
security_level = 3
host_challenge_len = 8
host_challenge = get_random_bytes(host_challenge_len)
kset = GpCardKeyset(key_ver, h2b(key_enc), h2b(key_mac), h2b(key_dek))
scp = SCP02(card_keys=kset)
establish_scp(hdlr.lchan, scp, host_challenge, security_level)
# To prove that it works, we need to do something that actually requires to be authenticated
# via a secure channel. In this example we will send an unlock command to the ARA-M applet
# found on any sysmoISIM-SJA5 card. (see also: https://gitea.osmocom.org/sim-card/aram-applet)
hdlr.print("Unlocking ARA-M applet ...")
ara_m_aid = "a00000015141434c00"
install(hdlr.lchan, 0x20, 0x00, "0000%02x%s000000" % (len(ara_m_aid) // 2, ara_m_aid))
store_data(hdlr.lchan, h2b("A2"), structure = 'ber_tlv')
# Release the secure channel
hdlr.print("Done, releasing secure channel ...")
release_scp(hdlr.lchan)
return 0
if __name__ == '__main__':
option_parser = rcpm_setup_argparse("Example Module")
opts = option_parser.parse_args()
rcpm_run_module(opts, ExmpleModule)