17 Commits

Author SHA1 Message Date
Vadim Yanitskiy
115b517c6a esim/saip: raise an exception properly
Change-Id: Ia3749c02120fdc16e556214d0461cbeca032447b
2026-03-20 14:32:59 -07:00
Vadim Yanitskiy
99aef1fecf cdma_ruim: fix inaccurate comment for EF_AD
Change-Id: I71ea27fd30e44685ff35f49843072ca392995973
2026-03-20 14:32:59 -07:00
Vadim Yanitskiy
caddd1c7a0 ts_31_102: EF_5G_PROSE_UIR: fix copy-pasted inner class name
Change-Id: I460e5ad70f35026d0d794271a4aef17323c14dfb
2026-03-20 14:32:59 -07:00
Vadim Yanitskiy
11a7a7e3b1 ts_31_102: fix description for EF_5GS3GPPLOCI
Change-Id: I9cf3adfce65090fedb3f0fd33c9b3d15a2c5fb8c
2026-03-20 14:32:59 -07:00
Vadim Yanitskiy
5138208ee6 ts_51_011: EF.EXT[6-7]: fix typo in desc
Change-Id: I93df1c9fd8a4d588ed7ed19ec2dc1d304412fc3d
2026-03-20 14:32:59 -07:00
Vadim Yanitskiy
5b2fabde62 utils: DataObjectCollection.encode(): fix TypeError
`members_by_name` is a plain dictionary.  Calling it with `()` raises:

  TypeError: 'dict' object is not callable

Change-Id: I7e0c09aa7303f1506fe3a025fdc3779919dd0e6c
2026-03-20 14:32:59 -07:00
Vadim Yanitskiy
24127e985a utils: dec_plmn(): remove redundant call
Change-Id: Ic95c3992ed57eb8fee952ec2dc7f092dd7689579
2026-03-20 14:32:59 -07:00
Vadim Yanitskiy
09ae327f8b ota: OtaAlgo{Crypt,Auth}: fix algo_auth vs algo_crypt
* OtaAlgoCrypt.from_keyset() searches by `otak.algo_crypt`
  but the error message prints `otak.algo_auth`.  Should be
  `otak.algo_crypt` instead.

* OtaAlgoAuth.__init__() checks `algo_auth` but the error message
  prints `algo_crypt`.  Should be `otak.algo_auth` instead.

Change-Id: Ia636fffaeadc68e3f6d5b65d477e753834c95895
2026-03-20 14:32:59 -07:00
Vadim Yanitskiy
d32bce19f6 sms: fix flags_construct in SMS_DELIVER
* field `tp_rp` appears at bit positions 7 and 5
** bit 7 should be `tp_rp` (Reply Path)
** bit 5 should be `tp_sri` (Status Report Indication)
* field `tp_lp` is completely missing
** should be at bit position 3

Change-Id: I0274849f0fa07281b5e050af429ffda7d249f9e8
2026-03-20 14:32:59 -07:00
Vadim Yanitskiy
83bfdc0d3b ara_m: fix undefined variable used in a format-string
Change-Id: I310a5d461bae2b5e4d8e07097000b079c23aa0f6
2026-03-20 14:32:59 -07:00
Vadim Yanitskiy
14ec52a06c ara_m: fix exceptions not being raised properly
Exceptions are meant to be thrown/raised, not returned.

Change-Id: Id799c264447e22887edcd2dc7eb991cf0af1bbfc
2026-03-20 14:32:59 -07:00
Vadim Yanitskiy
209d13e233 global_platform: fix docstring for Scp03SessionKeys._get_icv()
Change-Id: I8983bc27f581295544360ba8b4ae1d28b3ea850f
2026-03-20 14:32:59 -07:00
Vadim Yanitskiy
3b50e64c8b global_platform: fix s/GET/STORE/ DATA in docs
Both `do_store_data` and `store_data` have identical docstrings that
incorrectly describe the command as GET DATA.  Should be "STORE DATA".
Take a chance to fix missing space between `v2.3` and `Section`.

Change-Id: I33fc80ab8ca50fadc38217b0005eec6169c8e34e
2026-03-20 14:32:59 -07:00
Vadim Yanitskiy
b76cc80ea1 global_platform: fix store_data() returning last chunk only
The loop builds up `response` across multiple STORE DATA blocks,
but the function returns only `data` - the response from the
*last* block.  It should return the accumulated response instead.

Change-Id: I3e15c8004d1e366e8c3896e559656622f48bb1a2
2026-03-20 14:32:59 -07:00
Vadim Yanitskiy
3b87ba3cba global_platform: fix typo in ApplicationTemplate
The keyword argument should be `nested=`.  As written `ApplicationAID`
is silently ignored - `ApplicationTemplate` will not descend into its
nested TLVs.

Change-Id: If45dbb0c9b09fe53560d109957ce339267a9f2b0
2026-03-20 14:32:59 -07:00
Vadim Yanitskiy
ea1d5af383 global_platform: fix typo in SupportedTlsCipherSuitesForScp81
The attribute name is misspelled.  The BER-TLV infrastructure looks
for `_construct`; this typo means `SupportedTlsCipherSuitesForScp81`
will never decode its content.

Change-Id: I0f637951b0eeb7eca2a8b543baa737f216a935ed
2026-03-20 14:32:59 -07:00
Philipp Maier
0634f77308 esim/http_json_api: allow URL rewriting
The URL used when HTTP requests are performed is defined statically
with the url_prefix passed to the constructor of JsonHttpApiClient
together with the path property in JsonHttpApiFunction.

For applications that require dynamic URLs there is no way to rewrite
the URL. Let's add a mechanism that allows API users to apply custom
URL reqriting rules by adding a rewrite_url method to
JsonHttpApiFunction. API users may then overload this method with a
custom implementation as needed.

Related: SYS#7918
Change-Id: Id2713a867079cc140517fe312189e5e2162608a5
2026-03-17 11:17:12 +01:00
13 changed files with 69 additions and 877 deletions

View File

