The Onomondo SoftSIM is an Open Source C based UICC implementation, allowing new and innovative cellular device designs to see the light of day in the ever-growing landscape of IoT!
To integrate this awesome new SoftSIM UICC form factor, we have partnered with Nordic Semiconductor to develop and distribute a new SoftSIM modem interface that supports APDU exchange between the modem and the application processor. For more details and an in-depth explenation, refer to Nordic Semiconductor's documentation.
The Onomondo SoftSIM samples for nRF91 Series SiP's can be imported as a Zephyr module within the nRF Connect SDK.
A new SDK can be initiated with the following two commands if you are already a user of west and nrf:
west init -m https://github.com/onomondo/nrf-softsim.git
git -C modules/lib/onomondo-softsim submodule update --init
west update
Getting started with the external profile sample:
cd modules/lib/onomondo-softsim/samples/softsim_external_profile
west build --sysbuild -b nrf9151dk/nrf9151/ns
west flash
onomondo-uicc is bundled as a git submodule of this repository (under lib/onomondo-uicc). west update does not automatically initialize submodules of the manifest self repo, so you must run:
git -C modules/lib/onomondo-softsim submodule update --init
Skipping this step leaves lib/onomondo-uicc empty and the CMake configure step will fail. If your application uses west.yml to consume nrf-softsim as a dependency, set submodules: true on the project entry — see Using onomondo-softsim as a dependency below.
SoftSIM profiles are delivered through our API. As this can be a bit cumbersome, we've developed a small tool to make this process easier. The tool is available at sofsim-cli. Additional instructions can be found in the CLI repository.
- Generate an API key on app.onomondo.com/api-keys. Follow the instructions on the app.
- Download the
softsimcli tool for your platform. - Fetch your profile:
./softsim fetch --api-key = <your_api_key> -n 1. This will create aprofilesdirectory for you with1encrypted profile.
Every time you require a new profile, simply use the ./softsim next --key=<path to your private key>. It will look in the ./profiles folder and decrypt and format a profile. This command guarantees that a new profile is given each time.
- Configure NCS to include SoftSIM libraries in your build system
- Set-up your API key to get access to SoftSIM profiles through our API
- Configure your project to build with SoftSIM
- Configuring SoftSIM in NCS samples
- Building and running
- SoftSIM and physical SIM selection
- Understanding the SIM - why SoftSIM is possible
- Details on provisioning
- Details on kConfig options
For existing toolchains and build systems it is sufficient to update the manifest to point to west.yml inside this repository:
cd <ncs_base>
git clone https://github.com/onomondo/nrf-softsim.git modules/lib/onomondo-softsim
git -C modules/lib/onomondo-softsim submodule update --init
west config manifest.path modules/lib/onomondo-softsim/
west update
The git submodule update --init line is required — see Initialize the onomondo-uicc git submodule in Prerequisites for the full rationale.
If your application has its own west.yml that imports sdk-nrf and lists onomondo-softsim as an additional west project, you must add submodules: true to ensure west update also fetches onomondo-uicc:
projects:
- name: sdk-nrf
remote: nrfconnect
revision: <tag>
import: true
- name: onomondo-softsim
url: https://github.com/onomondo/nrf-softsim.git
path: modules/lib/onomondo-softsim
revision: <tag>
submodules: true # required: initializes the onomondo-uicc git submoduleFirst time setting it up? We recommend using the nRF Connect for Desktop to get the build system correctly set up. Once done, configure the manifest as described above.
Bonus tip: The Toolchain Manager allows you to easily generate the correct environment variables. Click the small arrow and select Generate environment script. The output file contains everything you need to set up the new toolchain.
Your folder structure should look something like:
ncs
|___ .west
|___ nrf
|___ nrfxlib
|___ modules
|___lib
|___onomondo-softsim
|___ ...
|___ ...
|___ zephyr
|___ ...
The samples/softsim_external_profile sample showcases how a SoftSIM profile can be provisioned, in two modes selected by configuration.
Build and flash, then supply a profile over the serial port. It is provisioned and the device reboots to free up the UART for AT commands, then attaches.
west build --sysbuild -b nrf9151dk/nrf9151/ns
west flash
echo "<my_profile>" > /dev/tty.usbmodem<id>
Provisions a compiled-in profile during the first system initialization, so the serial step is skipped. Handy for development, as the profile doesn't have to be re-provisioned on every flash. Enable it either by uncommenting the static lines in prj.conf, or by building with the overlay:
west build --sysbuild -b nrf9151dk/nrf9151/ns -- -DEXTRA_CONF_FILE=overlay-static.conf
The external (default) flow results in:
*** Booting Zephyr OS build v3.2.99-ncs1 ***
[00:00:00.610,198] <inf> softsim_sample: SoftSIM sample started.
[00:00:00.610,656] <inf> softsim_sample: Transfer SoftSIM profile using serial COM port, terminate by newline character (return key)
*** Booting Zephyr OS build v3.2.99-ncs1 ***
[00:00:00.555,664] <inf> softsim_sample: SoftSIM sample started.
[00:00:00.615,875] <inf> softsim_sample: Waiting for LTE connect event.
[00:00:00.744,140] <inf> softsim_sample: LTE cell changed: Cell ID: -1, Tracking area: -1
[00:00:01.185,760] <inf> softsim_sample: LTE cell changed: Cell ID: 13358642, Tracking area: 2000
[00:00:01.308,349] <inf> softsim_sample: RRC mode: Connected```
+CEREG: 5,"07D0","00CBD632",7,,,"00100011","11100000"
[00:00:07.096,221] <inf> softsim_sample: Network registration status: Connected - roaming
[00:00:07.096,405] <inf> softsim_sample: LTE connected!
For most samples and applications, it's sufficient to build by executing the following command:
west build -b nrf9151dk/nrf9151/ns -- "-DOVERLAY_CONFIG=$PATH_TO_ONOMONDO_SOFTSIM/overlay-softsim.conf"
Where PATH_TO_ONOMONDO_SOFTSIM is the path of the downloaded Onomondo SoftSIM repository, for example $HOME/ncs/nrf-softsim-dev.
SoftSIM is relying on some default data in the storage partition. This section of the flash can be generated and flashed manually (see steps below) or, as we recommend, automatically included by adding SB_CONFIG_SOFTSIM_BUNDLE_TEMPLATE_HEX=y to sysbuild.conf.
Manually generating SoftSIM profile template data:
- After building the application, generate the application-specific template profile.
west build -b nrf9151dk/nrf9151/ns -t onomondo_softsim_template - Flash the application-specific template profile.
west flash --hex-file build/onomondo-softsim/template.hex
If the partition table of the application changes, for example due to another partition changing size, the template profile must be rebuilt and flashed again. The partition table can be checked at any time with:
west build -t partition_manager_report
Some applications will fail to link with error zephyr/zephyr_pre0.elf uses VFP register arguments (for example modem_shell). In this case it is required to also enable CONFIG_FP_SOFTABI=y. It is suggested to create an additional Kconfig overlay for application specific SoftSIM configurations and add them to overlay-softsim.conf inside
the application directory.
The application can then be built like this:
west build -b nrf9151dk/nrf9151/ns -- "-DOVERLAY_CONFIG=$PATH_TO_ONOMONDO_SOFTSIM/overlay-softsim.conf;overlay-softsim.conf"
For very large applications, it is required to disable features in order to reduce the size of the application binary and leave space on Flash for the SIM profile. During the build step, a Flash overflow error will be reported if this requirement is not satisfied. The same principle applies for RAM requirements.
Onomondo SoftSIM uses the heap memory pool. It is expected that CONFIG_HEAP_MEM_POOL_SIZE is at least 30000, so if the target application also uses the heap, please
consider adjusting this Kconfig accordingly.
Onomondo SoftSIM cannot coexist with CONFIG_SETTINGS with NVS backend CONFIG_SETTINGS_NVS. Please consider switching instead to FCB backend by enabling CONFIG_SETTINGS_FCB.
The Modem is runtime-configurable to use regular SIM and/or SoftSIM (or iSIM if supported). The configuration is done by the AT command AT%CSUS=2 for Software SIM selection. The configuration can be done only when the Modem is deactivated. When reverting to physical SIM, configure with the AT command AT%CSUS=0. SIM selection is committed to NVM after a AT+CFUN=0.
When enabling SoftSIM, the Software SIM will be selected automatically upon initialization.
Isn't completely finalized yet. The following fields should either be y selected by SoftSIM or the application developer:
- Flash access
- TFM for
psa_crypto - NVS for profile
CONFIG_SOFTSIM includes SoftSIM in the build system
CONFIG_SOFTSIM_AUTO_INIT starts the SoftSIM task automatically. This can be omitted and done expicitly in the user application.
Debug logging is split into two independent layers, each with its own Zephyr log module so they can be enabled at different verbosity:
CONFIG_SOFTSIM_NRF_DEBUG_LOGS=y # nrf-softsim layer (SIM HAL, filesystem, crypto)
CONFIG_SOFTSIM_LIBS_DEBUG_LOGS=y # onomondo-uicc library SS_LOGP traces (high volume)
in your prj.conf. Either raises its layer to DBG and bumps the Zephyr log +
UART backend buffers so traces stay readable instead of being garbled by the
default 1-byte UART backend buffer. Leave both =n (the default) for production.
The onomondo-uicc trace (CONFIG_SOFTSIM_LIBS_DEBUG_LOGS) is high volume, and the
default deferred logging drops most of it during SIM init. To capture it losslessly,
enable synchronous logging with CONFIG_LOG_MODE_IMMEDIATE=y — see the
SOFTSIM_LOG_IMMEDIATE_MODE Kconfig help for the details and trade-offs.
To be as brief as possible - a SIM is nothing more than a fancy filesystem with the ability to calculate an authentication response to a given authentication challenge. More about that later.
For details - refer to https://www.etsi.org/deliver/etsi_ts/102200_102299/102221/18.00.00_60/ts_102221v180000p.pdf
The majority of commands that the SIM understands are related to the underlying filesystem. These include, but not limitied to, SELECT, READ BINARY, UPDATE BINARY, READ RECORD, UPDATE RECORD, SEARCH RECORD. You get the idea. Something about selecting files and either reading them or updating them.
Not all files are free to update. For instance the IMSI can only be changed by the operator with the correct PINs - so a SIM also manages access rights. Some rights are unlocked with the PIN - for that VERIFY PIN command is issued.
What happens when you 'activate' the SIM on your device (AT+CFUN=41)? The first many commands follows the same pattern - 00a4.... which is the SELECT command followed by 00b0.... which is the READ BINARY command.
80f20000000168-STATUS00a408040000022fe20168-SELECT'2fe2' (EF_ICCID)00b000000a-READ BINARY- Get the actual content of selected fine00a408040000022f000168 -SELECT'2f00' (EF_DIR)00b2010426-READ RECORD00a408040000022f050168-SELECT'2f05' (EF_PL) which encodes the Preferred Language00b000000a- -READ BINARY00a408040000022f080168-SELECT'2f08' (EF_UMPC) (UICC Maximum Power Consumption)
And the list goes on...
The main point here is that EF_DIR, EF_PL, EF_UMPC etc are the same for all SIMs. Only the ICCID and IMSI is different across SIMs when they are fresh out of the factory.
To accomodate that we've created a bootstrapping filesystem that should be flashed together with the application.
The list of files is fairly involved - but in the end only a subset of files are ever accessed.
Internally this is a NVS partition which is a key-value store type. It is pretty compact and generally sufficient. The previous request of reading the ICCID internally translates to something similar to:
00a408040000022fe20168 -> open('/3f00/2fe2) -> nvs_read(id=14)
We've made a caching layer as well to avoid i) slow reads and ii) excessive writes to flash. So, the SoftSIM profile data looks something like:
The first entry is used to translate between paths ('3f00/2fe2) to an actual NVS key. It contains an ordered list of files sorted by frequency of access - i.e. the 'master file, 3f00' is in the top, since it is most frequently accessed.
The list is read and parsed to a linked list - and this makes the base for all cached operations. The order makes the lookup very fast.
And that leads us to provisioning of SIMs.
The IMSI/ICCID should't be a surprise at this point:
#define IMSI_PATH "/3f00/7ff0/6f07"
#define ICCID_PATH "/3f00/2fe2"
int write_profile(...){
...
// find the NVS key based on the cache
struct cache_entry *entry = (struct cache_entry * f_cache_find_by_name(IMSI_PATH, &fs_cache);
// commit directly to NVS
nvs_write(&fs, entry->key, profile->IMSI, IMSI_LEN)
... // repeat for ICCID and support files
}Alright, that was the easy part. In reality something else happens just before:
struct ss_profile profile = {0};
decode_profile(len, profile_r, &profile);
// import to psa_crypto
ss_utils_setup_key(KMU_KEY_SIZE, profile.K, KEY_ID_KI);
ss_utils_setup_key(KMU_KEY_SIZE, profile.KIC, KEY_ID_KIC);
ss_utils_setup_key(KMU_KEY_SIZE, profile.KID, KEY_ID_KID);3 keys are written to the KMU. These are related to the authentication and remote SIM OTA commands.
When a device finds a network it wants to attach to, something like this happens (simplified version):
Step 5 is a SIM command as well AUTHENTICATE EVEN and it is running the milenage algorithm that also creates session keys, checks that the network in fact isn't an imposter, etc.
The milenage algorithm is AES based and we utilize the KMU through the psa_crypto API to implement this. This means that the keys are very protected and once written they can't ever be extracted. Instead they are used by reference in the crypto engine.
Is done through a pretty simple interface -
nrf_softsim_provision(uint8_t * data, size_t len) decodes and write the profile to the appropriate places.
The profile is fetched from Onomondo's API. The sample uses UART to transfer it.
At any time the provisioning status can be queried with nrf_softsim_check_provisioned(). Handy when boothing up as the device can enter a provision mode based on this.
struct profile
{
u8[] Ki,
u8[] KIC,
u8[] KID,
u8[] IMSI,
u8[] ICCID,
u8[] SMSP // SMS related
}The profile encoding is a TLV like structure (Tag Length Value) to make it a bit more flexible. Each field in the profile has a tag assigned e.g. IMSI_TAG=(0x01).
The profile is contructed by encoding TAG|LEN|DATA[LEN] for each field and concatinating multple TLV fields:
Encoding the IMSI is done by: TAG: 1, LEN: 0x12:
IMSI_TLV: 0112082943051220434955
Where the first 4 bytes are recognized as 01(TAG) 12(LEN) and indeed 18 bytes follow.
The default value in prj.conf is the GSMA TS.48 standardtabnndard USIM test profile, TLV-encoded as TAG | LEN | DATA (all hex characters). Decoded:
| Tag | Field | Raw hex | Decoded |
|---|---|---|---|
01 |
IMSI | 080910101032547698 |
001010123456789 (MCC=001 MNC=01) |
02 |
ICCID | 98001032547698103214 |
89000123456789012341 |
03 |
OPC | 00000000000000000000000000000000 |
all zeros (test value) |
04 |
KI | 000102030405060708090A0B0C0D0E0F |
sequential bytes (test value) |
05 |
KIC | 000102030405060708090A0B0C0D0E0F |
sequential bytes (test value) |
06 |
KID | 000102030405060708090A0B0C0D0E0F |
sequential bytes (test value) |
mem.h/ss_heap.c can now be optionally used to implement custom allocators - i.e. k_malloc() to avoid conflict with stdc heap pools. We default to k_malloc()/k_free()
The main challenge here is that the NVS is a "key-value" store i.e. UINT16 -> void *. SoftSIM needs a char * path -> void * data map.
To overcome this, the first entry (ID 1) in the NVS will store a mapping from paths to actual ID's.
The ID is leveraged to encode additional information, i.e. if content is in protected storage instead.
Dir entry:
[[ID_1, PATH_LEN, PATH], [ID_n, PATH_LEN, PATH], ... ]
Where ID is the actual ID where DATA is stored.
ID & 0xFF00 (upper bits) are used for flags, i.e.
#define FS_READ_ONLY (1UL << 8)
#define FS_COMMIT_ON_CLOSE (1UL << 7) // commit changes to NVS on close
The inital DIR is designed to prioritize most accessed files as well. Internally, files are cached and in general kept in memory.
FS_COMMIT_ON_CLOSE is used for SEQ numbers that should be committed to flash directly to reduce attack vectors.
Either call nrf_softsim_init() explicitly or let the kernel do it on boot with the config option.
SoftSIM entrypoint starts its own work queue and returns immediately after. The handler installed with nrf_modem_softsim_req_handler_set() will enqueue requests, as they come and the work queue will unblock and handle the request. The SoftSIM context will be blocked most of the time. The main interaction happens on boot.
Please note that SoftSIM internally need access to a storage partition. This should be pre-populated with the template.hex provided in the samples. The adress in the template.hex is hardcoded but can be moved around freely as pleased with an appropriate tool. The location is derived from the devicetree at compile time ( FIXED_PARTITION_DEVICE(NVS_PARTITION))