Compare commits
17 Commits
ewild/ossl
...
osmith/wip
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4ea1c9973 | ||
|
|
947154639c | ||
|
|
4ee99c18cd | ||
|
|
5d2e2ee259 | ||
|
|
92841f2cd5 | ||
|
|
caa955b3ac | ||
|
|
4dddcf932a | ||
|
|
10fe0e3aae | ||
|
|
076fec267a | ||
|
|
b4a12ecc14 | ||
|
|
6cffb31b42 | ||
|
|
6aed97d6c8 | ||
|
|
cb7d5aa3a7 | ||
|
|
70fedb5a46 | ||
|
|
7798ea9c5c | ||
|
|
0b1d3c85fd | ||
|
|
3c1a59640c |
@@ -24,7 +24,7 @@ ICCID_HELP='The ICCID of the eSIM that shall be made available'
|
|||||||
MATCHID_HELP='MatchingID that shall be used by profile download'
|
MATCHID_HELP='MatchingID that shall be used by profile download'
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="""
|
parser = argparse.ArgumentParser(description="""
|
||||||
Utility to manuall issue requests against the ES2+ API of an SM-DP+ according to GSMA SGP.22.""")
|
Utility to manually issue requests against the ES2+ API of an SM-DP+ according to GSMA SGP.22.""")
|
||||||
parser.add_argument('--url', required=True, help='Base URL of ES2+ API endpoint')
|
parser.add_argument('--url', required=True, help='Base URL of ES2+ API endpoint')
|
||||||
parser.add_argument('--id', required=True, help='Entity identifier passed to SM-DP+')
|
parser.add_argument('--id', required=True, help='Entity identifier passed to SM-DP+')
|
||||||
parser.add_argument('--client-cert', help='X.509 client certificate used to authenticate to server')
|
parser.add_argument('--client-cert', help='X.509 client certificate used to authenticate to server')
|
||||||
@@ -63,7 +63,7 @@ if __name__ == '__main__':
|
|||||||
data = {}
|
data = {}
|
||||||
for k, v in vars(opts).items():
|
for k, v in vars(opts).items():
|
||||||
if k in ['url', 'id', 'client_cert', 'server_ca_cert', 'command']:
|
if k in ['url', 'id', 'client_cert', 'server_ca_cert', 'command']:
|
||||||
# remove keys from dict that shold not end up in JSON...
|
# remove keys from dict that should not end up in JSON...
|
||||||
continue
|
continue
|
||||||
if v is not None:
|
if v is not None:
|
||||||
data[k] = v
|
data[k] = v
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ parser_dl.add_argument('--confirmation-code',
|
|||||||
# notification
|
# notification
|
||||||
parser_ntf = subparsers.add_parser('notification', help='ES9+ (other) notification')
|
parser_ntf = subparsers.add_parser('notification', help='ES9+ (other) notification')
|
||||||
parser_ntf.add_argument('operation', choices=['enable','disable','delete'],
|
parser_ntf.add_argument('operation', choices=['enable','disable','delete'],
|
||||||
help='Profile Management Opreation whoise occurrence shall be notififed')
|
help='Profile Management Operation whoise occurrence shall be notififed')
|
||||||
parser_ntf.add_argument('--sequence-nr', type=int, required=True,
|
parser_ntf.add_argument('--sequence-nr', type=int, required=True,
|
||||||
help='eUICC global notification sequence number')
|
help='eUICC global notification sequence number')
|
||||||
parser_ntf.add_argument('--notification-address', help='notificationAddress, if different from URL')
|
parser_ntf.add_argument('--notification-address', help='notificationAddress, if different from URL')
|
||||||
@@ -123,8 +123,8 @@ class Es9pClient:
|
|||||||
'profileManagementOperation': PMO(self.opts.operation).to_bitstring(),
|
'profileManagementOperation': PMO(self.opts.operation).to_bitstring(),
|
||||||
'notificationAddress': self.opts.notification_address or urlparse(self.opts.url).netloc,
|
'notificationAddress': self.opts.notification_address or urlparse(self.opts.url).netloc,
|
||||||
}
|
}
|
||||||
if opts.iccid:
|
if self.opts.iccid:
|
||||||
ntf_metadata['iccid'] = h2b(swap_nibbles(opts.iccid))
|
ntf_metadata['iccid'] = h2b(swap_nibbles(self.opts.iccid))
|
||||||
|
|
||||||
if self.opts.operation == 'download':
|
if self.opts.operation == 'download':
|
||||||
pird = {
|
pird = {
|
||||||
|
|||||||
@@ -82,10 +82,6 @@ case "$JOB_TYPE" in
|
|||||||
|
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
||||||
# XXX: workaround for https://github.com/python-cmd2/cmd2/issues/1414
|
|
||||||
# 2.4.3 was the last stable release not affected by this bug (OS#6776)
|
|
||||||
pip install cmd2==2.4.3
|
|
||||||
|
|
||||||
rm -rf docs/_build
|
rm -rf docs/_build
|
||||||
make -C "docs" html latexpdf
|
make -C "docs" html latexpdf
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ parser_rpe.add_argument('--output-file', required=True, help='Output file name')
|
|||||||
parser_rpe.add_argument('--identification', default=[], type=int, action='append', help='Remove PEs matching specified identification')
|
parser_rpe.add_argument('--identification', default=[], type=int, action='append', help='Remove PEs matching specified identification')
|
||||||
parser_rpe.add_argument('--type', default=[], action='append', help='Remove PEs matching specified type')
|
parser_rpe.add_argument('--type', default=[], action='append', help='Remove PEs matching specified type')
|
||||||
|
|
||||||
parser_rn = subparsers.add_parser('remove-naa', help='Remove speciifed NAAs from PE-Sequence')
|
parser_rn = subparsers.add_parser('remove-naa', help='Remove specified NAAs from PE-Sequence')
|
||||||
parser_rn.add_argument('--output-file', required=True, help='Output file name')
|
parser_rn.add_argument('--output-file', required=True, help='Output file name')
|
||||||
parser_rn.add_argument('--naa-type', required=True, choices=NAAs.keys(), help='Network Access Application type to remove')
|
parser_rn.add_argument('--naa-type', required=True, choices=NAAs.keys(), help='Network Access Application type to remove')
|
||||||
# TODO: add an --naa-index or the like, so only one given instance can be removed
|
# TODO: add an --naa-index or the like, so only one given instance can be removed
|
||||||
|
|||||||
@@ -27,5 +27,5 @@ PYTHONPATH=$PYSIMPATH python3 $PYSIMPATH/contrib/saip-tool.py $OUTPATH add-app-i
|
|||||||
# Display the contents of the resulting application PE:
|
# Display the contents of the resulting application PE:
|
||||||
PYTHONPATH=$PYSIMPATH python3 $PYSIMPATH/contrib/saip-tool.py $OUTPATH info --apps
|
PYTHONPATH=$PYSIMPATH python3 $PYSIMPATH/contrib/saip-tool.py $OUTPATH info --apps
|
||||||
|
|
||||||
# For an explaination of --uicc-toolkit-app-spec-pars, see:
|
# For an explanation of --uicc-toolkit-app-spec-pars, see:
|
||||||
# ETSI TS 102 226, section 8.2.1.3.2.2.1
|
# ETSI TS 102 226, section 8.2.1.3.2.2.1
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# A more useful verion of the 'unber' tool provided with asn1c:
|
# A more useful version of the 'unber' tool provided with asn1c:
|
||||||
# Give a hierarchical decode of BER/DER-encoded ASN.1 TLVs
|
# Give a hierarchical decode of BER/DER-encoded ASN.1 TLVs
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -21,6 +21,14 @@ project = 'osmopysim-usermanual'
|
|||||||
copyright = '2009-2023 by Sylvain Munaut, Harald Welte, Philipp Maier, Supreeth Herle, Merlin Chlosta'
|
copyright = '2009-2023 by Sylvain Munaut, Harald Welte, Philipp Maier, Supreeth Herle, Merlin Chlosta'
|
||||||
author = 'Sylvain Munaut, Harald Welte, Philipp Maier, Supreeth Herle, Merlin Chlosta'
|
author = 'Sylvain Munaut, Harald Welte, Philipp Maier, Supreeth Herle, Merlin Chlosta'
|
||||||
|
|
||||||
|
# PDF: Avoid that the authors list exceeds the page by inserting '\and'
|
||||||
|
# manually as line break (https://github.com/sphinx-doc/sphinx/issues/6875)
|
||||||
|
latex_elements = {
|
||||||
|
"maketitle":
|
||||||
|
r"""\author{Sylvain Munaut, Harald Welte, Philipp Maier, \and Supreeth Herle, Merlin Chlosta}
|
||||||
|
\sphinxmaketitle
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ Two modes are possible:
|
|||||||
Ki and OPc will be generated during each programming cycle. This means fresh keys are generated, even when the
|
Ki and OPc will be generated during each programming cycle. This means fresh keys are generated, even when the
|
||||||
``--num`` remains unchanged.
|
``--num`` remains unchanged.
|
||||||
|
|
||||||
The parameter ``--num`` specifies a card individual number. This number will be manged into the random seed so that
|
The parameter ``--num`` specifies a card individual number. This number will be managed into the random seed so that
|
||||||
it serves as an identifier for a particular set of randomly generated parameters.
|
it serves as an identifier for a particular set of randomly generated parameters.
|
||||||
|
|
||||||
In the example above the parameters ``--mcc``, and ``--mnc`` are specified as well, since they identify the GSM
|
In the example above the parameters ``--mcc``, and ``--mnc`` are specified as well, since they identify the GSM
|
||||||
@@ -77,7 +77,7 @@ the parameter ``--type``. The following card types are supported:
|
|||||||
|
|
||||||
Specifying the card reader:
|
Specifying the card reader:
|
||||||
|
|
||||||
It is most common to use ``pySim-prog`` together whith a PCSC reader. The PCSC reader number is specified via the
|
It is most common to use ``pySim-prog`` together with a PCSC reader. The PCSC reader number is specified via the
|
||||||
``--pcsc-device`` or ``-p`` option. However, other reader types (such as serial readers and modems) are supported. Use
|
``--pcsc-device`` or ``-p`` option. However, other reader types (such as serial readers and modems) are supported. Use
|
||||||
the ``--help`` option of ``pySim-prog`` for more information.
|
the ``--help`` option of ``pySim-prog`` for more information.
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ osmo-smdpp currently
|
|||||||
|
|
||||||
* [by default] uses test certificates copied from GSMA SGP.26 into `./smdpp-data/certs`, assuming that your
|
* [by default] uses test certificates copied from GSMA SGP.26 into `./smdpp-data/certs`, assuming that your
|
||||||
osmo-smdpp would be running at the host name `testsmdpplus1.example.com`. You can of course replace those
|
osmo-smdpp would be running at the host name `testsmdpplus1.example.com`. You can of course replace those
|
||||||
certificates with your own, whether SGP.26 derived or part of a *private root CA* setup with mathcing eUICCs.
|
certificates with your own, whether SGP.26 derived or part of a *private root CA* setup with matching eUICCs.
|
||||||
* doesn't understand profile state. Any profile can always be downloaded any number of times, irrespective
|
* doesn't understand profile state. Any profile can always be downloaded any number of times, irrespective
|
||||||
of the EID or whether it was donwloaded before. This is actually very useful for R&D and testing, as it
|
of the EID or whether it was downloaded before. This is actually very useful for R&D and testing, as it
|
||||||
doesn't require you to generate new profiles all the time. This logic of course is unsuitable for
|
doesn't require you to generate new profiles all the time. This logic of course is unsuitable for
|
||||||
production usage.
|
production usage.
|
||||||
* doesn't perform any personalization, so the IMSI/ICCID etc. are always identical (the ones that are stored in
|
* doesn't perform any personalization, so the IMSI/ICCID etc. are always identical (the ones that are stored in
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ The response body is a JSON document, either
|
|||||||
#. key freshness failure
|
#. key freshness failure
|
||||||
#. unspecified card error
|
#. unspecified card error
|
||||||
|
|
||||||
Example (succcess):
|
Example (success):
|
||||||
::
|
::
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ In any case, in order to operate a SUCI-enabled 5G SA network, you will have to
|
|||||||
#. deploy the public key on your USIMs
|
#. deploy the public key on your USIMs
|
||||||
#. deploy the private key on your 5GC, specifically the UDM function
|
#. deploy the private key on your 5GC, specifically the UDM function
|
||||||
|
|
||||||
pysim contains (int its `contrib` directory) a small utility program that can make it easy to generate
|
pysim contains (in its `contrib` directory) a small utility program that can make it easy to generate
|
||||||
such keys: `suci-keytool.py`
|
such keys: `suci-keytool.py`
|
||||||
|
|
||||||
Generating keys
|
Generating keys
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ This guide covers the basic workflow of provisioning SIM cards with the 5G SUCI
|
|||||||
|
|
||||||
For specific information on sysmocom SIM cards, refer to
|
For specific information on sysmocom SIM cards, refer to
|
||||||
|
|
||||||
* the `sysmoISIM-SJA5 User Manual <https://sysmocom.de/manuals/sysmoisim-sja5-manual.pdf>`__ for the curent
|
* the `sysmoISIM-SJA5 User Manual <https://sysmocom.de/manuals/sysmoisim-sja5-manual.pdf>`__ for the current
|
||||||
sysmoISIM-SJA5 product
|
sysmoISIM-SJA5 product
|
||||||
* the `sysmoISIM-SJA2 User Manual <https://sysmocom.de/manuals/sysmousim-manual.pdf>`__ for the older
|
* the `sysmoISIM-SJA2 User Manual <https://sysmocom.de/manuals/sysmousim-manual.pdf>`__ for the older
|
||||||
sysmoISIM-SJA2 product
|
sysmoISIM-SJA2 product
|
||||||
|
|||||||
885
osmo-smdpp.py
885
osmo-smdpp.py
File diff suppressed because it is too large
Load Diff
@@ -586,7 +586,7 @@ def read_params_csv(opts, imsi=None, iccid=None):
|
|||||||
else:
|
else:
|
||||||
row['mnc'] = row.get('mnc', mnc_from_imsi(row.get('imsi'), False))
|
row['mnc'] = row.get('mnc', mnc_from_imsi(row.get('imsi'), False))
|
||||||
|
|
||||||
# NOTE: We might concider to specify a new CSV field "mnclen" in our
|
# NOTE: We might consider to specify a new CSV field "mnclen" in our
|
||||||
# CSV files for a better automatization. However, this only makes sense
|
# CSV files for a better automatization. However, this only makes sense
|
||||||
# when the tools and databases we export our files from will also add
|
# when the tools and databases we export our files from will also add
|
||||||
# such a field.
|
# such a field.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
#
|
#
|
||||||
# Utility to display some informations about a SIM card
|
# Utility to display some information about a SIM card
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# Copyright (C) 2009 Sylvain Munaut <tnt@246tNt.com>
|
# Copyright (C) 2009 Sylvain Munaut <tnt@246tNt.com>
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ Online manual available at https://downloads.osmocom.org/docs/pysim/master/html/
|
|||||||
def do_apdu(self, opts):
|
def do_apdu(self, opts):
|
||||||
"""Send a raw APDU to the card, and print SW + Response.
|
"""Send a raw APDU to the card, and print SW + Response.
|
||||||
CAUTION: this command bypasses the logical channel handling of pySim-shell and card state changes are not
|
CAUTION: this command bypasses the logical channel handling of pySim-shell and card state changes are not
|
||||||
tracked. Dpending on the raw APDU sent, pySim-shell may not continue to work as expected if you e.g. select
|
tracked. Depending on the raw APDU sent, pySim-shell may not continue to work as expected if you e.g. select
|
||||||
a different file."""
|
a different file."""
|
||||||
|
|
||||||
# When sending raw APDUs we access the scc object through _scc member of the card object. It should also be
|
# When sending raw APDUs we access the scc object through _scc member of the card object. It should also be
|
||||||
@@ -336,7 +336,7 @@ Online manual available at https://downloads.osmocom.org/docs/pysim/master/html/
|
|||||||
|
|
||||||
def _process_card(self, first, script_path):
|
def _process_card(self, first, script_path):
|
||||||
|
|
||||||
# Early phase of card initialzation (this part may fail with an exception)
|
# Early phase of card initialization (this part may fail with an exception)
|
||||||
try:
|
try:
|
||||||
rs, card = init_card(self.sl)
|
rs, card = init_card(self.sl)
|
||||||
rc = self.equip(card, rs)
|
rc = self.equip(card, rs)
|
||||||
@@ -377,7 +377,7 @@ Online manual available at https://downloads.osmocom.org/docs/pysim/master/html/
|
|||||||
|
|
||||||
bulk_script_parser = argparse.ArgumentParser()
|
bulk_script_parser = argparse.ArgumentParser()
|
||||||
bulk_script_parser.add_argument('SCRIPT_PATH', help="path to the script file")
|
bulk_script_parser.add_argument('SCRIPT_PATH', help="path to the script file")
|
||||||
bulk_script_parser.add_argument('--halt_on_error', help='stop card handling if an exeption occurs',
|
bulk_script_parser.add_argument('--halt_on_error', help='stop card handling if an exception occurs',
|
||||||
action='store_true')
|
action='store_true')
|
||||||
bulk_script_parser.add_argument('--tries', type=int, default=2,
|
bulk_script_parser.add_argument('--tries', type=int, default=2,
|
||||||
help='how many tries before trying the next card')
|
help='how many tries before trying the next card')
|
||||||
@@ -731,7 +731,7 @@ class PySimCommands(CommandSet):
|
|||||||
body = {}
|
body = {}
|
||||||
for t in tags:
|
for t in tags:
|
||||||
result = self._cmd.lchan.retrieve_data(t)
|
result = self._cmd.lchan.retrieve_data(t)
|
||||||
(tag, l, val, remainer) = bertlv_parse_one(h2b(result[0]))
|
(tag, l, val, remainder) = bertlv_parse_one(h2b(result[0]))
|
||||||
body[t] = b2h(val)
|
body[t] = b2h(val)
|
||||||
else:
|
else:
|
||||||
raise RuntimeError('Unsupported structure "%s" of file "%s"' % (structure, filename))
|
raise RuntimeError('Unsupported structure "%s" of file "%s"' % (structure, filename))
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ def tcp_connected_callback(p: protocol.Protocol):
|
|||||||
logger.error("%s: connected!" % p)
|
logger.error("%s: connected!" % p)
|
||||||
|
|
||||||
class ProactChannel:
|
class ProactChannel:
|
||||||
"""Representation of a single proective channel."""
|
"""Representation of a single protective channel."""
|
||||||
def __init__(self, channels: 'ProactChannels', chan_nr: int):
|
def __init__(self, channels: 'ProactChannels', chan_nr: int):
|
||||||
self.channels = channels
|
self.channels = channels
|
||||||
self.chan_nr = chan_nr
|
self.chan_nr = chan_nr
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ global_group.add_argument('--no-suppress-select', action='store_false', dest='su
|
|||||||
global_group.add_argument('--no-suppress-status', action='store_false', dest='suppress_status',
|
global_group.add_argument('--no-suppress-status', action='store_false', dest='suppress_status',
|
||||||
help="""
|
help="""
|
||||||
Don't suppress displaying STATUS APDUs. We normally suppress them as they don't provide any
|
Don't suppress displaying STATUS APDUs. We normally suppress them as they don't provide any
|
||||||
information that was not already received in resposne to the most recent SEELCT.""")
|
information that was not already received in response to the most recent SEELCT.""")
|
||||||
global_group.add_argument('--show-raw-apdu', action='store_true', dest='show_raw_apdu',
|
global_group.add_argument('--show-raw-apdu', action='store_true', dest='show_raw_apdu',
|
||||||
help="""Show the raw APDU in addition to its parsed form.""")
|
help="""Show the raw APDU in addition to its parsed form.""")
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ parser_rspro_pyshark_live.add_argument('-i', '--interface', required=True,
|
|||||||
parser_tcaloader_log = subparsers.add_parser('tca-loader-log', help="""
|
parser_tcaloader_log = subparsers.add_parser('tca-loader-log', help="""
|
||||||
Read APDUs from a TCA Loader log file.""")
|
Read APDUs from a TCA Loader log file.""")
|
||||||
parser_tcaloader_log.add_argument('-f', '--log-file', required=True,
|
parser_tcaloader_log.add_argument('-f', '--log-file', required=True,
|
||||||
help='Name of te log file to be read')
|
help='Name of the log file to be read')
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ class ADF_ARAM(CardADF):
|
|||||||
store_ref_ar_do_parse = argparse.ArgumentParser()
|
store_ref_ar_do_parse = argparse.ArgumentParser()
|
||||||
# REF-DO
|
# REF-DO
|
||||||
store_ref_ar_do_parse.add_argument(
|
store_ref_ar_do_parse.add_argument(
|
||||||
'--device-app-id', required=True, help='Identifies the specific device application that the rule appplies to. Hash of Certificate of Application Provider, or UUID. (20/32 hex bytes)')
|
'--device-app-id', required=True, help='Identifies the specific device application that the rule applies to. Hash of Certificate of Application Provider, or UUID. (20/32 hex bytes)')
|
||||||
aid_grp = store_ref_ar_do_parse.add_mutually_exclusive_group()
|
aid_grp = store_ref_ar_do_parse.add_mutually_exclusive_group()
|
||||||
aid_grp.add_argument(
|
aid_grp.add_argument(
|
||||||
'--aid', help='Identifies the specific SE application for which rules are to be stored. Can be a partial AID, containing for example only the RID. (5-16 or 0 hex bytes)')
|
'--aid', help='Identifies the specific SE application for which rules are to be stored. Can be a partial AID, containing for example only the RID. (5-16 or 0 hex bytes)')
|
||||||
@@ -399,7 +399,7 @@ class ADF_ARAM(CardADF):
|
|||||||
sw_aram = {
|
sw_aram = {
|
||||||
'ARA-M': {
|
'ARA-M': {
|
||||||
'6381': 'Rule successfully stored but an access rule already exists',
|
'6381': 'Rule successfully stored but an access rule already exists',
|
||||||
'6382': 'Rule successfully stored bu contained at least one unknown (discarded) BER-TLV',
|
'6382': 'Rule successfully stored but contained at least one unknown (discarded) BER-TLV',
|
||||||
'6581': 'Memory Problem',
|
'6581': 'Memory Problem',
|
||||||
'6700': 'Wrong Length in Lc',
|
'6700': 'Wrong Length in Lc',
|
||||||
'6981': 'DO is not supported by the ARA-M/ARA-C',
|
'6981': 'DO is not supported by the ARA-M/ARA-C',
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ def card_key_provider_register(provider: CardKeyProvider, provider_list=card_key
|
|||||||
provider_list : override the list of providers from the global default
|
provider_list : override the list of providers from the global default
|
||||||
"""
|
"""
|
||||||
if not isinstance(provider, CardKeyProvider):
|
if not isinstance(provider, CardKeyProvider):
|
||||||
raise ValueError("provider is not a card data provier")
|
raise ValueError("provider is not a card data provider")
|
||||||
provider_list.append(provider)
|
provider_list.append(provider)
|
||||||
|
|
||||||
|
|
||||||
@@ -181,7 +181,7 @@ def card_key_provider_get(fields, key: str, value: str, provider_list=card_key_p
|
|||||||
for p in provider_list:
|
for p in provider_list:
|
||||||
if not isinstance(p, CardKeyProvider):
|
if not isinstance(p, CardKeyProvider):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"provider list contains element which is not a card data provier")
|
"provider list contains element which is not a card data provider")
|
||||||
result = p.get(fields, key, value)
|
result = p.get(fields, key, value)
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
@@ -202,7 +202,7 @@ def card_key_provider_get_field(field: str, key: str, value: str, provider_list=
|
|||||||
for p in provider_list:
|
for p in provider_list:
|
||||||
if not isinstance(p, CardKeyProvider):
|
if not isinstance(p, CardKeyProvider):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"provider list contains element which is not a card data provier")
|
"provider list contains element which is not a card data provider")
|
||||||
result = p.get_field(field, key, value)
|
result = p.get_field(field, key, value)
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -112,6 +112,8 @@ class UiccCardBase(SimCardBase):
|
|||||||
def probe(self) -> bool:
|
def probe(self) -> bool:
|
||||||
# EF.DIR is a mandatory EF on all ICCIDs; however it *may* also exist on a TS 51.011 SIM
|
# EF.DIR is a mandatory EF on all ICCIDs; however it *may* also exist on a TS 51.011 SIM
|
||||||
ef_dir = EF_DIR()
|
ef_dir = EF_DIR()
|
||||||
|
# select MF first
|
||||||
|
self.file_exists("3f00")
|
||||||
return self.file_exists(ef_dir.fid)
|
return self.file_exists(ef_dir.fid)
|
||||||
|
|
||||||
def read_aids(self) -> List[Hexstr]:
|
def read_aids(self) -> List[Hexstr]:
|
||||||
|
|||||||
@@ -316,19 +316,19 @@ class FileList(COMPR_TLV_IE, tag=0x92):
|
|||||||
_construct = Struct('number_of_files'/Int8ub,
|
_construct = Struct('number_of_files'/Int8ub,
|
||||||
'files'/GreedyRange(FileId))
|
'files'/GreedyRange(FileId))
|
||||||
|
|
||||||
# TS 102 223 Secton 8.19
|
# TS 102 223 Section 8.19
|
||||||
class LocationInformation(COMPR_TLV_IE, tag=0x93):
|
class LocationInformation(COMPR_TLV_IE, tag=0x93):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# TS 102 223 Secton 8.20
|
# TS 102 223 Section 8.20
|
||||||
class IMEI(COMPR_TLV_IE, tag=0x94):
|
class IMEI(COMPR_TLV_IE, tag=0x94):
|
||||||
_construct = BcdAdapter(GreedyBytes)
|
_construct = BcdAdapter(GreedyBytes)
|
||||||
|
|
||||||
# TS 102 223 Secton 8.21
|
# TS 102 223 Section 8.21
|
||||||
class HelpRequest(COMPR_TLV_IE, tag=0x95):
|
class HelpRequest(COMPR_TLV_IE, tag=0x95):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# TS 102 223 Secton 8.22
|
# TS 102 223 Section 8.22
|
||||||
class NetworkMeasurementResults(COMPR_TLV_IE, tag=0x96):
|
class NetworkMeasurementResults(COMPR_TLV_IE, tag=0x96):
|
||||||
_construct = BcdAdapter(GreedyBytes)
|
_construct = BcdAdapter(GreedyBytes)
|
||||||
|
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ class SimCardCommands:
|
|||||||
return self.send_apdu_checksw(self.cla_byte + "a40304")
|
return self.send_apdu_checksw(self.cla_byte + "a40304")
|
||||||
|
|
||||||
def select_adf(self, aid: Hexstr) -> ResTuple:
|
def select_adf(self, aid: Hexstr) -> ResTuple:
|
||||||
"""Execute SELECT a given Applicaiton ADF.
|
"""Execute SELECT a given Application ADF.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
aid : application identifier as hex string
|
aid : application identifier as hex string
|
||||||
@@ -577,7 +577,7 @@ class SimCardCommands:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
rand : 16 byte random data as hex string (RAND)
|
rand : 16 byte random data as hex string (RAND)
|
||||||
autn : 8 byte Autentication Token (AUTN)
|
autn : 8 byte Authentication Token (AUTN)
|
||||||
context : 16 byte random data ('3g' or 'gsm')
|
context : 16 byte random data ('3g' or 'gsm')
|
||||||
"""
|
"""
|
||||||
# 3GPP TS 31.102 Section 7.1.2.1
|
# 3GPP TS 31.102 Section 7.1.2.1
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ class BspInstance:
|
|||||||
def mac_only_one(self, tag: int, plaintext: bytes) -> bytes:
|
def mac_only_one(self, tag: int, plaintext: bytes) -> bytes:
|
||||||
"""MAC a single plaintext TLV. Returns the protected ciphertext."""
|
"""MAC a single plaintext TLV. Returns the protected ciphertext."""
|
||||||
assert tag <= 255
|
assert tag <= 255
|
||||||
assert len(plaintext) < self.max_payload_size
|
assert len(plaintext) <= self.max_payload_size
|
||||||
maced = self.m_algo.auth(tag, plaintext)
|
maced = self.m_algo.auth(tag, plaintext)
|
||||||
# The data block counter for ICV calculation is incremented also for each segment with C-MAC only.
|
# The data block counter for ICV calculation is incremented also for each segment with C-MAC only.
|
||||||
self.c_algo.block_nr += 1
|
self.c_algo.block_nr += 1
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class param:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
class Es2PlusApiFunction(JsonHttpApiFunction):
|
class Es2PlusApiFunction(JsonHttpApiFunction):
|
||||||
"""Base classs for representing an ES2+ API Function."""
|
"""Base class for representing an ES2+ API Function."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# ES2+ DownloadOrder function (SGP.22 section 5.3.1)
|
# ES2+ DownloadOrder function (SGP.22 section 5.3.1)
|
||||||
|
|||||||
@@ -199,12 +199,12 @@ class BoundProfilePackage(ProfilePackage):
|
|||||||
# 'initialiseSecureChannelRequest'
|
# 'initialiseSecureChannelRequest'
|
||||||
bpp_seq = rsp.asn1.encode('InitialiseSecureChannelRequest', iscr)
|
bpp_seq = rsp.asn1.encode('InitialiseSecureChannelRequest', iscr)
|
||||||
# firstSequenceOf87
|
# firstSequenceOf87
|
||||||
logger.debug(f"BPP_ENCODE_DEBUG: Encrypting ConfigureISDP with BSP keys")
|
logger.debug("BPP_ENCODE_DEBUG: Encrypting ConfigureISDP with BSP keys")
|
||||||
logger.debug(f"BPP_ENCODE_DEBUG: BSP S-ENC: {bsp.c_algo.s_enc.hex()}")
|
logger.debug(f"BPP_ENCODE_DEBUG: BSP S-ENC: {bsp.c_algo.s_enc.hex()}")
|
||||||
logger.debug(f"BPP_ENCODE_DEBUG: BSP S-MAC: {bsp.m_algo.s_mac.hex()}")
|
logger.debug(f"BPP_ENCODE_DEBUG: BSP S-MAC: {bsp.m_algo.s_mac.hex()}")
|
||||||
bpp_seq += encode_seq(0xa0, bsp.encrypt_and_mac(0x87, conf_idsp_bin))
|
bpp_seq += encode_seq(0xa0, bsp.encrypt_and_mac(0x87, conf_idsp_bin))
|
||||||
# sequenceOF88
|
# sequenceOF88
|
||||||
logger.debug(f"BPP_ENCODE_DEBUG: MAC-only StoreMetadata with BSP keys")
|
logger.debug("BPP_ENCODE_DEBUG: MAC-only StoreMetadata with BSP keys")
|
||||||
bpp_seq += encode_seq(0xa1, bsp.mac_only(0x88, smr_bin))
|
bpp_seq += encode_seq(0xa1, bsp.mac_only(0x88, smr_bin))
|
||||||
|
|
||||||
if self.ppp: # we have to use session keys
|
if self.ppp: # we have to use session keys
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""GSMA eSIM RSP ES9+ interface according ot SGP.22 v2.5"""
|
"""GSMA eSIM RSP ES9+ interface according to SGP.22 v2.5"""
|
||||||
|
|
||||||
# (C) 2024 by Harald Welte <laforge@osmocom.org>
|
# (C) 2024 by Harald Welte <laforge@osmocom.org>
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ class ApiError(Exception):
|
|||||||
return f'{self.status}("{self.subject_code}","{self.reason_code}","{self.subject_id}","{self.message}")'
|
return f'{self.status}("{self.subject_code}","{self.reason_code}","{self.subject_id}","{self.message}")'
|
||||||
|
|
||||||
class JsonHttpApiFunction(abc.ABC):
|
class JsonHttpApiFunction(abc.ABC):
|
||||||
"""Base classs for representing an HTTP[s] API Function."""
|
"""Base class for representing an HTTP[s] API Function."""
|
||||||
# the below class variables are expected to be overridden in derived classes
|
# the below class variables are expected to be overridden in derived classes
|
||||||
|
|
||||||
path = None
|
path = None
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class RspSessionState:
|
|||||||
# FIXME: how to add the public key from smdp_otpk to an instance of EllipticCurvePrivateKey?
|
# FIXME: how to add the public key from smdp_otpk to an instance of EllipticCurvePrivateKey?
|
||||||
del state['_smdp_otsk']
|
del state['_smdp_otsk']
|
||||||
del state['_smdp_ot_curve']
|
del state['_smdp_ot_curve']
|
||||||
# automatically recover all the remainig state
|
# automatically recover all the remaining state
|
||||||
self.__dict__.update(state)
|
self.__dict__.update(state)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ class File:
|
|||||||
self.fill_pattern = pefi['fillPattern']
|
self.fill_pattern = pefi['fillPattern']
|
||||||
self.fill_pattern_repeat = False
|
self.fill_pattern_repeat = False
|
||||||
elif fdb_dec['file_type'] == 'df':
|
elif fdb_dec['file_type'] == 'df':
|
||||||
# only set it, if an earlier call to from_template() didn't alrady set it, as
|
# only set it, if an earlier call to from_template() didn't already set it, as
|
||||||
# the template can differentiate between MF, DF and ADF (unlike FDB)
|
# the template can differentiate between MF, DF and ADF (unlike FDB)
|
||||||
if not self.file_type:
|
if not self.file_type:
|
||||||
self.file_type = 'DF'
|
self.file_type = 'DF'
|
||||||
@@ -427,7 +427,7 @@ class File:
|
|||||||
|
|
||||||
class ProfileElement:
|
class ProfileElement:
|
||||||
"""Generic Class representing a Profile Element (PE) within a SAIP Profile. This may be used directly,
|
"""Generic Class representing a Profile Element (PE) within a SAIP Profile. This may be used directly,
|
||||||
but ist more likely sub-classed with a specific class for the specific profile element type, like e.g
|
but it's more likely sub-classed with a specific class for the specific profile element type, like e.g
|
||||||
ProfileElementHeader, ProfileElementMF, ...
|
ProfileElementHeader, ProfileElementMF, ...
|
||||||
"""
|
"""
|
||||||
FILE_BEARING = ['mf', 'cd', 'telecom', 'usim', 'opt-usim', 'isim', 'opt-isim', 'phonebook', 'gsm-access',
|
FILE_BEARING = ['mf', 'cd', 'telecom', 'usim', 'opt-usim', 'isim', 'opt-isim', 'phonebook', 'gsm-access',
|
||||||
@@ -440,7 +440,7 @@ class ProfileElement:
|
|||||||
'genericFileManagement': 'gfm-header',
|
'genericFileManagement': 'gfm-header',
|
||||||
'akaParameter': 'aka-header',
|
'akaParameter': 'aka-header',
|
||||||
'cdmaParameter': 'cdma-header',
|
'cdmaParameter': 'cdma-header',
|
||||||
# note how they couldn't even consistently captialize the 'header' suffix :(
|
# note how they couldn't even consistently capitalize the 'header' suffix :(
|
||||||
'application': 'app-Header',
|
'application': 'app-Header',
|
||||||
'pukCodes': 'puk-Header',
|
'pukCodes': 'puk-Header',
|
||||||
'pinCodes': 'pin-Header',
|
'pinCodes': 'pin-Header',
|
||||||
@@ -628,7 +628,7 @@ class FsProfileElement(ProfileElement):
|
|||||||
# this is a template that belongs into the [A]DF of another template
|
# this is a template that belongs into the [A]DF of another template
|
||||||
# 1) find the PE for the referenced template
|
# 1) find the PE for the referenced template
|
||||||
parent_pe = self.pe_sequence.get_closest_prev_pe_for_templateID(self, template.parent.oid)
|
parent_pe = self.pe_sequence.get_closest_prev_pe_for_templateID(self, template.parent.oid)
|
||||||
# 2) resolve te [A]DF that forms the base of that parent PE
|
# 2) resolve the [A]DF that forms the base of that parent PE
|
||||||
pe_df = parent_pe.files[template.parent.base_df().pe_name].node
|
pe_df = parent_pe.files[template.parent.base_df().pe_name].node
|
||||||
self.pe_sequence.cur_df = pe_df
|
self.pe_sequence.cur_df = pe_df
|
||||||
self.pe_sequence.cur_df = self.pe_sequence.cur_df.add_file(file)
|
self.pe_sequence.cur_df = self.pe_sequence.cur_df.add_file(file)
|
||||||
@@ -649,7 +649,7 @@ class FsProfileElement(ProfileElement):
|
|||||||
self.add_file(file)
|
self.add_file(file)
|
||||||
|
|
||||||
def create_file(self, pename: str) -> File:
|
def create_file(self, pename: str) -> File:
|
||||||
"""Programatically create a file by its PE-Name."""
|
"""Programmatically create a file by its PE-Name."""
|
||||||
template = templates.ProfileTemplateRegistry.get_by_oid(self.templateID)
|
template = templates.ProfileTemplateRegistry.get_by_oid(self.templateID)
|
||||||
file = File(pename, None, template.files_by_pename.get(pename, None))
|
file = File(pename, None, template.files_by_pename.get(pename, None))
|
||||||
self.add_file(file)
|
self.add_file(file)
|
||||||
@@ -1409,7 +1409,7 @@ class ProfileElementHeader(ProfileElement):
|
|||||||
iccid: Optional[Hexstr] = '0'*20, profile_type: Optional[str] = None,
|
iccid: Optional[Hexstr] = '0'*20, profile_type: Optional[str] = None,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
"""You would usually initialize an instance either with a "decoded" argument (as read from
|
"""You would usually initialize an instance either with a "decoded" argument (as read from
|
||||||
a DER-encoded SAIP file via asn1tools), or [some of] the othe arguments in case you're
|
a DER-encoded SAIP file via asn1tools), or [some of] the other arguments in case you're
|
||||||
constructing a Profile Header from scratch.
|
constructing a Profile Header from scratch.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -1562,7 +1562,7 @@ class ProfileElementSequence:
|
|||||||
|
|
||||||
def _rebuild_pes_by_naa(self) -> None:
|
def _rebuild_pes_by_naa(self) -> None:
|
||||||
"""rebuild the self.pes_by_naa dict {naa: [ [pe, pe, pe], [pe, pe] ]} form,
|
"""rebuild the self.pes_by_naa dict {naa: [ [pe, pe, pe], [pe, pe] ]} form,
|
||||||
which basically means for every NAA there's a lsit of instances, and each consists
|
which basically means for every NAA there's a list of instances, and each consists
|
||||||
of a list of a list of PEs."""
|
of a list of a list of PEs."""
|
||||||
self.pres_by_naa = {}
|
self.pres_by_naa = {}
|
||||||
petype_not_naa_related = ['securityDomain', 'rfm', 'application', 'end']
|
petype_not_naa_related = ['securityDomain', 'rfm', 'application', 'end']
|
||||||
@@ -1690,7 +1690,7 @@ class ProfileElementSequence:
|
|||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
def get_index_by_pe(self, pe: ProfileElement) -> int:
|
def get_index_by_pe(self, pe: ProfileElement) -> int:
|
||||||
"""Return a list with the indicies of all instances of PEs of petype."""
|
"""Return a list with the indices of all instances of PEs of petype."""
|
||||||
ret = []
|
ret = []
|
||||||
i = 0
|
i = 0
|
||||||
for cur in self.pe_list:
|
for cur in self.pe_list:
|
||||||
@@ -1711,7 +1711,7 @@ class ProfileElementSequence:
|
|||||||
self.insert_at_index(idx+1, pe_new)
|
self.insert_at_index(idx+1, pe_new)
|
||||||
|
|
||||||
def get_index_by_type(self, petype: str) -> List[int]:
|
def get_index_by_type(self, petype: str) -> List[int]:
|
||||||
"""Return a list with the indicies of all instances of PEs of petype."""
|
"""Return a list with the indices of all instances of PEs of petype."""
|
||||||
ret = []
|
ret = []
|
||||||
i = 0
|
i = 0
|
||||||
for pe in self.pe_list:
|
for pe in self.pe_list:
|
||||||
@@ -1736,7 +1736,7 @@ class ProfileElementSequence:
|
|||||||
for service in naa.mandatory_services:
|
for service in naa.mandatory_services:
|
||||||
if service in hdr.decoded['eUICC-Mandatory-services']:
|
if service in hdr.decoded['eUICC-Mandatory-services']:
|
||||||
del hdr.decoded['eUICC-Mandatory-services'][service]
|
del hdr.decoded['eUICC-Mandatory-services'][service]
|
||||||
# remove any associaed mandatory filesystem templates
|
# remove any associated mandatory filesystem templates
|
||||||
for template in naa.templates:
|
for template in naa.templates:
|
||||||
if template in hdr.decoded['eUICC-Mandatory-GFSTEList']:
|
if template in hdr.decoded['eUICC-Mandatory-GFSTEList']:
|
||||||
hdr.decoded['eUICC-Mandatory-GFSTEList'] = [x for x in hdr.decoded['eUICC-Mandatory-GFSTEList'] if not template.prefix_match(x)]
|
hdr.decoded['eUICC-Mandatory-GFSTEList'] = [x for x in hdr.decoded['eUICC-Mandatory-GFSTEList'] if not template.prefix_match(x)]
|
||||||
@@ -2040,7 +2040,8 @@ class FsNodeADF(FsNodeDF):
|
|||||||
super().__init__(fid, parent, file, name)
|
super().__init__(fid, parent, file, name)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s(%s)' % (self.__class__.__name__, b2h(self.df_name))
|
# self.df_name is usually None for an ADF like ADF.USIM or ADF.ISIM so we need to guard against it
|
||||||
|
return '%s(%s)' % (self.__class__.__name__, b2h(self.df_name) if self.df_name else None)
|
||||||
|
|
||||||
class FsNodeMF(FsNodeDF):
|
class FsNodeMF(FsNodeDF):
|
||||||
"""The MF (Master File) in the filesystem hierarchy."""
|
"""The MF (Master File) in the filesystem hierarchy."""
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class CheckBasicStructure(ProfileConstraintChecker):
|
|||||||
|
|
||||||
def check_optional_ordering(self, pes: ProfileElementSequence):
|
def check_optional_ordering(self, pes: ProfileElementSequence):
|
||||||
"""Check the ordering of optional PEs following the respective mandatory ones."""
|
"""Check the ordering of optional PEs following the respective mandatory ones."""
|
||||||
# ordering and required depenencies
|
# ordering and required dependencies
|
||||||
self._is_after_if_exists(pes,'opt-usim', 'usim')
|
self._is_after_if_exists(pes,'opt-usim', 'usim')
|
||||||
self._is_after_if_exists(pes,'opt-isim', 'isim')
|
self._is_after_if_exists(pes,'opt-isim', 'isim')
|
||||||
self._is_after_if_exists(pes,'gsm-access', 'usim')
|
self._is_after_if_exists(pes,'gsm-access', 'usim')
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ from cryptography.hazmat.primitives.serialization import load_pem_private_key, E
|
|||||||
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
|
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
|
||||||
|
|
||||||
from pySim.utils import b2h
|
from pySim.utils import b2h
|
||||||
from . import x509_err
|
|
||||||
|
|
||||||
def check_signed(signed: x509.Certificate, signer: x509.Certificate) -> bool:
|
def check_signed(signed: x509.Certificate, signer: x509.Certificate) -> bool:
|
||||||
"""Verify if 'signed' certificate was signed using 'signer'."""
|
"""Verify if 'signed' certificate was signed using 'signer'."""
|
||||||
@@ -65,6 +64,9 @@ class oid:
|
|||||||
id_rspRole_ds_tls_v2 = x509.ObjectIdentifier(ID_RSP_ROLE + '.6')
|
id_rspRole_ds_tls_v2 = x509.ObjectIdentifier(ID_RSP_ROLE + '.6')
|
||||||
id_rspRole_ds_auth_v2 = x509.ObjectIdentifier(ID_RSP_ROLE + '.7')
|
id_rspRole_ds_auth_v2 = x509.ObjectIdentifier(ID_RSP_ROLE + '.7')
|
||||||
|
|
||||||
|
class VerifyError(Exception):
|
||||||
|
"""An error during certificate verification,"""
|
||||||
|
|
||||||
class CertificateSet:
|
class CertificateSet:
|
||||||
"""A set of certificates consisting of a trusted [self-signed] CA root certificate,
|
"""A set of certificates consisting of a trusted [self-signed] CA root certificate,
|
||||||
and an optional number of intermediate certificates. Can be used to verify the certificate chain
|
and an optional number of intermediate certificates. Can be used to verify the certificate chain
|
||||||
@@ -133,7 +135,7 @@ class CertificateSet:
|
|||||||
# we cannot check if there's no CRL
|
# we cannot check if there's no CRL
|
||||||
return
|
return
|
||||||
if self.crl.get_revoked_certificate_by_serial_number(cert.serial_nr):
|
if self.crl.get_revoked_certificate_by_serial_number(cert.serial_nr):
|
||||||
raise x509_err.CertificateRevoked()
|
raise VerifyError('Certificate is present in CRL, verification failed')
|
||||||
|
|
||||||
def verify_cert_chain(self, cert: x509.Certificate, max_depth: int = 100):
|
def verify_cert_chain(self, cert: x509.Certificate, max_depth: int = 100):
|
||||||
"""Verify if a given certificate's signature chain can be traced back to the root CA of this
|
"""Verify if a given certificate's signature chain can be traced back to the root CA of this
|
||||||
@@ -148,13 +150,13 @@ class CertificateSet:
|
|||||||
return
|
return
|
||||||
parent_cert = self.intermediate_certs.get(aki, None)
|
parent_cert = self.intermediate_certs.get(aki, None)
|
||||||
if not parent_cert:
|
if not parent_cert:
|
||||||
raise x509_err.MissingIntermediateCert(b2h(aki))
|
raise VerifyError('Could not find intermediate certificate for AuthKeyId %s' % b2h(aki))
|
||||||
check_signed(c, parent_cert)
|
check_signed(c, parent_cert)
|
||||||
# if we reach here, we passed (no exception raised)
|
# if we reach here, we passed (no exception raised)
|
||||||
c = parent_cert
|
c = parent_cert
|
||||||
depth += 1
|
depth += 1
|
||||||
if depth > max_depth:
|
if depth > max_depth:
|
||||||
raise x509_err.MaxDepthExceeded(max_depth, depth)
|
raise VerifyError('Maximum depth %u exceeded while verifying certificate chain' % max_depth)
|
||||||
|
|
||||||
|
|
||||||
def ecdsa_dss_to_tr03111(sig: bytes) -> bytes:
|
def ecdsa_dss_to_tr03111(sig: bytes) -> bytes:
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
"""X.509 certificate verification exceptions for GSMA eSIM."""
|
|
||||||
|
|
||||||
class VerifyError(Exception):
|
|
||||||
"""Base class for certificate verification errors."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MissingIntermediateCert(VerifyError):
|
|
||||||
"""Raised when an intermediate certificate in the chain cannot be found."""
|
|
||||||
def __init__(self, auth_key_id: str):
|
|
||||||
self.auth_key_id = auth_key_id
|
|
||||||
super().__init__(f'Could not find intermediate certificate for AuthKeyId {auth_key_id}')
|
|
||||||
|
|
||||||
|
|
||||||
class CertificateRevoked(VerifyError):
|
|
||||||
"""Raised when a certificate is found in the CRL."""
|
|
||||||
def __init__(self, cert_serial: str = None):
|
|
||||||
self.cert_serial = cert_serial
|
|
||||||
msg = 'Certificate is present in CRL, verification failed'
|
|
||||||
if cert_serial:
|
|
||||||
msg += f' (serial: {cert_serial})'
|
|
||||||
super().__init__(msg)
|
|
||||||
|
|
||||||
|
|
||||||
class MaxDepthExceeded(VerifyError):
|
|
||||||
"""Raised when certificate chain depth exceeds the maximum allowed."""
|
|
||||||
def __init__(self, max_depth: int, actual_depth: int):
|
|
||||||
self.max_depth = max_depth
|
|
||||||
self.actual_depth = actual_depth
|
|
||||||
super().__init__(f'Maximum depth {max_depth} exceeded while verifying certificate chain (actual: {actual_depth})')
|
|
||||||
|
|
||||||
|
|
||||||
class SignatureVerification(VerifyError):
|
|
||||||
"""Raised when certificate signature verification fails."""
|
|
||||||
def __init__(self, cert_subject: str = None, signer_subject: str = None):
|
|
||||||
self.cert_subject = cert_subject
|
|
||||||
self.signer_subject = signer_subject
|
|
||||||
msg = 'Certificate signature verification failed'
|
|
||||||
if cert_subject and signer_subject:
|
|
||||||
msg += f': {cert_subject} not signed by {signer_subject}'
|
|
||||||
super().__init__(msg)
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidCertificate(VerifyError):
|
|
||||||
"""Raised when a certificate is invalid (missing required fields, wrong type, etc)."""
|
|
||||||
def __init__(self, reason: str):
|
|
||||||
self.reason = reason
|
|
||||||
super().__init__(f'Invalid certificate: {reason}')
|
|
||||||
|
|
||||||
|
|
||||||
class CertificateExpired(VerifyError):
|
|
||||||
"""Raised when a certificate has expired."""
|
|
||||||
def __init__(self, cert_subject: str = None):
|
|
||||||
self.cert_subject = cert_subject
|
|
||||||
msg = 'Certificate has expired'
|
|
||||||
if cert_subject:
|
|
||||||
msg += f': {cert_subject}'
|
|
||||||
super().__init__(msg)
|
|
||||||
@@ -39,7 +39,7 @@ from osmocom.utils import h2b, b2h, is_hex, auto_int, auto_uint8, auto_uint16, i
|
|||||||
from osmocom.tlv import bertlv_parse_one
|
from osmocom.tlv import bertlv_parse_one
|
||||||
from osmocom.construct import filter_dict, parse_construct, build_construct
|
from osmocom.construct import filter_dict, parse_construct, build_construct
|
||||||
|
|
||||||
from pySim.utils import sw_match
|
from pySim.utils import sw_match, decomposeATR
|
||||||
from pySim.jsonpath import js_path_modify
|
from pySim.jsonpath import js_path_modify
|
||||||
from pySim.commands import SimCardCommands
|
from pySim.commands import SimCardCommands
|
||||||
from pySim.exceptions import SwMatchError
|
from pySim.exceptions import SwMatchError
|
||||||
@@ -86,7 +86,7 @@ class CardFile:
|
|||||||
self.service = service
|
self.service = service
|
||||||
self.shell_commands = [] # type: List[CommandSet]
|
self.shell_commands = [] # type: List[CommandSet]
|
||||||
|
|
||||||
# Note: the basic properties (fid, name, ect.) are verified when
|
# Note: the basic properties (fid, name, etc.) are verified when
|
||||||
# the file is attached to a parent file. See method add_file() in
|
# the file is attached to a parent file. See method add_file() in
|
||||||
# class Card DF
|
# class Card DF
|
||||||
|
|
||||||
@@ -266,7 +266,7 @@ class CardFile:
|
|||||||
def get_profile(self):
|
def get_profile(self):
|
||||||
"""Get the profile associated with this file. If this file does not have any
|
"""Get the profile associated with this file. If this file does not have any
|
||||||
profile assigned, try to find a file above (usually the MF) in the filesystem
|
profile assigned, try to find a file above (usually the MF) in the filesystem
|
||||||
hirarchy that has a profile assigned
|
hierarchy that has a profile assigned
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# If we have a profile set, return it
|
# If we have a profile set, return it
|
||||||
@@ -679,7 +679,7 @@ class TransparentEF(CardEF):
|
|||||||
Args:
|
Args:
|
||||||
fid : File Identifier (4 hex digits)
|
fid : File Identifier (4 hex digits)
|
||||||
sfid : Short File Identifier (2 hex digits, optional)
|
sfid : Short File Identifier (2 hex digits, optional)
|
||||||
name : Brief name of the file, lik EF_ICCID
|
name : Brief name of the file, like EF_ICCID
|
||||||
desc : Description of the file
|
desc : Description of the file
|
||||||
parent : Parent CardFile object within filesystem hierarchy
|
parent : Parent CardFile object within filesystem hierarchy
|
||||||
size : tuple of (minimum_size, recommended_size)
|
size : tuple of (minimum_size, recommended_size)
|
||||||
@@ -982,11 +982,11 @@ class LinFixedEF(CardEF):
|
|||||||
Args:
|
Args:
|
||||||
fid : File Identifier (4 hex digits)
|
fid : File Identifier (4 hex digits)
|
||||||
sfid : Short File Identifier (2 hex digits, optional)
|
sfid : Short File Identifier (2 hex digits, optional)
|
||||||
name : Brief name of the file, lik EF_ICCID
|
name : Brief name of the file, like EF_ICCID
|
||||||
desc : Description of the file
|
desc : Description of the file
|
||||||
parent : Parent CardFile object within filesystem hierarchy
|
parent : Parent CardFile object within filesystem hierarchy
|
||||||
rec_len : Tuple of (minimum_length, recommended_length)
|
rec_len : Tuple of (minimum_length, recommended_length)
|
||||||
leftpad: On write, data must be padded from the left to fit pysical record length
|
leftpad: On write, data must be padded from the left to fit physical record length
|
||||||
"""
|
"""
|
||||||
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
|
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
|
||||||
self.rec_len = rec_len
|
self.rec_len = rec_len
|
||||||
@@ -1422,7 +1422,7 @@ class BerTlvEF(CardEF):
|
|||||||
Args:
|
Args:
|
||||||
fid : File Identifier (4 hex digits)
|
fid : File Identifier (4 hex digits)
|
||||||
sfid : Short File Identifier (2 hex digits, optional)
|
sfid : Short File Identifier (2 hex digits, optional)
|
||||||
name : Brief name of the file, lik EF_ICCID
|
name : Brief name of the file, like EF_ICCID
|
||||||
desc : Description of the file
|
desc : Description of the file
|
||||||
parent : Parent CardFile object within filesystem hierarchy
|
parent : Parent CardFile object within filesystem hierarchy
|
||||||
size : tuple of (minimum_size, recommended_size)
|
size : tuple of (minimum_size, recommended_size)
|
||||||
@@ -1455,7 +1455,7 @@ class BerTlvEF(CardEF):
|
|||||||
export_str += "delete_all\n"
|
export_str += "delete_all\n"
|
||||||
for t in tags:
|
for t in tags:
|
||||||
result = lchan.retrieve_data(t)
|
result = lchan.retrieve_data(t)
|
||||||
(tag, l, val, remainer) = bertlv_parse_one(h2b(result[0]))
|
(tag, l, val, remainder) = bertlv_parse_one(h2b(result[0]))
|
||||||
export_str += ("set_data 0x%02x %s\n" % (t, b2h(val)))
|
export_str += ("set_data 0x%02x %s\n" % (t, b2h(val)))
|
||||||
return export_str.strip()
|
return export_str.strip()
|
||||||
|
|
||||||
@@ -1495,7 +1495,7 @@ class CardApplication:
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.adf = adf
|
self.adf = adf
|
||||||
self.sw = sw or {}
|
self.sw = sw or {}
|
||||||
# back-reference from ADF to Applicaiton
|
# back-reference from ADF to Application
|
||||||
if self.adf:
|
if self.adf:
|
||||||
self.aid = aid or self.adf.aid
|
self.aid = aid or self.adf.aid
|
||||||
self.adf.application = self
|
self.adf.application = self
|
||||||
@@ -1545,6 +1545,13 @@ class CardModel(abc.ABC):
|
|||||||
if atr == card_atr:
|
if atr == card_atr:
|
||||||
print("Detected CardModel:", cls.__name__)
|
print("Detected CardModel:", cls.__name__)
|
||||||
return True
|
return True
|
||||||
|
# if nothing found try to just compare the Historical Bytes of the ATR
|
||||||
|
card_atr_hb = decomposeATR(card_atr)['hb']
|
||||||
|
for atr in cls._atrs:
|
||||||
|
atr_hb = decomposeATR(atr)['hb']
|
||||||
|
if atr_hb == card_atr_hb:
|
||||||
|
print("Detected CardModel:", cls.__name__)
|
||||||
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -1565,7 +1572,7 @@ class Path:
|
|||||||
p = p.split('/')
|
p = p.split('/')
|
||||||
elif len(p) and isinstance(p[0], int):
|
elif len(p) and isinstance(p[0], int):
|
||||||
p = ['%04x' % x for x in p]
|
p = ['%04x' % x for x in p]
|
||||||
# make sure internal representation alwas is uppercase only
|
# make sure internal representation always is uppercase only
|
||||||
self.list = [x.upper() for x in p]
|
self.list = [x.upper() for x in p]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
|
|||||||
@@ -627,7 +627,7 @@ class ADF_SD(CardADF):
|
|||||||
kcv_bin = compute_kcv(opts.key_type[i], h2b(opts.key_data[i])) or b''
|
kcv_bin = compute_kcv(opts.key_type[i], h2b(opts.key_data[i])) or b''
|
||||||
kcv = b2h(kcv_bin)
|
kcv = b2h(kcv_bin)
|
||||||
if self._cmd.lchan.scc.scp:
|
if self._cmd.lchan.scc.scp:
|
||||||
# encrypte key data with DEK of current SCP
|
# encrypted key data with DEK of current SCP
|
||||||
kcb = b2h(self._cmd.lchan.scc.scp.encrypt_key(h2b(opts.key_data[i])))
|
kcb = b2h(self._cmd.lchan.scc.scp.encrypt_key(h2b(opts.key_data[i])))
|
||||||
else:
|
else:
|
||||||
# (for example) during personalization, DEK might not be required)
|
# (for example) during personalization, DEK might not be required)
|
||||||
@@ -755,7 +755,7 @@ class ADF_SD(CardADF):
|
|||||||
|
|
||||||
inst_load_parser = argparse.ArgumentParser()
|
inst_load_parser = argparse.ArgumentParser()
|
||||||
inst_load_parser.add_argument('--load-file-aid', type=is_hexstr, required=True,
|
inst_load_parser.add_argument('--load-file-aid', type=is_hexstr, required=True,
|
||||||
help='AID of the loded file')
|
help='AID of the loaded file')
|
||||||
inst_load_parser.add_argument('--security-domain-aid', type=is_hexstr, default='',
|
inst_load_parser.add_argument('--security-domain-aid', type=is_hexstr, default='',
|
||||||
help='AID of the Security Domain into which the file shalle be added')
|
help='AID of the Security Domain into which the file shalle be added')
|
||||||
inst_load_parser.add_argument('--load-file-hash', type=is_hexstr, default='',
|
inst_load_parser.add_argument('--load-file-hash', type=is_hexstr, default='',
|
||||||
@@ -845,7 +845,7 @@ class ADF_SD(CardADF):
|
|||||||
# TODO:tune chunk_len based on the overhead of the used SCP?
|
# TODO:tune chunk_len based on the overhead of the used SCP?
|
||||||
# build TLV according to GPC_SPE_034 section 11.6.2.3 / Table 11-58 for unencrypted case
|
# build TLV according to GPC_SPE_034 section 11.6.2.3 / Table 11-58 for unencrypted case
|
||||||
remainder = b'\xC4' + bertlv_encode_len(len(contents)) + contents
|
remainder = b'\xC4' + bertlv_encode_len(len(contents)) + contents
|
||||||
# transfer this in vaious chunks to the card
|
# transfer this in various chunks to the card
|
||||||
total_size = len(remainder)
|
total_size = len(remainder)
|
||||||
block_nr = 0
|
block_nr = 0
|
||||||
while len(remainder):
|
while len(remainder):
|
||||||
|
|||||||
@@ -104,4 +104,4 @@ class UiccSdInstallParams(TLV_IE_Collection, nested=[UiccScp, AcceptExtradAppsAn
|
|||||||
# KID 0x02: SK.CASD.AUT (PK) and KS.CASD.AUT (Non-PK)
|
# KID 0x02: SK.CASD.AUT (PK) and KS.CASD.AUT (Non-PK)
|
||||||
# KID 0x03: SK.CASD.CT (P) and KS.CASD.CT (Non-PK)
|
# KID 0x03: SK.CASD.CT (P) and KS.CASD.CT (Non-PK)
|
||||||
# KVN 0x75 KID 0x01: 16-byte DES key for Ciphered Load File Data Block
|
# KVN 0x75 KID 0x01: 16-byte DES key for Ciphered Load File Data Block
|
||||||
# KVN 0xFF reserved for ISD with SCP02 without SCP80 s upport
|
# KVN 0xFF reserved for ISD with SCP02 without SCP80 s support
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class CapFile():
|
|||||||
raise ValueError("invalid cap file, %s missing!" % required_components[component])
|
raise ValueError("invalid cap file, %s missing!" % required_components[component])
|
||||||
|
|
||||||
def get_loadfile(self) -> bytes:
|
def get_loadfile(self) -> bytes:
|
||||||
"""Get the executeable loadfile as hexstring"""
|
"""Get the executable loadfile as hexstring"""
|
||||||
# Concatenate all cap file components in the specified order
|
# Concatenate all cap file components in the specified order
|
||||||
# see also: Java Card Platform Virtual Machine Specification, v3.2, section 6.3
|
# see also: Java Card Platform Virtual Machine Specification, v3.2, section 6.3
|
||||||
loadfile = self.__component['Header']
|
loadfile = self.__component['Header']
|
||||||
|
|||||||
@@ -495,7 +495,7 @@ class IsimCard(UiccCardBase):
|
|||||||
|
|
||||||
class MagicSimBase(abc.ABC, SimCard):
|
class MagicSimBase(abc.ABC, SimCard):
|
||||||
"""
|
"""
|
||||||
Theses cards uses several record based EFs to store the provider infos,
|
These cards uses several record based EFs to store the provider infos,
|
||||||
each possible provider uses a specific record number in each EF. The
|
each possible provider uses a specific record number in each EF. The
|
||||||
indexes used are ( where N is the number of providers supported ) :
|
indexes used are ( where N is the number of providers supported ) :
|
||||||
- [2 .. N+1] for the operator name
|
- [2 .. N+1] for the operator name
|
||||||
@@ -644,7 +644,7 @@ class MagicSim(MagicSimBase):
|
|||||||
|
|
||||||
class FakeMagicSim(SimCard):
|
class FakeMagicSim(SimCard):
|
||||||
"""
|
"""
|
||||||
Theses cards have a record based EF 3f00/000c that contains the provider
|
These cards have a record based EF 3f00/000c that contains the provider
|
||||||
information. See the program method for its format. The records go from
|
information. See the program method for its format. The records go from
|
||||||
1 to N.
|
1 to N.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ def dec_addr_tlv(hexstr):
|
|||||||
|
|
||||||
elif addr_type == 0x01: # IPv4
|
elif addr_type == 0x01: # IPv4
|
||||||
# Skip address tye byte i.e. first byte in value list
|
# Skip address tye byte i.e. first byte in value list
|
||||||
# Skip the unused byte in Octect 4 after address type byte as per 3GPP TS 31.102
|
# Skip the unused byte in Octet 4 after address type byte as per 3GPP TS 31.102
|
||||||
ipv4 = tlv[2][2:]
|
ipv4 = tlv[2][2:]
|
||||||
content = '.'.join(str(x) for x in ipv4)
|
content = '.'.join(str(x) for x in ipv4)
|
||||||
return (content, '01')
|
return (content, '01')
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class CardProfile:
|
|||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def _try_match_card(cls, scc: SimCardCommands) -> None:
|
def _try_match_card(cls, scc: SimCardCommands) -> None:
|
||||||
"""Try to see if the specific profile matches the card. This method is a
|
"""Try to see if the specific profile matches the card. This method is a
|
||||||
placeholder that is overloaded by specific dirived classes. The method
|
placeholder that is overloaded by specific derived classes. The method
|
||||||
actively probes the card to make sure the profile class matches the
|
actively probes the card to make sure the profile class matches the
|
||||||
physical card. This usually also means that the card is reset during
|
physical card. This usually also means that the card is reset during
|
||||||
the process, so this method must not be called at random times. It may
|
the process, so this method must not be called at random times. It may
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ class RuntimeState:
|
|||||||
self.card.set_apdu_parameter(
|
self.card.set_apdu_parameter(
|
||||||
cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
|
cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
|
||||||
|
|
||||||
|
# make sure MF is selected before probing for Addons
|
||||||
|
self.lchan[0].select('MF')
|
||||||
|
|
||||||
for addon_cls in self.profile.addons:
|
for addon_cls in self.profile.addons:
|
||||||
addon = addon_cls()
|
addon = addon_cls()
|
||||||
if addon.probe(self.card):
|
if addon.probe(self.card):
|
||||||
@@ -147,7 +150,7 @@ class RuntimeState:
|
|||||||
# select MF to reset internal state and to verify card really works
|
# select MF to reset internal state and to verify card really works
|
||||||
self.lchan[0].select('MF', cmd_app)
|
self.lchan[0].select('MF', cmd_app)
|
||||||
self.lchan[0].selected_adf = None
|
self.lchan[0].selected_adf = None
|
||||||
# store ATR as part of our card identies dict
|
# store ATR as part of our card identities dict
|
||||||
self.identity['ATR'] = atr
|
self.identity['ATR'] = atr
|
||||||
return atr
|
return atr
|
||||||
|
|
||||||
@@ -321,7 +324,7 @@ class RuntimeLchan:
|
|||||||
# If we succeed, we know that the file exists on the card and we may
|
# If we succeed, we know that the file exists on the card and we may
|
||||||
# proceed with creating a new CardEF object in the local file model at
|
# proceed with creating a new CardEF object in the local file model at
|
||||||
# run time. In case the file does not exist on the card, we just abort.
|
# run time. In case the file does not exist on the card, we just abort.
|
||||||
# The state on the card (selected file/application) wont't be changed,
|
# The state on the card (selected file/application) won't be changed,
|
||||||
# so we do not have to update any state in that case.
|
# so we do not have to update any state in that case.
|
||||||
(data, _sw) = self.scc.select_file(fid)
|
(data, _sw) = self.scc.select_file(fid)
|
||||||
except SwMatchError as swm:
|
except SwMatchError as swm:
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class SecureChannel(abc.ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def send_apdu_wrapper(self, send_fn: callable, pdu: Hexstr, *args, **kwargs) -> ResTuple:
|
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."""
|
"""Wrapper function to wrap command APDU and unwrap response APDU around send_apdu callable."""
|
||||||
pdu_wrapped = b2h(self.wrap_cmd_apdu(h2b(pdu)))
|
pdu_wrapped = b2h(self.wrap_cmd_apdu(h2b(pdu)))
|
||||||
res, sw = send_fn(pdu_wrapped, *args, **kwargs)
|
res, sw = send_fn(pdu_wrapped, *args, **kwargs)
|
||||||
res_unwrapped = b2h(self.unwrap_rsp_apdu(h2b(sw), h2b(res)))
|
res_unwrapped = b2h(self.unwrap_rsp_apdu(h2b(sw), h2b(res)))
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ class LinkBase(abc.ABC):
|
|||||||
# It *was* successful after all -- the extra pieces FETCH handled
|
# It *was* successful after all -- the extra pieces FETCH handled
|
||||||
# need not concern the caller.
|
# need not concern the caller.
|
||||||
rv = (rv[0], '9000')
|
rv = (rv[0], '9000')
|
||||||
# proactive sim as per TS 102 221 Setion 7.4.2
|
# proactive sim as per TS 102 221 Section 7.4.2
|
||||||
# TODO: Check SW manually to avoid recursing on the stack (provided this piece of code stays in this place)
|
# TODO: Check SW manually to avoid recursing on the stack (provided this piece of code stays in this place)
|
||||||
fetch_rv = self.send_apdu_checksw('80120000' + last_sw[2:], sw)
|
fetch_rv = self.send_apdu_checksw('80120000' + last_sw[2:], sw)
|
||||||
# Setting this in case we later decide not to send a terminal
|
# Setting this in case we later decide not to send a terminal
|
||||||
@@ -228,7 +228,7 @@ class LinkBase(abc.ABC):
|
|||||||
# Structure as per TS 102 223 V4.4.0 Section 6.8
|
# Structure as per TS 102 223 V4.4.0 Section 6.8
|
||||||
|
|
||||||
# Testing hint: The value of tail does not influence the behavior
|
# Testing hint: The value of tail does not influence the behavior
|
||||||
# of an SJA2 that sent ans SMS, so this is implemented only
|
# of an SJA2 that sent an SMS, so this is implemented only
|
||||||
# following TS 102 223, and not fully tested.
|
# following TS 102 223, and not fully tested.
|
||||||
ti_list_bin = [x.to_tlv() for x in ti_list]
|
ti_list_bin = [x.to_tlv() for x in ti_list]
|
||||||
tail = b''.join(ti_list_bin)
|
tail = b''.join(ti_list_bin)
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ EF_5G_PROSE_ST_map = {
|
|||||||
5: '5G ProSe configuration data for usage information reporting',
|
5: '5G ProSe configuration data for usage information reporting',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Mapping between USIM Enbled Service Number and its description
|
# Mapping between USIM Enabled Service Number and its description
|
||||||
EF_EST_map = {
|
EF_EST_map = {
|
||||||
1: 'Fixed Dialling Numbers (FDN)',
|
1: 'Fixed Dialling Numbers (FDN)',
|
||||||
2: 'Barred Dialling Numbers (BDN)',
|
2: 'Barred Dialling Numbers (BDN)',
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ class EF_AC_GBAUAPI(LinFixedEF):
|
|||||||
"""The use of this EF is eescribed in 3GPP TS 31.130"""
|
"""The use of this EF is eescribed in 3GPP TS 31.130"""
|
||||||
class AppletNafAccessControl(BER_TLV_IE, tag=0x80):
|
class AppletNafAccessControl(BER_TLV_IE, tag=0x80):
|
||||||
# the use of Int8ub as length field in Prefixed is strictly speaking incorrect, as it is a BER-TLV
|
# the use of Int8ub as length field in Prefixed is strictly speaking incorrect, as it is a BER-TLV
|
||||||
# length field whihc will consume two bytes from length > 127 bytes. However, AIDs and NAF IDs can
|
# length field which will consume two bytes from length > 127 bytes. However, AIDs and NAF IDs can
|
||||||
# safely be assumed shorter than that
|
# safely be assumed shorter than that
|
||||||
_construct = Struct('aid'/Prefixed(Int8ub, GreedyBytes),
|
_construct = Struct('aid'/Prefixed(Int8ub, GreedyBytes),
|
||||||
'naf_id'/Prefixed(Int8ub, GreedyBytes))
|
'naf_id'/Prefixed(Int8ub, GreedyBytes))
|
||||||
|
|||||||
@@ -1007,7 +1007,7 @@ class EF_ICCID(TransparentEF):
|
|||||||
def _encode_hex(self, abstract, **kwargs):
|
def _encode_hex(self, abstract, **kwargs):
|
||||||
return enc_iccid(abstract['iccid'])
|
return enc_iccid(abstract['iccid'])
|
||||||
|
|
||||||
# TS 102 221 Section 13.3 / TS 31.101 Secction 13 / TS 51.011 Section 10.1.2
|
# TS 102 221 Section 13.3 / TS 31.101 Section 13 / TS 51.011 Section 10.1.2
|
||||||
class EF_PL(TransRecEF):
|
class EF_PL(TransRecEF):
|
||||||
_test_de_encode = [
|
_test_de_encode = [
|
||||||
( '6465', "de" ),
|
( '6465', "de" ),
|
||||||
|
|||||||
139
pySim/utils.py
139
pySim/utils.py
@@ -15,6 +15,7 @@ from osmocom.tlv import bertlv_encode_tag, bertlv_encode_len
|
|||||||
|
|
||||||
# Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com>
|
# Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com>
|
||||||
# Copyright (C) 2021 Harald Welte <laforge@osmocom.org>
|
# Copyright (C) 2021 Harald Welte <laforge@osmocom.org>
|
||||||
|
# Copyright (C) 2009-2022 Ludovic Rousseau
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
@@ -330,7 +331,7 @@ def derive_mnc(digit1: int, digit2: int, digit3: int = 0x0f) -> int:
|
|||||||
mnc = 0
|
mnc = 0
|
||||||
|
|
||||||
# 3-rd digit is optional for the MNC. If present
|
# 3-rd digit is optional for the MNC. If present
|
||||||
# the algorythm is the same as for the MCC.
|
# the algorithm is the same as for the MCC.
|
||||||
if digit3 != 0x0f:
|
if digit3 != 0x0f:
|
||||||
return derive_mcc(digit1, digit2, digit3)
|
return derive_mcc(digit1, digit2, digit3)
|
||||||
|
|
||||||
@@ -410,7 +411,7 @@ def get_addr_type(addr):
|
|||||||
|
|
||||||
fqdn_flag = True
|
fqdn_flag = True
|
||||||
for i in addr_list:
|
for i in addr_list:
|
||||||
# Only Alpha-numeric characters and hyphen - RFC 1035
|
# Only Alphanumeric characters and hyphen - RFC 1035
|
||||||
import re
|
import re
|
||||||
if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
|
if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i):
|
||||||
fqdn_flag = False
|
fqdn_flag = False
|
||||||
@@ -476,7 +477,7 @@ def expand_hex(hexstring, length):
|
|||||||
"""Expand a given hexstring to a specified length by replacing "." or ".."
|
"""Expand a given hexstring to a specified length by replacing "." or ".."
|
||||||
with a filler that is derived from the neighboring nibbles respective
|
with a filler that is derived from the neighboring nibbles respective
|
||||||
bytes. Usually this will be the nibble respective byte before "." or
|
bytes. Usually this will be the nibble respective byte before "." or
|
||||||
"..", execpt when the string begins with "." or "..", then the nibble
|
"..", except when the string begins with "." or "..", then the nibble
|
||||||
respective byte after "." or ".." is used.". In case the string cannot
|
respective byte after "." or ".." is used.". In case the string cannot
|
||||||
be expanded for some reason, the input string is returned unmodified.
|
be expanded for some reason, the input string is returned unmodified.
|
||||||
|
|
||||||
@@ -585,10 +586,138 @@ def parse_command_apdu(apdu: bytes) -> int:
|
|||||||
raise ValueError('invalid APDU (%s), too short!' % b2h(apdu))
|
raise ValueError('invalid APDU (%s), too short!' % b2h(apdu))
|
||||||
|
|
||||||
|
|
||||||
|
# ATR handling code under GPL from parseATR: https://github.com/LudovicRousseau/pyscard-contrib
|
||||||
|
def normalizeATR(atr):
|
||||||
|
"""Transform an ATR in list of integers.
|
||||||
|
valid input formats are
|
||||||
|
"3B A7 00 40 18 80 65 A2 08 01 01 52"
|
||||||
|
"3B:A7:00:40:18:80:65:A2:08:01:01:52"
|
||||||
|
|
||||||
|
Args:
|
||||||
|
atr: string
|
||||||
|
Returns:
|
||||||
|
list of bytes
|
||||||
|
|
||||||
|
>>> normalize("3B:A7:00:40:18:80:65:A2:08:01:01:52")
|
||||||
|
[59, 167, 0, 64, 24, 128, 101, 162, 8, 1, 1, 82]
|
||||||
|
"""
|
||||||
|
atr = atr.replace(":", "")
|
||||||
|
atr = atr.replace(" ", "")
|
||||||
|
|
||||||
|
res = []
|
||||||
|
while len(atr) >= 2:
|
||||||
|
byte, atr = atr[:2], atr[2:]
|
||||||
|
res.append(byte)
|
||||||
|
if len(atr) > 0:
|
||||||
|
raise ValueError("warning: odd string, remainder: %r" % atr)
|
||||||
|
|
||||||
|
atr = [int(x, 16) for x in res]
|
||||||
|
return atr
|
||||||
|
|
||||||
|
|
||||||
|
# ATR handling code under GPL from parseATR: https://github.com/LudovicRousseau/pyscard-contrib
|
||||||
|
def decomposeATR(atr_txt):
|
||||||
|
"""Decompose the ATR in elementary fields
|
||||||
|
|
||||||
|
Args:
|
||||||
|
atr_txt: ATR as a hex bytes string
|
||||||
|
Returns:
|
||||||
|
dictionary of field and values
|
||||||
|
|
||||||
|
>>> decomposeATR("3B A7 00 40 18 80 65 A2 08 01 01 52")
|
||||||
|
{ 'T0': {'value': 167},
|
||||||
|
'TB': {1: {'value': 0}},
|
||||||
|
'TC': {2: {'value': 24}},
|
||||||
|
'TD': {1: {'value': 64}},
|
||||||
|
'TS': {'value': 59},
|
||||||
|
'atr': [59, 167, 0, 64, 24, 128, 101, 162, 8, 1, 1, 82],
|
||||||
|
'hb': {'value': [128, 101, 162, 8, 1, 1, 82]},
|
||||||
|
'hbn': 7}
|
||||||
|
"""
|
||||||
|
ATR_PROTOCOL_TYPE_T0 = 0
|
||||||
|
atr_txt = normalizeATR(atr_txt)
|
||||||
|
atr = {}
|
||||||
|
|
||||||
|
# the ATR itself as a list of integers
|
||||||
|
atr["atr"] = atr_txt
|
||||||
|
|
||||||
|
# store TS and T0
|
||||||
|
atr["TS"] = {"value": atr_txt[0]}
|
||||||
|
TDi = atr_txt[1]
|
||||||
|
atr["T0"] = {"value": TDi}
|
||||||
|
hb_length = TDi & 15
|
||||||
|
pointer = 1
|
||||||
|
# protocol number
|
||||||
|
pn = 1
|
||||||
|
|
||||||
|
# store number of historical bytes
|
||||||
|
atr["hbn"] = TDi & 0xF
|
||||||
|
|
||||||
|
while pointer < len(atr_txt):
|
||||||
|
# Check TAi is present
|
||||||
|
if (TDi | 0xEF) == 0xFF:
|
||||||
|
pointer += 1
|
||||||
|
if "TA" not in atr:
|
||||||
|
atr["TA"] = {}
|
||||||
|
atr["TA"][pn] = {"value": atr_txt[pointer]}
|
||||||
|
|
||||||
|
# Check TBi is present
|
||||||
|
if (TDi | 0xDF) == 0xFF:
|
||||||
|
pointer += 1
|
||||||
|
if "TB" not in atr:
|
||||||
|
atr["TB"] = {}
|
||||||
|
atr["TB"][pn] = {"value": atr_txt[pointer]}
|
||||||
|
|
||||||
|
# Check TCi is present
|
||||||
|
if (TDi | 0xBF) == 0xFF:
|
||||||
|
pointer += 1
|
||||||
|
if "TC" not in atr:
|
||||||
|
atr["TC"] = {}
|
||||||
|
atr["TC"][pn] = {"value": atr_txt[pointer]}
|
||||||
|
|
||||||
|
# Check TDi is present
|
||||||
|
if (TDi | 0x7F) == 0xFF:
|
||||||
|
pointer += 1
|
||||||
|
if "TD" not in atr:
|
||||||
|
atr["TD"] = {}
|
||||||
|
TDi = atr_txt[pointer]
|
||||||
|
atr["TD"][pn] = {"value": TDi}
|
||||||
|
if (TDi & 0x0F) != ATR_PROTOCOL_TYPE_T0:
|
||||||
|
atr["TCK"] = True
|
||||||
|
pn += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Store historical bytes
|
||||||
|
atr["hb"] = {"value": atr_txt[pointer + 1 : pointer + 1 + hb_length]}
|
||||||
|
|
||||||
|
# Store TCK
|
||||||
|
last = pointer + 1 + hb_length
|
||||||
|
if "TCK" in atr:
|
||||||
|
try:
|
||||||
|
atr["TCK"] = {"value": atr_txt[last]}
|
||||||
|
except IndexError:
|
||||||
|
atr["TCK"] = {"value": -1}
|
||||||
|
last += 1
|
||||||
|
|
||||||
|
if len(atr_txt) > last:
|
||||||
|
atr["extra"] = atr_txt[last:]
|
||||||
|
|
||||||
|
if len(atr["hb"]["value"]) < hb_length:
|
||||||
|
missing = hb_length - len(atr["hb"]["value"])
|
||||||
|
if missing > 1:
|
||||||
|
(t1, t2) = ("s", "are")
|
||||||
|
else:
|
||||||
|
(t1, t2) = ("", "is")
|
||||||
|
atr["warning"] = "ATR is truncated: %d byte%s %s missing" % (missing, t1, t2)
|
||||||
|
|
||||||
|
return atr
|
||||||
|
|
||||||
|
|
||||||
class DataObject(abc.ABC):
|
class DataObject(abc.ABC):
|
||||||
"""A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
|
"""A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
|
||||||
simply has any number of different TLVs that may occur in any order at any point, ISO 7816
|
simply has any number of different TLVs that may occur in any order at any point, ISO 7816
|
||||||
has the habit of specifying TLV data but with very spcific ordering, or specific choices of
|
has the habit of specifying TLV data but with very specific ordering, or specific choices of
|
||||||
tags at specific points in a stream. This class tries to represent this."""
|
tags at specific points in a stream. This class tries to represent this."""
|
||||||
|
|
||||||
def __init__(self, name: str, desc: Optional[str] = None, tag: Optional[int] = None):
|
def __init__(self, name: str, desc: Optional[str] = None, tag: Optional[int] = None):
|
||||||
@@ -710,7 +839,7 @@ class TL0_DataObject(DataObject):
|
|||||||
|
|
||||||
|
|
||||||
class DataObjectCollection:
|
class DataObjectCollection:
|
||||||
"""A DataObjectCollection consits of multiple Data Objects identified by their tags.
|
"""A DataObjectCollection consists of multiple Data Objects identified by their tags.
|
||||||
A given encoded DO may contain any of them in any order, and may contain multiple instances
|
A given encoded DO may contain any of them in any order, and may contain multiple instances
|
||||||
of each DO."""
|
of each DO."""
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools", "wheel"]
|
requires = ["setuptools", "wheel"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[tool.pylint.main]
|
||||||
|
ignored-classes = ["twisted.internet.reactor"]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
pyscard
|
pyscard
|
||||||
pyserial
|
pyserial
|
||||||
pytlv
|
pytlv
|
||||||
cmd2>=1.5
|
cmd2>=2.6.2,<3.0
|
||||||
jsonpath-ng
|
jsonpath-ng
|
||||||
construct>=2.10.70
|
construct>=2.10.70
|
||||||
bidict
|
bidict
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,7 +1,7 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIICgjCCAimgAwIBAgIBCTAKBggqhkjOPQQDAjBEMRAwDgYDVQQDDAdUZXN0IENJ
|
MIICgzCCAimgAwIBAgIBCTAKBggqhkjOPQQDAjBEMRAwDgYDVQQDDAdUZXN0IENJ
|
||||||
MREwDwYDVQQLDAhURVNUQ0VSVDEQMA4GA1UECgwHUlNQVEVTVDELMAkGA1UEBhMC
|
MREwDwYDVQQLDAhURVNUQ0VSVDEQMA4GA1UECgwHUlNQVEVTVDELMAkGA1UEBhMC
|
||||||
SVQwHhcNMjQwNzA5MTUyOTM2WhcNMjUwODExMTUyOTM2WjAzMQ0wCwYDVQQKDARB
|
SVQwHhcNMjUwNjMwMTMxNDM4WhcNMjYwODAyMTMxNDM4WjAzMQ0wCwYDVQQKDARB
|
||||||
Q01FMSIwIAYDVQQDDBl0ZXN0c21kcHBsdXMxLmV4YW1wbGUuY29tMFowFAYHKoZI
|
Q01FMSIwIAYDVQQDDBl0ZXN0c21kcHBsdXMxLmV4YW1wbGUuY29tMFowFAYHKoZI
|
||||||
zj0CAQYJKyQDAwIIAQEHA0IABEwizNgsjQIh+dhUO3LhB7zJ/ZBU1mx1wOt0p73n
|
zj0CAQYJKyQDAwIIAQEHA0IABEwizNgsjQIh+dhUO3LhB7zJ/ZBU1mx1wOt0p73n
|
||||||
MOdhjvZbJwteguQ6eW+N7guvivvrilNiU3oC/WXHnkEZa7WjggEaMIIBFjAOBgNV
|
MOdhjvZbJwteguQ6eW+N7guvivvrilNiU3oC/WXHnkEZa7WjggEaMIIBFjAOBgNV
|
||||||
@@ -10,7 +10,7 @@ A1UdIAQNMAswCQYHZ4ESAQIBAzAdBgNVHQ4EFgQUPTMJg/OfzFvS5K1ophmnR0iu
|
|||||||
i50wHwYDVR0jBBgwFoAUwLxwujaSnUO0Z/9XVwUw5Xq4/NgwKQYDVR0RBCIwIIIZ
|
i50wHwYDVR0jBBgwFoAUwLxwujaSnUO0Z/9XVwUw5Xq4/NgwKQYDVR0RBCIwIIIZ
|
||||||
dGVzdHNtZHBwbHVzMS5leGFtcGxlLmNvbYgDiDcKMGEGA1UdHwRaMFgwKqAooCaG
|
dGVzdHNtZHBwbHVzMS5leGFtcGxlLmNvbYgDiDcKMGEGA1UdHwRaMFgwKqAooCaG
|
||||||
JGh0dHA6Ly9jaS50ZXN0LmV4YW1wbGUuY29tL0NSTC1BLmNybDAqoCigJoYkaHR0
|
JGh0dHA6Ly9jaS50ZXN0LmV4YW1wbGUuY29tL0NSTC1BLmNybDAqoCigJoYkaHR0
|
||||||
cDovL2NpLnRlc3QuZXhhbXBsZS5jb20vQ1JMLUIuY3JsMAoGCCqGSM49BAMCA0cA
|
cDovL2NpLnRlc3QuZXhhbXBsZS5jb20vQ1JMLUIuY3JsMAoGCCqGSM49BAMCA0gA
|
||||||
MEQCIHHmXEy9mgudh/VbK0hJwmX7eOgbvHLnlujrpQzvUd4uAiBFVJgSdzYvrmJ9
|
MEUCIQCfaGcMk+kuSJsbIyRPWttwWNftwQdHCQuu346PaiA2FAIgUrqhPw2um9gV
|
||||||
5yeIvmjHwxSMBgQp2dde7OtdVEK8Kw==
|
C+eWHaXio7WQh5L6VgLZzNifTQcldD4=
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
|||||||
Binary file not shown.
@@ -1,7 +1,7 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIICgzCCAiigAwIBAgIBCTAKBggqhkjOPQQDAjBEMRAwDgYDVQQDDAdUZXN0IENJ
|
MIICgjCCAiigAwIBAgIBCTAKBggqhkjOPQQDAjBEMRAwDgYDVQQDDAdUZXN0IENJ
|
||||||
MREwDwYDVQQLDAhURVNUQ0VSVDEQMA4GA1UECgwHUlNQVEVTVDELMAkGA1UEBhMC
|
MREwDwYDVQQLDAhURVNUQ0VSVDEQMA4GA1UECgwHUlNQVEVTVDELMAkGA1UEBhMC
|
||||||
SVQwHhcNMjQwNzA5MTUyODMzWhcNMjUwODExMTUyODMzWjAzMQ0wCwYDVQQKDARB
|
SVQwHhcNMjUwNjMwMTMxNDM4WhcNMjYwODAyMTMxNDM4WjAzMQ0wCwYDVQQKDARB
|
||||||
Q01FMSIwIAYDVQQDDBl0ZXN0c21kcHBsdXMxLmV4YW1wbGUuY29tMFkwEwYHKoZI
|
Q01FMSIwIAYDVQQDDBl0ZXN0c21kcHBsdXMxLmV4YW1wbGUuY29tMFkwEwYHKoZI
|
||||||
zj0CAQYIKoZIzj0DAQcDQgAEKCQwdc6O/R+uZ2g5QH2ybkzLQ3CUYhybOWEz8bJL
|
zj0CAQYIKoZIzj0DAQcDQgAEKCQwdc6O/R+uZ2g5QH2ybkzLQ3CUYhybOWEz8bJL
|
||||||
tQG4/k6yTT4NOS8lP28blGJws8opLjTbb3qHs6X2rJRfCKOCARowggEWMA4GA1Ud
|
tQG4/k6yTT4NOS8lP28blGJws8opLjTbb3qHs6X2rJRfCKOCARowggEWMA4GA1Ud
|
||||||
@@ -10,7 +10,7 @@ VR0gBA0wCzAJBgdngRIBAgEDMB0GA1UdDgQWBBQn/vHyKRh+x4Pt9uApZKRRjVfU
|
|||||||
qTAfBgNVHSMEGDAWgBT1QXK9+YqV1ly+uIo4ocEdgAqFwzApBgNVHREEIjAgghl0
|
qTAfBgNVHSMEGDAWgBT1QXK9+YqV1ly+uIo4ocEdgAqFwzApBgNVHREEIjAgghl0
|
||||||
ZXN0c21kcHBsdXMxLmV4YW1wbGUuY29tiAOINwowYQYDVR0fBFowWDAqoCigJoYk
|
ZXN0c21kcHBsdXMxLmV4YW1wbGUuY29tiAOINwowYQYDVR0fBFowWDAqoCigJoYk
|
||||||
aHR0cDovL2NpLnRlc3QuZXhhbXBsZS5jb20vQ1JMLUEuY3JsMCqgKKAmhiRodHRw
|
aHR0cDovL2NpLnRlc3QuZXhhbXBsZS5jb20vQ1JMLUEuY3JsMCqgKKAmhiRodHRw
|
||||||
Oi8vY2kudGVzdC5leGFtcGxlLmNvbS9DUkwtQi5jcmwwCgYIKoZIzj0EAwIDSQAw
|
Oi8vY2kudGVzdC5leGFtcGxlLmNvbS9DUkwtQi5jcmwwCgYIKoZIzj0EAwIDSAAw
|
||||||
RgIhAL1qQ/cnrCZC7UnnLJ8WeK+0aWUJFWh1cOlBEzw0NlTVAiEA25Vf4WHzwmJi
|
RQIhAL+1lp/hGsj87/5RqOX2u3hS/VSftDN7EPrHJJFnTXLRAiBVxemKIKmC7+W1
|
||||||
zkARzxJ1qB0qfBofuJrtfPM4gNJ4Quw=
|
+RsTY5I51R+Cyoq4l5TEU49eplo5bw==
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
/tmp/sm-dp-sessions-BRP
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
/tmp/sm-dp-sessions-NIST
|
|
||||||
Reference in New Issue
Block a user