@@ -1,842 +0,0 @@
Guide: Managing GP Keys
=======================
Most of todays smartcards follow the GlobalPlatform Card Specification and the included Security Domain model.
UICCs and eUCCCs are no exception here.
The Security Domain acts as an on-card representative of a card authority or administrator. It is used to perform tasks
like the installation of applications or the provisioning and rotation of secure channel keys. It also acts as a secure
key storage and offers all kinds of cryptographic services to applications that are installed under a specific
Security Domain (see also GlobalPlatform Card Specification, section 7).
In this tutorial, we will show how to work with the key material (keysets) stored inside a Security Domain and how to
rotate (replace) existing keys. We will also show how to provision new keys.
.. warning:: Making changes to keysets requires extreme caution as misconfigured keysets may lock you out permanently.
It also strongly recommended to maintain at least one backup keyset that you can use as fallback in case
the primary keyset becomes unusable for some reason.
Selecting a Security Domain
~~~~~~~~~~~~~~~~~~~~~~~~~~~
A typical smartcard, such as an UICC will have one primary Security Domain, called the Issuer Security Domain (ISD).
When working with those cards, the ISD will show up in the UICC filesystem tree as `ADF.ISD` and can be selected like
any other file.
::
pySIM-shell (00:MF)> select ADF.ISD
{
"application_id": "a000000003000000",
"proprietary_data": {
"maximum_length_of_data_field_in_command_message": 255
}
}
When working with eUICCs, multiple Security Domains are involved. The model is slightly different from the classic
model with one primary ISD. In the case of eUICCs, an ISD-R and an ISD-P exists.
The ISD-R (Issuer Security Domain - Root) is indeed the primary ISD. Its purpose is to handle the installation of new
profiles and to manage the already installed profiles. The ISD-R shows up as a `ADF.ISD-R` and can be selected normally
(see above) The key material that allows access to the ISD-R is usually only known to the eUICC manufacturer.
The ISD-P (Issuer Security Domain - Profile) is the primary ISD of the currently enabled profile. The ISD-P is
comparable to the ISD we find on a UICC. The key material for the ISD-P should be known known to the ISP, which
is the owner of the installed profile.
Since the AID of the ISD-P is allocated during the profile installation and different for each profile, it is not known
by pySim-shell. This means there will no `ADF.ISD-P` file show up in the file system, but we can simply select the
ISD-R, request the AID of the ISD-P and switch over to that ISD-P using a raw APDU:
``00a4040410`` + ``a0000005591010ffffffff8900001000`` + ``00``
::
pySIM-shell (00:MF)> select ADF.ISD-R
{
"application_id": "a0000005591010ffffffff8900000100",
"proprietary_data": {
"maximum_length_of_data_field_in_command_message": 255
},
"isdr_proprietary_application_template": {
"supported_version_number": "020300"
}
}
pySIM-shell (00:MF/ADF.ISD-R)> get_profiles_info
{
"profile_info_seq": {
"profile_info": {
"iccid": "8949449999999990023",
"isdp_aid": "a0000005591010ffffffff8900001000",
"profile_state": "enabled",
"service_provider_name": "OsmocomSPN",
"profile_name": "TS48V1-A-UNIQUE",
"profile_class": "operational"
}
}
}
pySIM-shell (00:MF/ADF.ISD-R)> apdu 00a4040410a0000005591010ffffffff890000100000
SW: 9000, RESP: 6f188410a0000005591010ffffffff8900001000a5049f6501ff
pySIM-shell (00:MF/ADF.ISD-R)>
After that, the prompt will still show the ADF.ISD-R, but we are actually in ADF.ISD-P and the standard GlobalPlatform
operations like `establish_scpXX`, `get_data`, and `put_key` should work. The same workaround can also be applied to any
Supplementary Security Domain as well, provided that the AID is known to the user.
Establishing a secure channel
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Before we can make changes to the keysets in the currently selected Security Domain we must first establish a secure channel
with that Security Domain. The secure channel protocols commonly used for this are `SCP02` (see also GlobalPlatform Card
Specification, section E.1.1) and `SCP03` (see also GlobalPlatform Card Specification Amendment D). `SCP02` is slightly
older and commonly used on UICCs. The more modern `SCP03` is commonly used on eUICCs. The main difference between the
two is that `SCP02` uses 3DES while `SCP03` is based on AES.
.. warning:: Secure channel protocols like `SCP02` and `SCP03` may manage an error counter to count failed login
attempts. This means attempting to establish a secure channel with a wrong keyset multiple times may lock
you out permanently. Double check the applied keyset before attempting to establish a secure channel.
Example: `SCP02`
----------------
In the following example, we assume that we want to establish a secure channel with the ISD of a `sysmoUSIM-SJA5` UICC.
Along with the card we have received the following keyset:
+---------+----------------------------------+
| Keyname | Keyvalue |
+=========+==================================+
| ENC/KIC | F09C43EE1A0391665CC9F05AF4E0BD10 |
+---------+----------------------------------+
| MAC/KID | 01981F4A20999F62AF99988007BAF6CA |
+---------+----------------------------------+
| DEK/KIK | 8F8AEE5CDCC5D361368BC45673D99195 |
+---------+----------------------------------+
This keyset is tied to the key version number KVN 122 and is configured as a DES keyset. We can use this keyset to
establish a secure channel using the SCP02 Secure Channel Protocol.
::
pySIM-shell (00:MF/ADF.ISD)> establish_scp02 --key-enc F09C43EE1A0391665CC9F05AF4E0BD10 --key-mac 01981F4A20999F62AF99988007BAF6CA --key-dek 8F8AEE5CDCC5D361368BC45673D99195 --security-level 3
Successfully established a SCP02[03] secure channel
Example: `SCP03`
----------------
The establishment of a secure channel via SCP03 works just the same. In the following example we will establish a
secure channel to the ISD-R of an eUICC. The SCP03 keyset we use is tied to KVN 50 and looks like this:
+---------+------------------------------------------------------------------+
| Keyname | Keyvalue |
+=========+==================================================================+
| ENC/KIC | 620ff456b0c0328b68dc0d7d5eb24e07dd749aa86c9ff1836a7263e1d8896510 |
+---------+------------------------------------------------------------------+
| MAC/KID | b38116a2c85f2c8f46bbdc0081d6e8a04b0a58087d0ce5ee0ccc4c945e4aeda6 |
+---------+------------------------------------------------------------------+
| DEK/KIK | d409486cbcb8092a8592ee46d8668dfa97bea5eb7ce9c2b5a3f3bb1db358a153 |
+---------+------------------------------------------------------------------+
We assume that ADF.ISD-R is already selected. We may now establish the SCP03 secure channel:
::
pySIM-shell (00:MF/ADF.ISD-R)> establish_scp03 --key-enc 620ff456b0c0328b68dc0d7d5eb24e07dd749aa86c9ff1836a7263e1d8896510 --key-mac b38116a2c85f2c8f46bbdc0081d6e8a04b0a58087d0ce5ee0ccc4c945e4aeda6 --key-dek d409486cbcb8092a8592ee46d8668dfa97bea5eb7ce9c2b5a3f3bb1db358a153 --key-ver 50 --security-level 3
Successfully established a SCP03[03] secure channel
Understanding Keysets
~~~~~~~~~~~~~~~~~~~~~
Before making any changes to keysets, it is recommended to check the status of the currently installed keysets. To do
so, we use the `get_data` command to retrieve the `key_information`. We cannot read back the key values themselves, but
we get a summary of the installed keys together with their KVN numbers, IDs, algorithm and key length values.
Example: `key_information` from a `sysmoISIM-SJA5`:
::
pySIM-shell (SCP02[03]:00:MF/ADF.ISD)> get_data key_information
{
"key_information": [
{
"key_information_data": {
"key_identifier": 1,
"key_version_number": 112,
"key_types": [
{
"type": "des",
"length": 16
}
]
}
},
{
"key_information_data": {
"key_identifier": 2,
"key_version_number": 112,
"key_types": [
{
"type": "des",
"length": 16
}
]
}
},
{
"key_information_data": {
"key_identifier": 3,
"key_version_number": 112,
"key_types": [
{
"type": "des",
"length": 16
}
]
}
},
{
"key_information_data": {
"key_identifier": 1,
"key_version_number": 1,
"key_types": [
{
"type": "des",
"length": 16
}
]
}
},
{
"key_information_data": {
"key_identifier": 2,
"key_version_number": 1,
"key_types": [
{
"type": "des",
"length": 16
}
]
}
},
{
"key_information_data": {
"key_identifier": 3,
"key_version_number": 1,
"key_types": [
{
"type": "des",
"length": 16
}
]
}
},
{
"key_information_data": {
"key_identifier": 1,
"key_version_number": 2,
"key_types": [
{
"type": "des",
"length": 16
}
]
}
},
{
"key_information_data": {
"key_identifier": 2,
"key_version_number": 2,
"key_types": [
{
"type": "des",
"length": 16
}
]
}
},
{
"key_information_data": {
"key_identifier": 3,
"key_version_number": 2,
"key_types": [
{
"type": "des",
"length": 16
}
]
}
},
{
"key_information_data": {
"key_identifier": 1,
"key_version_number": 47,
"key_types": [
{
"type": "des",
"length": 16
}
]
}
},
{
"key_information_data": {
"key_identifier": 2,
"key_version_number": 47,
"key_types": [
{
"type": "des",
"length": 16
}
]
}
},
{
"key_information_data": {
"key_identifier": 3,
"key_version_number": 47,
"key_types": [
{
"type": "des",
"length": 16
}
]
}
}
]
}
Example: `key_information` from a `sysmoEUICC1-C2T`:
::
pySIM-shell (SCP03[03]:00:MF/ADF.ISD-R)> get_data key_information
{
"key_information": [
{
"key_information_data": {
"key_identifier": 3,
"key_version_number": 50,
"key_types": [
{
"type": "aes",
"length": 32
}
]
}
},
{
"key_information_data": {
"key_identifier": 2,
"key_version_number": 50,
"key_types": [
{
"type": "aes",
"length": 32
}
]
}
},
{
"key_information_data": {
"key_identifier": 1,
"key_version_number": 50,
"key_types": [
{
"type": "aes",
"length": 32
}
]
}
},
{
"key_information_data": {
"key_identifier": 2,
"key_version_number": 64,
"key_types": [
{
"type": "aes",
"length": 16
}
]
}
},
{
"key_information_data": {
"key_identifier": 1,
"key_version_number": 64,
"key_types": [
{
"type": "tls_psk",
"length": 16
}
]
}
}
]
}
The output from those two examples above may seem lengthy, but in order to move on and to provision own keys
successfully, it is important to understand each aspect of it.
Key Version Number (KVN)
------------------------
Each key is associated with a Key Version Number (KVN). Multiple keys that share the same KVN belong to the same
keyset. In the first example above we can see that four keysets with KVN numbers 112, 1, 2 and 47 are provisioned.
In the second example we see two keysets. One with KVN 50 and one with KVN 64.
The term "Key Version Number" is misleading as this number is not really a version number. It's actually a unique
identifier for a specific keyset that also defines with which Secure Channel Protocol a key can be used. This means
that the KVN is not just an arbitrary number. The following (incomplete) table gives a hint which KVN numbers may be
used with which Secure Channel Protocol.
+-----------+-------------------------------------------------------+
| KVN range | Secure Channel Protocol |
+===========+=======================================================+
| 1-15 | reserved for `SCP80` (OTA SMS) |
+-----------+-------------------------------------------------------+
| 17 | reserved for DAP specified in ETSI TS 102 226 |
+-----------+-------------------------------------------------------+
| 32-47 | reserved for `SCP02` |
+-----------+-------------------------------------------------------+
| 48-63 | reserved for `SCP03` |
+-----------+-------------------------------------------------------+
| 112 | Token key (RSA public or DES, also used with `SCP02`) |
+-----------+-------------------------------------------------------+
| 113 | Receipt key (DES) |
+-----------+-------------------------------------------------------+
| 115 | DAP verifiation key (RS public or DES) |
+-----------+-------------------------------------------------------+
| 116 | reserved for CASD |
+-----------+-------------------------------------------------------+
| 117 | 16-byte DES key for Ciphered Load File Data Block |
+-----------+-------------------------------------------------------+
| 129-143 | reserved for `SCP81` |
+-----------+-------------------------------------------------------+
| 255 | reserved for ISD with SCP02 without SCP80 support |
+-----------+-------------------------------------------------------+
With that we can now understand that in the first example, the first and the last keyset is intended to be used with
`SCP02` and that the second and the third keyset is intended to be used with `SCP80` (OTA SMS). In the second example we
can see that the first keyset is intended to be used with `SCP03`, wheres the second should be usable with `SCP81`.
Key Identifier
--------------
Each keyset consists of a number of keys, where each key has a different Key Identifier. The Key Identifier is usually
an incrementing number that starts counting at 1. The Key Identifier is used to distinguish the keys within the keyset.
The exact number of keys and their attributes depends on the secure channel protocol for which the keyset is intended
for. Each secure channel protocol may have its specific requirements on how many keys of which which type, length or
Key Identifier have to be present.
However, almost all of the classic secure channel protocols (including `SCP02`, `SCP03` and `SCP81`) make use of the
following three-key scheme:
+----------------+---------+---------------------------------------+
| Key Identifier | Keyname | Purpose |
+================+=========+=======================================+
| 1 | ENC/KIC | encryption/decryption |
+----------------+---------+---------------------------------------+
| 2 | MAC/KID | cryptographic checksumming/signing |
+----------------+---------+---------------------------------------+
| 3 | DEK/KIK | encryption/decryption of key material |
+----------------+---------+---------------------------------------+
In this case, all three keys share the same length and are used with the same algorithm. The key length is often used
to implicitly select sub-types of an algorithm. (e.g. a 16 byte key of type `aes` is associated with `AES128`, where a 32
byte key would be associated with `AES256`).
That different schemes are possible shows the second example. The `SCP80` keyset from the second example uses a scheme
that works with two keys:
+----------------+---------+---------------------------------------+
| Key Identifier | Keyname | Purpose |
+================+=========+=======================================+
| 1 | TLS-PSK | pre-shared key used for TLS |
+----------------+---------+---------------------------------------+
| 2 | DEK/KIK | encryption/decryption of key material |
+----------------+---------+---------------------------------------+
It should also be noted that the order in which keysets and keys appear is an implementation detail of the UICC/eUICC
O/S. The order has no influence on how a keyset is interpreted. Only the Key Version Number (KVN) and the Key Identifier
matter.
Rotating a keyset
~~~~~~~~~~~~~~~~~
Rotating keys is one of the most basic tasks one might want to perform on an UICC/eUICC before using it productively. In
the following example we will illustrate how key rotation can be done. When rotating keys, only the key itself may
change. For example it is not possible to change the key length or the algorithm used (see also GlobalPlatform Card
Specification, section 11.8.2.3.3). Any key of the current Security Domain can be rotated, this also includes the key
that was used to establish the secure channel.
In the following example we assume that the Security Domain is selected and a secure channel is already established. We
intend to rotate the keyset with KVN 112. Since this keyset uses triple DES keys with a key length of 16, we must
replace it with a keyset with keys of the same nature.
The new keyset shall look like this:
+----------------+---------+----------------------------------+
| Key Identifier | Keyname | Keyvalue |
+================+=========+==================================+
| 1 | ENC/KIC | 542C37A6043679F2F9F71116418B1CD5 |
+----------------+---------+----------------------------------+
| 2 | MAC/KID | 34F11BAC8E5390B57F4E601372339E3C |
+----------------+---------+----------------------------------+
| 3 | DEK/KIK | 5524F4BECFE96FB63FC29D6BAAC6058B |
+----------------+---------+----------------------------------+
When passing the keys to the `put_key` commandline, we set the Key Identifier of the first key using the `--key-id`
parameter. This Key Identifier will be valid for the first key (KIC) we pass. For all consecutive keys, the Key
Identifier will be incremented automatically (see also GlobalPlatform Card Specification, section 11.8.2.2). To Ensure
that the new KIC, KID and KIK keys get the correct Key Identifiers, it is crucial to maintain order when passing the
keys in the `--key-data` arguments. It is also important that each `--key-data` argument is preceded by a `--key-type`
argument that sets the algorithm correctly (`des` in this case).
Finally we have to target the keyset we want to rotate by its KVN. The `--old-key-version-nr` argument is set to 112
as this is identifies the keyset we want to rotate. The `--key-version-nr` is also set to 112 as we do not want to the
KVN to be changed in this example. Changing the KVN while rotating a keyset is possible. In case the KVN has to change
for some reason, the new KVN must be selected carefully to keep the key usable with the associated Secure Channel
Protocol.
The commandline that matches the keyset we had laid out above looks like this:
::
pySIM-shell (SCP02[03]:00:MF/ADF.ISD)> put_key --key-id 1 --key-type des --key-data 542C37A6043679F2F9F71116418B1CD5 --key-type des --key-data 34F11BAC8E5390B57F4E601372339E3C --key-type des --key-data 5524F4BECFE96FB63FC29D6BAAC6058B --old-key-version-nr 112 --key-version-nr 112
After executing this put_key commandline, the keyset identified by KVN 122 is equipped with new keys. We can use
`get_data key_information` to inspect the currently installed keysets. The output should appear unchanged as
we only swapped out the keys. All other parameters, identifiers etc. should remain constant.
.. warning:: It is technically possible to rotate a keyset in a `non atomic` way using one `put_key` commandline for
each key. However, in case the targeted keyset is the one used to establish the current secure channel,
this method should not be used since, depending on the UICC/eUICC model, half-written key material may
interrupt the current secure channel.
Removing a keyset
~~~~~~~~~~~~~~~~~
In some cases it is necessary to remove a keyset entirely. This can be done with the `delete_key` command. Here it is
important to understand that `delete_key` only removes one specific key from a specific keyset. This means that you
need to run a separate `delete_key` command for each key inside a keyset.
In the following example we assume that the Security Domain is selected and a secure channel is already established. We
intend to remove the keyset with KVN 112. This keyset consists of three keys.
::
pySIM-shell (SCP02[03]:00:MF/ADF.ISD)> delete_key --key-ver 112 --key-id 1
pySIM-shell (SCP02[03]:00:MF/ADF.ISD)> delete_key --key-ver 112 --key-id 2
pySIM-shell (SCP02[03]:00:MF/ADF.ISD)> delete_key --key-ver 112 --key-id 3
To verify that the keyset has been deleted properly, we can use the `get_data key_information` command to inspect the
current status of the installed keysets. We should see that the key with KVN 112 is no longer present.
Adding a keyset
~~~~~~~~~~~~~~~
In the following we will discuss how to add an entirely new keyset. The procedure is almost identical with the key
rotation procedure we have already discussed and it is assumed that all details about the key rotation are understood.
In this section we will go into more detail and and illustrate how to provision new 3DES, `AES128` and `AES256` keysets.
It is important to keep in mind that storage space on smartcard is a precious resource. In many cases the amount of
keysets that a Security Domain can store is limited. In some situations you may be forced to sacrifice one of your
existing keysets in favor of a new keyset.
The main difference between key rotation and the adding of new keys is that we do not simply replace an existing key.
Instead an entirely new key is programmed into the Security Domain. Therefore the `put_key` commandline will have no
`--old-key-version-nr` parameter. From the commandline perspective, this is already the only visible difference from a
commandline that simply rotates a keyset. Since we are writing an entirely new keyset, we are free to chose the
algorithm and the key length within the parameter range permitted by the targeted secure channel protocol. Otherwise
the same rules apply.
For reference, it should be mentioned that it is also possible to add or rotate keyset using multiple `put_key`
commandlines. In this case one `put_key` commandline for each key is used. Each commandline will specify `--key-id` and
`--key-version-nr` and one `--key-type` and `--key-data` tuple. However, when rotating or adding a keyset step-by-step,
the whole process happens in a `non-atomic` way, which is less reliable. Therefore we will favor the `atomic method`
In the following examples we assume that the Security Domain is selected and a secure channel is already established.
Example: `3DES` key for `SCP02`
-------------------------------
Let's assume we want to provision a new 3DES keyset that we can use for SCP02. The keyset shall look like this:
+----------------+---------+----------------------------------+
| Key Identifier | Keyname | Keyvalue |
+================+=========+==================================+
| 1 | ENC/KIC | 542C37A6043679F2F9F71116418B1CD5 |
+----------------+---------+----------------------------------+
| 2 | MAC/KID | 34F11BAC8E5390B57F4E601372339E3C |
+----------------+---------+----------------------------------+
| 3 | DEK/KIK | 5524F4BECFE96FB63FC29D6BAAC6058B |
+----------------+---------+----------------------------------+
The keyset shall be a associated with the KVN 46. We have made sure before that KVN 46 is still unused and that this
KVN number is actually suitable for SCP02 keys. As we are using 3DES, it is obvious that we have to pass 3 keys with 16
byte length.
To program the key, we may use the following commandline. As we can see, this commandline is almost the exact same as
the one from the key rotation example where we were rotating a 3DES key. The only difference is that we didn't specify
an old KVN number and that we have chosen a different KVN.
::
pySIM-shell (SCP02[03]:00:MF/ADF.ISD)> put_key --key-id 1 --key-type des --key-data 542C37A6043679F2F9F71116418B1CD5 --key-type des --key-data 34F11BAC8E5390B57F4E601372339E3C --key-type des --key-data 5524F4BECFE96FB63FC29D6BAAC6058B --key-version-nr 46
In case of success, the keyset should appear in the `key_information` among the other keysets that are already present.
::
pySIM-shell (SCP02[03]:00:MF/ADF.ISD)> get_data key_information
{
"key_information": [
{
"key_information_data": {
"key_identifier": 1,
"key_version_number": 46,
"key_types": [
{
"type": "des",
"length": 16
}
]
}
},
{
"key_information_data": {
"key_identifier": 2,
"key_version_number": 46,
"key_types": [
{
"type": "des",
"length": 16
}
]
}
},
{
"key_information_data": {
"key_identifier": 3,
"key_version_number": 46,
"key_types": [
{
"type": "des",
"length": 16
}
]
}
},
...
]
}
Example: `AES128` key for `SCP80`
---------------------------------
In this example we intend to provision a new `AES128` keyset that we can use with SCP80 (OTA SMS). The keyset shall look
like this:
+----------------+---------+----------------------------------+
| Key Identifier | Keyname | Keyvalue |
+================+=========+==================================+
| 1 | ENC/KIC | 542C37A6043679F2F9F71116418B1CD5 |
+----------------+---------+----------------------------------+
| 2 | MAC/KID | 34F11BAC8E5390B57F4E601372339E3C |
+----------------+---------+----------------------------------+
| 3 | DEK/KIK | 5524F4BECFE96FB63FC29D6BAAC6058B |
+----------------+---------+----------------------------------+
In addition to that, we want to associate this key with KVN 3. We have inspected the currently installed keysets before
and made sure that KVN 3 is still unused. We are also aware that for SCP80 we may only use KVN values from 1 to 15.
For `AES128`, we specify the algorithm using the `--key-type aes` parameter. The selection between `AES128` and `AES256` is
done implicitly using the key length. Since we want to use `AES128` in this case, all three keys have a length of 16 byte.
::
pySIM-shell (SCP02[03]:00:MF/ADF.ISD)> put_key --key-id 1 --key-type aes --key-data 542C37A6043679F2F9F71116418B1CD5 --key-type aes --key-data 34F11BAC8E5390B57F4E601372339E3C --key-type aes --key-data 5524F4BECFE96FB63FC29D6BAAC6058B --key-version-nr 3
In case of success, the keyset should appear in the `key_information` among the other keysets that are already present.
::
pySIM-shell (SCP02[03]:00:MF/ADF.ISD)> get_data key_information
{
"key_information": [
{
"key_information_data": {
"key_identifier": 1,
"key_version_number": 3,
"key_types": [
{
"type": "aes",
"length": 16
}
]
}
},
{
"key_information_data": {
"key_identifier": 2,
"key_version_number": 3,
"key_types": [
{
"type": "aes",
"length": 16
}
]
}
},
{
"key_information_data": {
"key_identifier": 3,
"key_version_number": 3,
"key_types": [
{
"type": "aes",
"length": 16
}
]
}
},
...
]
}
Example: `AES256` key for `SCP03`
---------------------------------
Let's assume we want to provision a new `AES256` keyset that we can use for SCP03. The keyset shall look like this:
+----------------+---------+------------------------------------------------------------------+
| Key Identifier | Keyname | Keyvalue |
+================+=========+==================================================================+
| 1 | ENC/KIC | 542C37A6043679F2F9F71116418B1CD5542C37A6043679F2F9F71116418B1CD5 |
+----------------+---------+------------------------------------------------------------------+
| 2 | MAC/KID | 34F11BAC8E5390B57F4E601372339E3C34F11BAC8E5390B57F4E601372339E3C |
+----------------+---------+------------------------------------------------------------------+
| 3 | DEK/KIK | 5524F4BECFE96FB63FC29D6BAAC6058B5524F4BECFE96FB63FC29D6BAAC6058B |
+----------------+---------+------------------------------------------------------------------+
In addition to that, we assume that we want to associate this key with KVN 51. This KVN number falls in the range of
48 - 63 and is therefore suitable for a key that shall be usable with SCP03. We also made sure before that KVN 51 is
still unused.
With that we can go ahead and make up the following commandline:
::
pySIM-shell (SCP02[03]:00:MF/ADF.ISD)> put_key --key-id 1 --key-type aes --key-data 542C37A6043679F2F9F71116418B1CD5542C37A6043679F2F9F71116418B1CD5 --key-type aes --key-data 34F11BAC8E5390B57F4E601372339E3C34F11BAC8E5390B57F4E601372339E3C --key-type aes --key-data 5524F4BECFE96FB63FC29D6BAAC6058B5524F4BECFE96FB63FC29D6BAAC6058B --key-version-nr 51
In case of success, we should see the keyset in the `key_information`
::
pySIM-shell (SCP02[03]:00:MF/ADF.ISD)> get_data key_information
{
"key_information": [
{
"key_information_data": {
"key_identifier": 1,
"key_version_number": 51,
"key_types": [
{
"type": "aes",
"length": 32
}
]
}
},
{
"key_information_data": {
"key_identifier": 2,
"key_version_number": 51,
"key_types": [
{
"type": "aes",
"length": 32
}
]
}
},
{
"key_information_data": {
"key_identifier": 3,
"key_version_number": 51,
"key_types": [
{
"type": "aes",
"length": 32
}
]
}
},
...
]
}
Example: `AES128` key for `SCP81`
---------------------------------
In this example we will show how to provision a new `AES128` keyset for `SCP81`. We will provision this keyset under
KVN 64. The keyset we intend to apply shall look like this:
+----------------+---------+----------------------------------+
| Key Identifier | Keyname | Keyvalue |
+================+=========+==================================+
| 1 | TLS-PSK | 000102030405060708090a0b0c0d0e0f |
+----------------+---------+----------------------------------+
| 2 | DEK/KIK | 000102030405060708090a0b0c0d0e0f |
+----------------+---------+----------------------------------+
With that we can put together the following command line:
::
put_key --key-id 1 --key-type tls_psk --key-data 000102030405060708090a0b0c0d0e0f --key-type aes --key-data 000102030405060708090a0b0c0d0e0f --key-version-nr 64
In case of success, the keyset should appear in the `key_information` as follows:
::
pySIM-shell (SCP03[03]:00:MF/ADF.ISD-R)> get_data key_information
{
"key_information": [
...,
{
"key_information_data": {
"key_identifier": 2,
"key_version_number": 64,
"key_types": [
{
"type": "aes",
"length": 16
}
]
}
},
{
"key_information_data": {
"key_identifier": 1,
"key_version_number": 64,
"key_types": [
{
"type": "tls_psk",
"length": 16
}
]
}
}
]
}

View File

@@ -68,7 +68,7 @@ Usage Examples
suci-tutorial suci-tutorial
cap-tutorial cap-tutorial
put_key-tutorial
Advanced Topics Advanced Topics
--------------- ---------------

View File

@@ -72,10 +72,10 @@ class ApduArDO(BER_TLV_IE, tag=0xd0):
if do[0] == 0x01: if do[0] == 0x01:
self.decoded = {'generic_access_rule': 'always'} self.decoded = {'generic_access_rule': 'always'}
return self.decoded return self.decoded
return ValueError('Invalid 1-byte generic APDU access rule') raise ValueError('Invalid 1-byte generic APDU access rule')
else: else:
if len(do) % 8: if len(do) % 8:
return ValueError('Invalid non-modulo-8 length of APDU filter: %d' % len(do)) raise ValueError('Invalid non-modulo-8 length of APDU filter: %d' % len(do))
self.decoded = {'apdu_filter': []} self.decoded = {'apdu_filter': []}
offset = 0 offset = 0
while offset < len(do): while offset < len(do):
@@ -90,19 +90,19 @@ class ApduArDO(BER_TLV_IE, tag=0xd0):
return b'\x00' return b'\x00'
if self.decoded['generic_access_rule'] == 'always': if self.decoded['generic_access_rule'] == 'always':
return b'\x01' return b'\x01'
return ValueError('Invalid 1-byte generic APDU access rule') raise ValueError('Invalid 1-byte generic APDU access rule')
else: else:
if not 'apdu_filter' in self.decoded: if not 'apdu_filter' in self.decoded:
return ValueError('Invalid APDU AR DO') raise ValueError('Invalid APDU AR DO')
filters = self.decoded['apdu_filter'] filters = self.decoded['apdu_filter']
res = b'' res = b''
for f in filters: for f in filters:
if not 'header' in f or not 'mask' in f: if not 'header' in f or not 'mask' in f:
return ValueError('APDU filter must contain header and mask') raise ValueError('APDU filter must contain header and mask')
header_b = h2b(f['header']) header_b = h2b(f['header'])
mask_b = h2b(f['mask']) mask_b = h2b(f['mask'])
if len(header_b) != 4 or len(mask_b) != 4: if len(header_b) != 4 or len(mask_b) != 4:
return ValueError('APDU filter header and mask must each be 4 bytes') raise ValueError('APDU filter header and mask must each be 4 bytes')
res += header_b + mask_b res += header_b + mask_b
return res return res
@@ -269,7 +269,7 @@ class ADF_ARAM(CardADF):
cmd_do_enc = cmd_do.to_ie() cmd_do_enc = cmd_do.to_ie()
cmd_do_len = len(cmd_do_enc) cmd_do_len = len(cmd_do_enc)
if cmd_do_len > 255: if cmd_do_len > 255:
return ValueError('DO > 255 bytes not supported yet') raise ValueError('DO > 255 bytes not supported yet')
else: else:
cmd_do_enc = b'' cmd_do_enc = b''
cmd_do_len = 0 cmd_do_len = 0
@@ -361,7 +361,7 @@ class ADF_ARAM(CardADF):
ar_do_content += [{'apdu_ar_do': {'generic_access_rule': 'always'}}] ar_do_content += [{'apdu_ar_do': {'generic_access_rule': 'always'}}]
elif opts.apdu_filter: elif opts.apdu_filter:
if len(opts.apdu_filter) % 16: if len(opts.apdu_filter) % 16:
return ValueError('Invalid non-modulo-16 length of APDU filter: %d' % len(do)) raise ValueError(f'Invalid non-modulo-16 length of APDU filter: {len(opts.apdu_filter)}')
offset = 0 offset = 0
apdu_filter = [] apdu_filter = []
while offset < len(opts.apdu_filter): while offset < len(opts.apdu_filter):

View File

@@ -131,7 +131,7 @@ class EF_AD(TransparentEF):
desc='Administrative Data', size=(3, None), **kwargs): desc='Administrative Data', size=(3, None), **kwargs):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs)
self._construct = Struct( self._construct = Struct(
# Byte 1: Display Condition # Byte 1: MS operation mode
'ms_operation_mode'/Enum(Byte, self.OP_MODE), 'ms_operation_mode'/Enum(Byte, self.OP_MODE),
# Bytes 2-3: Additional information # Bytes 2-3: Additional information
'additional_info'/Bytes(2), 'additional_info'/Bytes(2),

View File

@@ -19,7 +19,7 @@ import abc
import requests import requests
import logging import logging
import json import json
from typing import Optional from typing import Optional, Tuple
import base64 import base64
from twisted.web.server import Request from twisted.web.server import Request
@@ -180,7 +180,7 @@ class JsonHttpApiFunction(abc.ABC):
# receives from the a requesting client. The same applies vice versa to class variables that have an "output_" # receives from the a requesting client. The same applies vice versa to class variables that have an "output_"
# prefix. # prefix.
# path of the API function (e.g. '/gsma/rsp2/es2plus/confirmOrder') # path of the API function (e.g. '/gsma/rsp2/es2plus/confirmOrder', see also method rewrite_url).
path = None path = None
# dictionary of input parameters. key is parameter name, value is ApiParam class # dictionary of input parameters. key is parameter name, value is ApiParam class
@@ -336,6 +336,22 @@ class JsonHttpApiFunction(abc.ABC):
output[p] = p_class.decode(v) output[p] = p_class.decode(v)
return output return output
def rewrite_url(self, data: dict, url: str) -> Tuple[dict, str]:
"""
Rewrite a static URL using information passed in the data dict. This method may be overloaded by a derived
class to allow fully dynamic URLs. The input parameters required for the URL rewriting may be passed using
data parameter. In case those parameters are additional parameters that are not intended to be passed to
the encode_client method later, they must be removed explcitly.
Args:
data: (see JsonHttpApiClient and JsonHttpApiServer)
url: statically generated URL string (see comment in JsonHttpApiClient)
"""
# This implementation is a placeholder in which we do not perform any URL rewriting. We just pass through data
# and url unmodified.
return data, url
class JsonHttpApiClient(): class JsonHttpApiClient():
def __init__(self, api_func: JsonHttpApiFunction, url_prefix: str, func_req_id: Optional[str], def __init__(self, api_func: JsonHttpApiFunction, url_prefix: str, func_req_id: Optional[str],
session: requests.Session): session: requests.Session):
@@ -352,8 +368,16 @@ class JsonHttpApiClient():
self.session = session self.session = session
def call(self, data: dict, func_call_id: Optional[str] = None, timeout=10) -> Optional[dict]: def call(self, data: dict, func_call_id: Optional[str] = None, timeout=10) -> Optional[dict]:
"""Make an API call to the HTTP API endpoint represented by this object. Input data is passed in `data` as """
json-serializable dict. Output data is returned as json-deserialized dict.""" Make an API call to the HTTP API endpoint represented by this object. Input data is passed in `data` as
json-serializable fields. `data` may also contain additional parameters required for URL rewriting (see
rewrite_url in class JsonHttpApiFunction). Output data is returned as json-deserialized dict.
Args:
data: Input data required to perform the request.
func_call_id: Function Call Identifier, if present a header field is generated automatically.
timeout: Maximum amount of time to wait for the request to complete.
"""
# In case a function caller ID is supplied, use it together with the stored function requestor ID to generate # In case a function caller ID is supplied, use it together with the stored function requestor ID to generate
# and prepend the header field according to SGP.22, section 6.5.1.1 and 6.5.1.3. (the presence of the header # and prepend the header field according to SGP.22, section 6.5.1.1 and 6.5.1.3. (the presence of the header
@@ -362,6 +386,11 @@ class JsonHttpApiClient():
data = {'header' : {'functionRequesterIdentifier': self.func_req_id, data = {'header' : {'functionRequesterIdentifier': self.func_req_id,
'functionCallIdentifier': func_call_id}} | data 'functionCallIdentifier': func_call_id}} | data
# The URL used for the HTTP request (see below) normally consists of the initially given url_prefix
# concatenated with the path defined by the JsonHttpApiFunction definition. This static URL path may be
# rewritten by rewrite_url method defined in the JsonHttpApiFunction.
data, url = self.api_func.rewrite_url(data, self.url_prefix + self.api_func.path)
# Encode the message (the presence of mandatory fields is checked during encoding) # Encode the message (the presence of mandatory fields is checked during encoding)
encoded = json.dumps(self.api_func.encode_client(data)) encoded = json.dumps(self.api_func.encode_client(data))
@@ -373,7 +402,6 @@ class JsonHttpApiClient():
req_headers.update(self.api_func.extra_http_req_headers) req_headers.update(self.api_func.extra_http_req_headers)
# Perform HTTP request # Perform HTTP request
url = self.url_prefix + self.api_func.path
logger.debug("HTTP REQ %s - hdr: %s '%s'" % (url, req_headers, encoded)) logger.debug("HTTP REQ %s - hdr: %s '%s'" % (url, req_headers, encoded))
response = self.session.request(self.api_func.http_method, url, data=encoded, headers=req_headers, timeout=timeout) response = self.session.request(self.api_func.http_method, url, data=encoded, headers=req_headers, timeout=timeout)
logger.debug("HTTP RSP-STS: [%u] hdr: %s" % (response.status_code, response.headers)) logger.debug("HTTP RSP-STS: [%u] hdr: %s" % (response.status_code, response.headers))

View File

@@ -441,7 +441,7 @@ class File:
elif k == 'fillFileContent': elif k == 'fillFileContent':
stream.write(v) stream.write(v)
else: else:
return ValueError("Unknown key '%s' in tuple list" % k) raise ValueError("Unknown key '%s' in tuple list" % k)
return stream.getvalue() return stream.getvalue()
def file_content_to_tuples(self, optimize:bool = False) -> List[Tuple]: def file_content_to_tuples(self, optimize:bool = False) -> List[Tuple]:

View File

@@ -276,7 +276,7 @@ class ListOfSupportedOptions(BER_TLV_IE, tag=0x81):
class SupportedKeysForScp03(BER_TLV_IE, tag=0x82): class SupportedKeysForScp03(BER_TLV_IE, tag=0x82):
_construct = FlagsEnum(Byte, aes128=0x01, aes192=0x02, aes256=0x04) _construct = FlagsEnum(Byte, aes128=0x01, aes192=0x02, aes256=0x04)
class SupportedTlsCipherSuitesForScp81(BER_TLV_IE, tag=0x83): class SupportedTlsCipherSuitesForScp81(BER_TLV_IE, tag=0x83):
_consuruct = GreedyRange(Int16ub) _construct = GreedyRange(Int16ub)
class ScpInformation(BER_TLV_IE, tag=0xa0, nested=[ScpType, ListOfSupportedOptions, SupportedKeysForScp03, class ScpInformation(BER_TLV_IE, tag=0xa0, nested=[ScpType, ListOfSupportedOptions, SupportedKeysForScp03,
SupportedTlsCipherSuitesForScp81]): SupportedTlsCipherSuitesForScp81]):
pass pass
@@ -319,7 +319,7 @@ class CurrentSecurityLevel(BER_TLV_IE, tag=0xd3):
# GlobalPlatform v2.3.1 Section 11.3.3.1.3 # GlobalPlatform v2.3.1 Section 11.3.3.1.3
class ApplicationAID(BER_TLV_IE, tag=0x4f): class ApplicationAID(BER_TLV_IE, tag=0x4f):
_construct = GreedyBytes _construct = GreedyBytes
class ApplicationTemplate(BER_TLV_IE, tag=0x61, ntested=[ApplicationAID]): class ApplicationTemplate(BER_TLV_IE, tag=0x61, nested=[ApplicationAID]):
pass pass
class ListOfApplications(BER_TLV_IE, tag=0x2f00, nested=[ApplicationTemplate]): class ListOfApplications(BER_TLV_IE, tag=0x2f00, nested=[ApplicationTemplate]):
pass pass
@@ -562,14 +562,14 @@ class ADF_SD(CardADF):
@cmd2.with_argparser(store_data_parser) @cmd2.with_argparser(store_data_parser)
def do_store_data(self, opts): def do_store_data(self, opts):
"""Perform the GlobalPlatform GET DATA command in order to store some card-specific data. """Perform the GlobalPlatform STORE DATA command in order to store some card-specific data.
See GlobalPlatform CardSpecification v2.3Section 11.11 for details.""" See GlobalPlatform CardSpecification v2.3 Section 11.11 for details."""
response_permitted = opts.response == 'may_be_returned' response_permitted = opts.response == 'may_be_returned'
self.store_data(h2b(opts.DATA), opts.data_structure, opts.encryption, response_permitted) self.store_data(h2b(opts.DATA), opts.data_structure, opts.encryption, response_permitted)
def store_data(self, data: bytes, structure:str = 'none', encryption:str = 'none', response_permitted: bool = False) -> bytes: def store_data(self, data: bytes, structure:str = 'none', encryption:str = 'none', response_permitted: bool = False) -> bytes:
"""Perform the GlobalPlatform GET DATA command in order to store some card-specific data. """Perform the GlobalPlatform STORE DATA command in order to store some card-specific data.
See GlobalPlatform CardSpecification v2.3Section 11.11 for details.""" See GlobalPlatform CardSpecification v2.3 Section 11.11 for details."""
max_cmd_len = self._cmd.lchan.scc.max_cmd_len max_cmd_len = self._cmd.lchan.scc.max_cmd_len
# Table 11-89 of GP Card Specification v2.3 # Table 11-89 of GP Card Specification v2.3
remainder = data remainder = data
@@ -585,7 +585,7 @@ class ADF_SD(CardADF):
data, _sw = self._cmd.lchan.scc.send_apdu_checksw(hdr + b2h(chunk) + "00") data, _sw = self._cmd.lchan.scc.send_apdu_checksw(hdr + b2h(chunk) + "00")
block_nr += 1 block_nr += 1
response += data response += data
return data return h2b(response)
put_key_parser = argparse.ArgumentParser() put_key_parser = argparse.ArgumentParser()
put_key_parser.add_argument('--old-key-version-nr', type=auto_uint8, default=0, help='Old Key Version Number') put_key_parser.add_argument('--old-key-version-nr', type=auto_uint8, default=0, help='Old Key Version Number')

View File

@@ -438,7 +438,7 @@ class Scp03SessionKeys:
"""Obtain the ICV value computed as described in 6.2.6. """Obtain the ICV value computed as described in 6.2.6.
This method has two modes: This method has two modes:
* is_response=False for computing the ICV for C-ENC. Will pre-increment the counter. * is_response=False for computing the ICV for C-ENC. Will pre-increment the counter.
* is_response=False for computing the ICV for R-DEC.""" * is_response=True for computing the ICV for R-DEC."""
if not is_response: if not is_response:
self.block_nr += 1 self.block_nr += 1
# The binary value of this number SHALL be left padded with zeroes to form a full block. # The binary value of this number SHALL be left padded with zeroes to form a full block.

View File

@@ -221,12 +221,12 @@ class OtaAlgoCrypt(OtaAlgo, abc.ABC):
for subc in cls.__subclasses__(): for subc in cls.__subclasses__():
if subc.enum_name == otak.algo_crypt: if subc.enum_name == otak.algo_crypt:
return subc(otak) return subc(otak)
raise ValueError('No implementation for crypt algorithm %s' % otak.algo_auth) raise ValueError('No implementation for crypt algorithm %s' % otak.algo_crypt)
class OtaAlgoAuth(OtaAlgo, abc.ABC): class OtaAlgoAuth(OtaAlgo, abc.ABC):
def __init__(self, otak: OtaKeyset): def __init__(self, otak: OtaKeyset):
if self.enum_name != otak.algo_auth: if self.enum_name != otak.algo_auth:
raise ValueError('Cannot use algorithm %s with key for %s' % (self.enum_name, otak.algo_crypt)) raise ValueError('Cannot use algorithm %s with key for %s' % (self.enum_name, otak.algo_auth))
super().__init__(otak) super().__init__(otak)
def sign(self, data:bytes) -> bytes: def sign(self, data:bytes) -> bytes:

View File

@@ -169,8 +169,14 @@ class SMS_TPDU(abc.ABC):
class SMS_DELIVER(SMS_TPDU): class SMS_DELIVER(SMS_TPDU):
"""Representation of a SMS-DELIVER T-PDU. This is the Network to MS/UE (downlink) direction.""" """Representation of a SMS-DELIVER T-PDU. This is the Network to MS/UE (downlink) direction."""
flags_construct = BitStruct('tp_rp'/Flag, 'tp_udhi'/Flag, 'tp_rp'/Flag, 'tp_sri'/Flag, flags_construct = BitStruct('tp_rp'/Flag,
Padding(1), 'tp_mms'/Flag, 'tp_mti'/BitsInteger(2)) 'tp_udhi'/Flag,
'tp_sri'/Flag,
Padding(1),
'tp_lp'/Flag,
'tp_mms'/Flag,
'tp_mti'/BitsInteger(2))
def __init__(self, **kwargs): def __init__(self, **kwargs):
kwargs['tp_mti'] = 0 kwargs['tp_mti'] = 0
super().__init__(**kwargs) super().__init__(**kwargs)

View File

@@ -1058,7 +1058,7 @@ class EF_OCSGL(LinFixedEF):
# TS 31.102 Section 4.4.11.2 (Rel 15) # TS 31.102 Section 4.4.11.2 (Rel 15)
class EF_5GS3GPPLOCI(TransparentEF): class EF_5GS3GPPLOCI(TransparentEF):
def __init__(self, fid='4f01', sfid=0x01, name='EF.5GS3GPPLOCI', size=(20, 20), def __init__(self, fid='4f01', sfid=0x01, name='EF.5GS3GPPLOCI', size=(20, 20),
desc='5S 3GP location information', **kwargs): desc='5GS 3GPP location information', **kwargs):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs)
upd_status_constr = Enum( upd_status_constr = Enum(
Byte, updated=0, not_updated=1, roaming_not_allowed=2) Byte, updated=0, not_updated=1, roaming_not_allowed=2)
@@ -1326,7 +1326,7 @@ class EF_5G_PROSE_UIR(TransparentEF):
pass pass
class FiveGDdnmfCtfAddrForUploading(BER_TLV_IE, tag=0x97): class FiveGDdnmfCtfAddrForUploading(BER_TLV_IE, tag=0x97):
pass pass
class ProSeConfigDataForUeToNetworkRelayUE(BER_TLV_IE, tag=0xa0, class ProSeConfigDataForUsageInfoReporting(BER_TLV_IE, tag=0xa0,
nested=[EF_5G_PROSE_DD.ValidityTimer, nested=[EF_5G_PROSE_DD.ValidityTimer,
CollectionPeriod, ReportingWindow, CollectionPeriod, ReportingWindow,
ReportingIndicators, ReportingIndicators,
@@ -1336,7 +1336,7 @@ class EF_5G_PROSE_UIR(TransparentEF):
desc='5G ProSe configuration data for usage information reporting', **kwargs): desc='5G ProSe configuration data for usage information reporting', **kwargs):
super().__init__(fid, sfid=sfid, name=name, desc=desc, **kwargs) super().__init__(fid, sfid=sfid, name=name, desc=desc, **kwargs)
# contains TLV structure despite being TransparentEF, not BER-TLV ?!? # contains TLV structure despite being TransparentEF, not BER-TLV ?!?
self._tlv = EF_5G_PROSE_UIR.ProSeConfigDataForUeToNetworkRelayUE self._tlv = EF_5G_PROSE_UIR.ProSeConfigDataForUsageInfoReporting
# TS 31.102 Section 4.4.13.8 (Rel 18) # TS 31.102 Section 4.4.13.8 (Rel 18)
class EF_5G_PROSE_U2URU(TransparentEF): class EF_5G_PROSE_U2URU(TransparentEF):

View File

@@ -1117,8 +1117,8 @@ class DF_GSM(CardDF):
EF_MBI(), EF_MBI(),
EF_MWIS(), EF_MWIS(),
EF_CFIS(), EF_CFIS(),
EF_EXT('6fc8', None, 'EF.EXT6', desc='Externsion6 (MBDN)'), EF_EXT('6fc8', None, 'EF.EXT6', desc='Extension6 (MBDN)'),
EF_EXT('6fcc', None, 'EF.EXT7', desc='Externsion7 (CFIS)'), EF_EXT('6fcc', None, 'EF.EXT7', desc='Extension7 (CFIS)'),
EF_SPDI(), EF_SPDI(),
EF_MMSN(), EF_MMSN(),
EF_EXT('6fcf', None, 'EF.EXT8', desc='Extension8 (MMSN)'), EF_EXT('6fcf', None, 'EF.EXT8', desc='Extension8 (MMSN)'),

View File

@@ -139,7 +139,6 @@ def enc_plmn(mcc: Hexstr, mnc: Hexstr) -> Hexstr:
def dec_plmn(threehexbytes: Hexstr) -> dict: def dec_plmn(threehexbytes: Hexstr) -> dict:
res = {'mcc': "0", 'mnc': "0"} res = {'mcc': "0", 'mnc': "0"}
dec_mcc_from_plmn_str(threehexbytes)
res['mcc'] = dec_mcc_from_plmn_str(threehexbytes) res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
res['mnc'] = dec_mnc_from_plmn_str(threehexbytes) res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
return res return res
@@ -911,7 +910,8 @@ class DataObjectCollection:
def encode(self, decoded) -> bytes: def encode(self, decoded) -> bytes:
res = bytearray() res = bytearray()
for i in decoded: for i in decoded:
obj = self.members_by_name(i[0]) name = i[0]
obj = self.members_by_name[name]
res.append(obj.to_tlv()) res.append(obj.to_tlv())
return res return res