From 1e956d053c3f5dab9be367d8407ed8caf75ea871 Mon Sep 17 00:00:00 2001 From: Benjamin Bruun Date: Wed, 14 Feb 2024 14:46:56 +0100 Subject: [PATCH] feat!: Initial commit --- .github/workflows/c-cpp.yml | 88 ++ .github/workflows/cmake-build.yml | 33 + .gitignore | 57 + BSD-3-Clause | 36 + CMakeLists.txt | 40 + LICENSE | 674 +++++++++ Makefile.am | 15 + README.md | 152 ++ configure.ac | 113 ++ files-creation/all.pysim | 32 + files-creation/softsim_create_files.pysim | 155 +++ .../softsim_create_files_minimal.pysim | 26 + files-creation/softsim_fill_files_efarr.pysim | 40 + files-creation/softsim_fill_files_mf.pysim | 67 + files-creation/softsim_fill_files_usim.pysim | 109 ++ files/3f00.def | 1 + files/3f00/2f00 | 1 + files/3f00/2f00.def | 1 + files/3f00/2f05 | 1 + files/3f00/2f05.def | 1 + files/3f00/2f06 | 1 + files/3f00/2f06.def | 1 + files/3f00/2f08 | 1 + files/3f00/2f08.def | 1 + files/3f00/2fe2 | 1 + files/3f00/2fe2.def | 1 + files/3f00/5f100001 | 1 + files/3f00/5f100001.def | 1 + files/3f00/7ff0.def | 1 + files/3f00/7ff0/5f100001 | 1 + files/3f00/7ff0/5f100001.def | 1 + files/3f00/7ff0/6f05 | 1 + files/3f00/7ff0/6f05.def | 1 + files/3f00/7ff0/6f06 | 1 + files/3f00/7ff0/6f06.def | 1 + files/3f00/7ff0/6f07 | 1 + files/3f00/7ff0/6f07.def | 1 + files/3f00/7ff0/6f08 | 1 + files/3f00/7ff0/6f08.def | 1 + files/3f00/7ff0/6f09 | 1 + files/3f00/7ff0/6f09.def | 1 + files/3f00/7ff0/6f31 | 1 + files/3f00/7ff0/6f31.def | 1 + files/3f00/7ff0/6f38 | 1 + files/3f00/7ff0/6f38.def | 1 + files/3f00/7ff0/6f42 | 1 + files/3f00/7ff0/6f42.def | 1 + files/3f00/7ff0/6f5b | 1 + files/3f00/7ff0/6f5b.def | 1 + files/3f00/7ff0/6f5c | 1 + files/3f00/7ff0/6f5c.def | 1 + files/3f00/7ff0/6f73 | 1 + files/3f00/7ff0/6f73.def | 1 + files/3f00/7ff0/6f78 | 1 + files/3f00/7ff0/6f78.def | 1 + files/3f00/7ff0/6f7b | 1 + files/3f00/7ff0/6f7b.def | 1 + files/3f00/7ff0/6f7e | 1 + files/3f00/7ff0/6f7e.def | 1 + files/3f00/7ff0/6fad | 1 + files/3f00/7ff0/6fad.def | 1 + files/3f00/7ff0/6fb7 | 1 + files/3f00/7ff0/6fb7.def | 1 + files/3f00/7ff0/6fc4 | 1 + files/3f00/7ff0/6fc4.def | 1 + files/3f00/7ff0/6fe3 | 1 + files/3f00/7ff0/6fe3.def | 1 + files/3f00/7ff0/6fe4 | 1 + files/3f00/7ff0/6fe4.def | 1 + files/3f00/a001 | 1 + files/3f00/a001.def | 1 + files/3f00/a002 | 1 + files/3f00/a002.def | 1 + files/3f00/a003 | 1 + files/3f00/a003.def | 1 + files/3f00/a004 | 1 + files/3f00/a004.def | 1 + files/3f00/a005 | 1 + files/3f00/a005.def | 1 + files/3f00/a1df1d01 | 1 + files/3f00/a1df1d01.def | 1 + gscriptor/chv_change_test.scriptor | 13 + gscriptor/chv_test.scriptor | 22 + gscriptor/chv_unblock_test.scriptor | 24 + gscriptor/create_delete_file_test.scriptor | 31 + gscriptor/proactive_sim_ota_sms.scriptor | 19 + gscriptor/read_binary_test.scriptor | 9 + gscriptor/read_record_test.scriptor | 18 + gscriptor/search_record_test.scriptor | 76 + gscriptor/select_test.scriptor | 26 + gscriptor/terminal_profile.scriptor | 6 + gscriptor/update_binary_test.scriptor | 9 + gscriptor/update_record_test.scriptor | 15 + include/Makefile.am | 6 + include/onomondo/Makefile.am | 6 + include/onomondo/softsim/Makefile.am | 12 + include/onomondo/softsim/file.h | 50 + include/onomondo/softsim/list.h | 117 ++ include/onomondo/softsim/log.h | 54 + include/onomondo/softsim/mem.h | 13 + include/onomondo/softsim/softsim.h | 20 + include/onomondo/softsim/storage.h | 21 + include/onomondo/softsim/utils.h | 86 ++ src/CMakeLists.txt | 4 + src/Makefile.am | 6 + src/softsim/CMakeLists.txt | 10 + src/softsim/Makefile.am | 38 + src/softsim/crypto/CMakeLists.txt | 19 + src/softsim/crypto/Makefile.am | 34 + src/softsim/crypto/aes-encblock.c | 32 + src/softsim/crypto/aes-internal-dec.c | 161 +++ src/softsim/crypto/aes-internal-enc.c | 126 ++ src/softsim/crypto/aes-internal.c | 845 ++++++++++++ src/softsim/crypto/aes-wrap.c | 70 + src/softsim/crypto/aes.h | 21 + src/softsim/crypto/aes_i.h | 125 ++ src/softsim/crypto/aes_wrap.h | 64 + src/softsim/crypto/common.h | 42 + src/softsim/crypto/crypto.h | 780 +++++++++++ src/softsim/crypto/des-internal.c | 494 +++++++ src/softsim/crypto/des_i.h | 27 + src/softsim/crypto/includes.h | 0 src/softsim/main.c | 281 ++++ src/softsim/milenage/CMakeLists.txt | 15 + src/softsim/milenage/Makefile.am | 28 + src/softsim/milenage/milenage.c | 347 +++++ src/softsim/milenage/milenage.h | 38 + src/softsim/milenage/milenage_usim.c | 187 +++ src/softsim/milenage/milenage_usim.h | 49 + src/softsim/storage.c | 471 +++++++ src/softsim/uicc/CMakeLists.txt | 54 + src/softsim/uicc/Makefile.am | 87 ++ src/softsim/uicc/access.c | 448 ++++++ src/softsim/uicc/access.h | 48 + src/softsim/uicc/apdu.c | 59 + src/softsim/uicc/apdu.h | 41 + src/softsim/uicc/btlv.h | 70 + src/softsim/uicc/btlv_dec.c | 209 +++ src/softsim/uicc/btlv_enc.c | 234 ++++ src/softsim/uicc/btlv_utils.c | 345 +++++ src/softsim/uicc/command.c | 239 ++++ src/softsim/uicc/command.h | 50 + src/softsim/uicc/context.h | 33 + src/softsim/uicc/ctlv.c | 459 +++++++ src/softsim/uicc/ctlv.h | 34 + src/softsim/uicc/df_name.c | 226 +++ src/softsim/uicc/df_name.h | 13 + src/softsim/uicc/fcp.c | 452 ++++++ src/softsim/uicc/fcp.h | 68 + src/softsim/uicc/file.c | 37 + src/softsim/uicc/fs.c | 451 ++++++ src/softsim/uicc/fs.h | 23 + src/softsim/uicc/fs_chg.c | 267 ++++ src/softsim/uicc/fs_chg.h | 16 + src/softsim/uicc/fs_utils.c | 321 +++++ src/softsim/uicc/fs_utils.h | 23 + src/softsim/uicc/log.c | 96 ++ src/softsim/uicc/proactive.c | 376 +++++ src/softsim/uicc/proactive.h | 240 ++++ src/softsim/uicc/sfi.c | 148 ++ src/softsim/uicc/sfi.h | 13 + src/softsim/uicc/sms.c | 527 +++++++ src/softsim/uicc/sms.h | 154 +++ src/softsim/uicc/softsim.c | 482 +++++++ src/softsim/uicc/sw.c | 25 + src/softsim/uicc/sw.h | 61 + src/softsim/uicc/tlv8.c | 341 +++++ src/softsim/uicc/tlv8.h | 31 + src/softsim/uicc/uicc_admin.c | 481 +++++++ src/softsim/uicc/uicc_admin.h | 11 + src/softsim/uicc/uicc_auth.c | 371 +++++ src/softsim/uicc/uicc_auth.h | 9 + src/softsim/uicc/uicc_cat.c | 192 +++ src/softsim/uicc/uicc_cat.h | 12 + src/softsim/uicc/uicc_file_ops.c | 1142 +++++++++++++++ src/softsim/uicc/uicc_file_ops.h | 15 + src/softsim/uicc/uicc_ins.h | 52 + src/softsim/uicc/uicc_lchan.c | 110 ++ src/softsim/uicc/uicc_lchan.h | 47 + src/softsim/uicc/uicc_pin.c | 623 +++++++++ src/softsim/uicc/uicc_pin.h | 27 + src/softsim/uicc/uicc_refresh.c | 152 ++ src/softsim/uicc/uicc_refresh.h | 23 + src/softsim/uicc/uicc_remote_cmd.c | 1224 +++++++++++++++++ src/softsim/uicc/uicc_remote_cmd.h | 16 + src/softsim/uicc/uicc_sms_rx.c | 383 ++++++ src/softsim/uicc/uicc_sms_rx.h | 32 + src/softsim/uicc/uicc_sms_tx.c | 510 +++++++ src/softsim/uicc/uicc_sms_tx.h | 39 + src/softsim/uicc/utils.c | 291 ++++ src/softsim/uicc/utils.h | 12 + src/softsim/uicc/utils_3des.c | 140 ++ src/softsim/uicc/utils_3des.h | 28 + src/softsim/uicc/utils_aes.c | 206 +++ src/softsim/uicc/utils_aes.h | 37 + src/softsim/uicc/utils_ota.c | 94 ++ src/softsim/uicc/utils_ota.h | 24 + tests/CMakeLists.txt | 16 + tests/Makefile.am | 63 + tests/aes/CMakeLists.txt | 17 + tests/aes/Makefile.am | 29 + tests/aes/aes_test.c | 209 +++ tests/aes/aes_test.ok | 15 + tests/atlocal.in | 0 tests/btlv/CMakeLists.txt | 11 + tests/btlv/Makefile.am | 28 + tests/btlv/btlv_test.c | 346 +++++ tests/btlv/btlv_test.err | 72 + tests/ctlv/CMakeLists.txt | 11 + tests/ctlv/Makefile.am | 28 + tests/ctlv/ctlv_test.c | 77 ++ tests/ctlv/ctlv_test.err | 17 + tests/des/CMakeLists.txt | 17 + tests/des/Makefile.am | 29 + tests/des/des_test.c | 105 ++ tests/des/des_test.ok | 7 + tests/fcp/CMakeLists.txt | 17 + tests/fcp/Makefile.am | 28 + tests/fcp/fcp_test.c | 146 ++ tests/fcp/fcp_test.ok | 26 + tests/list/CMakeLists.txt | 15 + tests/list/Makefile.am | 27 + tests/list/list_test.c | 77 ++ tests/list/list_test.ok | 13 + tests/ota/CMakeLists.txt | 17 + tests/ota/Makefile.am | 29 + tests/ota/ota_test.c | 151 ++ tests/ota/ota_test.ok | 32 + tests/sms/CMakeLists.txt | 11 + tests/sms/Makefile.am | 28 + tests/sms/sms_test.c | 316 +++++ tests/sms/sms_test.ok | 55 + tests/testsuite.at | 62 + tests/tlv8/CMakeLists.txt | 11 + tests/tlv8/Makefile.am | 28 + tests/tlv8/tlv8_test.c | 73 + tests/tlv8/tlv8_test.err | 17 + tests/utils/CMakeLists.txt | 17 + tests/utils/Makefile.am | 28 + tests/utils/utils_test.c | 46 + tests/utils/utils_test.ok | 5 + 241 files changed, 22146 insertions(+) create mode 100644 .github/workflows/c-cpp.yml create mode 100644 .github/workflows/cmake-build.yml create mode 100644 .gitignore create mode 100644 BSD-3-Clause create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 Makefile.am create mode 100644 README.md create mode 100644 configure.ac create mode 100644 files-creation/all.pysim create mode 100644 files-creation/softsim_create_files.pysim create mode 100644 files-creation/softsim_create_files_minimal.pysim create mode 100644 files-creation/softsim_fill_files_efarr.pysim create mode 100644 files-creation/softsim_fill_files_mf.pysim create mode 100644 files-creation/softsim_fill_files_usim.pysim create mode 100644 files/3f00.def create mode 100644 files/3f00/2f00 create mode 100644 files/3f00/2f00.def create mode 100644 files/3f00/2f05 create mode 100644 files/3f00/2f05.def create mode 100644 files/3f00/2f06 create mode 100644 files/3f00/2f06.def create mode 100644 files/3f00/2f08 create mode 100644 files/3f00/2f08.def create mode 100644 files/3f00/2fe2 create mode 100644 files/3f00/2fe2.def create mode 100644 files/3f00/5f100001 create mode 100644 files/3f00/5f100001.def create mode 100644 files/3f00/7ff0.def create mode 100644 files/3f00/7ff0/5f100001 create mode 100644 files/3f00/7ff0/5f100001.def create mode 100644 files/3f00/7ff0/6f05 create mode 100644 files/3f00/7ff0/6f05.def create mode 100644 files/3f00/7ff0/6f06 create mode 100644 files/3f00/7ff0/6f06.def create mode 100644 files/3f00/7ff0/6f07 create mode 100644 files/3f00/7ff0/6f07.def create mode 100644 files/3f00/7ff0/6f08 create mode 100644 files/3f00/7ff0/6f08.def create mode 100644 files/3f00/7ff0/6f09 create mode 100644 files/3f00/7ff0/6f09.def create mode 100644 files/3f00/7ff0/6f31 create mode 100644 files/3f00/7ff0/6f31.def create mode 100644 files/3f00/7ff0/6f38 create mode 100644 files/3f00/7ff0/6f38.def create mode 100644 files/3f00/7ff0/6f42 create mode 100644 files/3f00/7ff0/6f42.def create mode 100644 files/3f00/7ff0/6f5b create mode 100644 files/3f00/7ff0/6f5b.def create mode 100644 files/3f00/7ff0/6f5c create mode 100644 files/3f00/7ff0/6f5c.def create mode 100644 files/3f00/7ff0/6f73 create mode 100644 files/3f00/7ff0/6f73.def create mode 100644 files/3f00/7ff0/6f78 create mode 100644 files/3f00/7ff0/6f78.def create mode 100644 files/3f00/7ff0/6f7b create mode 100644 files/3f00/7ff0/6f7b.def create mode 100644 files/3f00/7ff0/6f7e create mode 100644 files/3f00/7ff0/6f7e.def create mode 100644 files/3f00/7ff0/6fad create mode 100644 files/3f00/7ff0/6fad.def create mode 100644 files/3f00/7ff0/6fb7 create mode 100644 files/3f00/7ff0/6fb7.def create mode 100644 files/3f00/7ff0/6fc4 create mode 100644 files/3f00/7ff0/6fc4.def create mode 100644 files/3f00/7ff0/6fe3 create mode 100644 files/3f00/7ff0/6fe3.def create mode 100644 files/3f00/7ff0/6fe4 create mode 100644 files/3f00/7ff0/6fe4.def create mode 100644 files/3f00/a001 create mode 100644 files/3f00/a001.def create mode 100644 files/3f00/a002 create mode 100644 files/3f00/a002.def create mode 100644 files/3f00/a003 create mode 100644 files/3f00/a003.def create mode 100644 files/3f00/a004 create mode 100644 files/3f00/a004.def create mode 100644 files/3f00/a005 create mode 100644 files/3f00/a005.def create mode 100644 files/3f00/a1df1d01 create mode 100644 files/3f00/a1df1d01.def create mode 100644 gscriptor/chv_change_test.scriptor create mode 100644 gscriptor/chv_test.scriptor create mode 100644 gscriptor/chv_unblock_test.scriptor create mode 100644 gscriptor/create_delete_file_test.scriptor create mode 100644 gscriptor/proactive_sim_ota_sms.scriptor create mode 100644 gscriptor/read_binary_test.scriptor create mode 100644 gscriptor/read_record_test.scriptor create mode 100644 gscriptor/search_record_test.scriptor create mode 100644 gscriptor/select_test.scriptor create mode 100644 gscriptor/terminal_profile.scriptor create mode 100644 gscriptor/update_binary_test.scriptor create mode 100644 gscriptor/update_record_test.scriptor create mode 100644 include/Makefile.am create mode 100644 include/onomondo/Makefile.am create mode 100644 include/onomondo/softsim/Makefile.am create mode 100644 include/onomondo/softsim/file.h create mode 100644 include/onomondo/softsim/list.h create mode 100644 include/onomondo/softsim/log.h create mode 100644 include/onomondo/softsim/mem.h create mode 100644 include/onomondo/softsim/softsim.h create mode 100644 include/onomondo/softsim/storage.h create mode 100644 include/onomondo/softsim/utils.h create mode 100644 src/CMakeLists.txt create mode 100644 src/Makefile.am create mode 100644 src/softsim/CMakeLists.txt create mode 100644 src/softsim/Makefile.am create mode 100644 src/softsim/crypto/CMakeLists.txt create mode 100644 src/softsim/crypto/Makefile.am create mode 100644 src/softsim/crypto/aes-encblock.c create mode 100644 src/softsim/crypto/aes-internal-dec.c create mode 100644 src/softsim/crypto/aes-internal-enc.c create mode 100644 src/softsim/crypto/aes-internal.c create mode 100644 src/softsim/crypto/aes-wrap.c create mode 100644 src/softsim/crypto/aes.h create mode 100644 src/softsim/crypto/aes_i.h create mode 100644 src/softsim/crypto/aes_wrap.h create mode 100644 src/softsim/crypto/common.h create mode 100644 src/softsim/crypto/crypto.h create mode 100644 src/softsim/crypto/des-internal.c create mode 100644 src/softsim/crypto/des_i.h create mode 100644 src/softsim/crypto/includes.h create mode 100644 src/softsim/main.c create mode 100644 src/softsim/milenage/CMakeLists.txt create mode 100644 src/softsim/milenage/Makefile.am create mode 100644 src/softsim/milenage/milenage.c create mode 100644 src/softsim/milenage/milenage.h create mode 100644 src/softsim/milenage/milenage_usim.c create mode 100644 src/softsim/milenage/milenage_usim.h create mode 100644 src/softsim/storage.c create mode 100644 src/softsim/uicc/CMakeLists.txt create mode 100644 src/softsim/uicc/Makefile.am create mode 100644 src/softsim/uicc/access.c create mode 100644 src/softsim/uicc/access.h create mode 100644 src/softsim/uicc/apdu.c create mode 100644 src/softsim/uicc/apdu.h create mode 100644 src/softsim/uicc/btlv.h create mode 100644 src/softsim/uicc/btlv_dec.c create mode 100644 src/softsim/uicc/btlv_enc.c create mode 100644 src/softsim/uicc/btlv_utils.c create mode 100644 src/softsim/uicc/command.c create mode 100644 src/softsim/uicc/command.h create mode 100644 src/softsim/uicc/context.h create mode 100644 src/softsim/uicc/ctlv.c create mode 100644 src/softsim/uicc/ctlv.h create mode 100644 src/softsim/uicc/df_name.c create mode 100644 src/softsim/uicc/df_name.h create mode 100644 src/softsim/uicc/fcp.c create mode 100644 src/softsim/uicc/fcp.h create mode 100644 src/softsim/uicc/file.c create mode 100644 src/softsim/uicc/fs.c create mode 100644 src/softsim/uicc/fs.h create mode 100644 src/softsim/uicc/fs_chg.c create mode 100644 src/softsim/uicc/fs_chg.h create mode 100644 src/softsim/uicc/fs_utils.c create mode 100644 src/softsim/uicc/fs_utils.h create mode 100644 src/softsim/uicc/log.c create mode 100644 src/softsim/uicc/proactive.c create mode 100644 src/softsim/uicc/proactive.h create mode 100644 src/softsim/uicc/sfi.c create mode 100644 src/softsim/uicc/sfi.h create mode 100644 src/softsim/uicc/sms.c create mode 100644 src/softsim/uicc/sms.h create mode 100644 src/softsim/uicc/softsim.c create mode 100644 src/softsim/uicc/sw.c create mode 100644 src/softsim/uicc/sw.h create mode 100644 src/softsim/uicc/tlv8.c create mode 100644 src/softsim/uicc/tlv8.h create mode 100644 src/softsim/uicc/uicc_admin.c create mode 100644 src/softsim/uicc/uicc_admin.h create mode 100644 src/softsim/uicc/uicc_auth.c create mode 100644 src/softsim/uicc/uicc_auth.h create mode 100644 src/softsim/uicc/uicc_cat.c create mode 100644 src/softsim/uicc/uicc_cat.h create mode 100644 src/softsim/uicc/uicc_file_ops.c create mode 100644 src/softsim/uicc/uicc_file_ops.h create mode 100644 src/softsim/uicc/uicc_ins.h create mode 100644 src/softsim/uicc/uicc_lchan.c create mode 100644 src/softsim/uicc/uicc_lchan.h create mode 100644 src/softsim/uicc/uicc_pin.c create mode 100644 src/softsim/uicc/uicc_pin.h create mode 100644 src/softsim/uicc/uicc_refresh.c create mode 100644 src/softsim/uicc/uicc_refresh.h create mode 100644 src/softsim/uicc/uicc_remote_cmd.c create mode 100644 src/softsim/uicc/uicc_remote_cmd.h create mode 100644 src/softsim/uicc/uicc_sms_rx.c create mode 100644 src/softsim/uicc/uicc_sms_rx.h create mode 100644 src/softsim/uicc/uicc_sms_tx.c create mode 100644 src/softsim/uicc/uicc_sms_tx.h create mode 100644 src/softsim/uicc/utils.c create mode 100644 src/softsim/uicc/utils.h create mode 100644 src/softsim/uicc/utils_3des.c create mode 100644 src/softsim/uicc/utils_3des.h create mode 100644 src/softsim/uicc/utils_aes.c create mode 100644 src/softsim/uicc/utils_aes.h create mode 100644 src/softsim/uicc/utils_ota.c create mode 100644 src/softsim/uicc/utils_ota.h create mode 100644 tests/CMakeLists.txt create mode 100644 tests/Makefile.am create mode 100644 tests/aes/CMakeLists.txt create mode 100644 tests/aes/Makefile.am create mode 100644 tests/aes/aes_test.c create mode 100644 tests/aes/aes_test.ok create mode 100644 tests/atlocal.in create mode 100644 tests/btlv/CMakeLists.txt create mode 100644 tests/btlv/Makefile.am create mode 100644 tests/btlv/btlv_test.c create mode 100644 tests/btlv/btlv_test.err create mode 100644 tests/ctlv/CMakeLists.txt create mode 100644 tests/ctlv/Makefile.am create mode 100644 tests/ctlv/ctlv_test.c create mode 100644 tests/ctlv/ctlv_test.err create mode 100644 tests/des/CMakeLists.txt create mode 100644 tests/des/Makefile.am create mode 100644 tests/des/des_test.c create mode 100644 tests/des/des_test.ok create mode 100644 tests/fcp/CMakeLists.txt create mode 100644 tests/fcp/Makefile.am create mode 100644 tests/fcp/fcp_test.c create mode 100644 tests/fcp/fcp_test.ok create mode 100644 tests/list/CMakeLists.txt create mode 100644 tests/list/Makefile.am create mode 100644 tests/list/list_test.c create mode 100644 tests/list/list_test.ok create mode 100644 tests/ota/CMakeLists.txt create mode 100644 tests/ota/Makefile.am create mode 100644 tests/ota/ota_test.c create mode 100644 tests/ota/ota_test.ok create mode 100644 tests/sms/CMakeLists.txt create mode 100644 tests/sms/Makefile.am create mode 100644 tests/sms/sms_test.c create mode 100644 tests/sms/sms_test.ok create mode 100644 tests/testsuite.at create mode 100644 tests/tlv8/CMakeLists.txt create mode 100644 tests/tlv8/Makefile.am create mode 100644 tests/tlv8/tlv8_test.c create mode 100644 tests/tlv8/tlv8_test.err create mode 100644 tests/utils/CMakeLists.txt create mode 100644 tests/utils/Makefile.am create mode 100644 tests/utils/utils_test.c create mode 100644 tests/utils/utils_test.ok diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml new file mode 100644 index 0000000..d1b7a7e --- /dev/null +++ b/.github/workflows/c-cpp.yml @@ -0,0 +1,88 @@ +name: C/C++ CI + +on: + push: + branches: + - "**" + + +jobs: + build-and-test: + + runs-on: ubuntu-latest + + + steps: + - uses: actions/checkout@v2 + + - name: Setup build + run: autoreconf -i --force && ./configure --enable-sanitize --enable-werror + + - name: Run tests + run: make check + + - name: Make all + run: make all + + - uses: actions/checkout@v2 + with: + repository: frankmorgner/vsmartcard + path: vsmartcard + + - name: Setup VPCD + run: sudo apt install libpcsclite-dev && sudo apt install help2man && cd vsmartcard/virtualsmartcard && autoreconf --verbose --install --force && sudo ./configure --sysconfdir=/etc && sudo make && sudo make install + + # make sure the server is running... + - name: make sure virtual card reader can be reached + run: sudo apt install pcscd && sudo systemctl start pcscd + + - uses: actions/checkout@v2 + with: + repository: onomondo/onomondo-softsim-test-suite + submodules: recursive + path: softsim-tests + token: ${{ secrets.GH_AUTH_TOKEN }} + + - uses: actions/setup-python@v3 + with: + python-version: '3.x' + + - name: Install test suite (esp. pysim) requirements + run: | + set -e + git clone https://gitea.osmocom.org/sim-card/pysim.git $GITHUB_WORKSPACE/pysim + pip3 install -r $GITHUB_WORKSPACE/softsim-tests/requirements.txt + # Shouldn't be any additional ones, but running it anyway in case upstream changes them + pip3 install -r $GITHUB_WORKSPACE/pysim/requirements.txt + + - name: Remove files before restoring them + run: git rm -rf files/3f00 files/3f00.def && mkdir files + + - name: Start soft sim + run: cd $GITHUB_WORKSPACE/ && src/softsim/softsim & + + - name: Restore files and verify their identity of restored with original files + run: | + set -e + ./pysim/pySim-shell.py -p0 --script ./files-creation/all.pysim + git add files + git diff --cached --exit-code + + - name: Run python-test suite + run: | + set -e + cd $GITHUB_WORKSPACE/softsim-tests/ + # Note that this only logs, and is expected to fail (but does not report that in its error code) + python test_SIM_OS.py + + - name: Stop soft sim (standalone version brings its own execution management) + # It takes some time for VPCD to be ready again + run: pkill -f softsim -USR1 && sleep 1 + + - name: Install libosmocore-utils (necessary for testing Milenage) + run: sudo apt-get install -y --no-install-recommends libosmocore-utils + + - name: Run python-test suite (standalone version) + run: | + cd $GITHUB_WORKSPACE/softsim-tests/ + SOFTSIM_TEST_RUNNER=execute_softsim PYTHONPATH=./pysim SOFTSIM=../src/softsim/softsim python3 -m unittest -v diff --git a/.github/workflows/cmake-build.yml b/.github/workflows/cmake-build.yml new file mode 100644 index 0000000..3e31c0d --- /dev/null +++ b/.github/workflows/cmake-build.yml @@ -0,0 +1,33 @@ +name: CI/CD for CMAKE Project + +on: + push: + branches: + - "**" + +jobs: + cmake-build-and-test: + runs-on: ubuntu-latest + steps: + - name: Checkout Project + uses: actions/checkout@v4 + + - name: Install CMake + uses: lukka/get-cmake@latest + with: + cmakeVersion: 3.27.0 + ninjaVersion: 1.11.1 + + - name: Install Embedded Toolchain + uses: carlosperate/arm-none-eabi-gcc-action@v1.8.0 + + - name: Configure and Build Project + run: | + cmake -S . -B build -DBUILD_TESTING=y -DCONFIG_USE_SYSTEM_HEAP=y + cmake --build build + + - name: Test Project + run: cd build && ctest + + - name: Install Project + run: cd build && make install diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a904cf0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,57 @@ +*.o +*.lo +*.a +.deps +Makefile +Makefile.in +config.h +config.h.in +*.pc +*~ + +*.*~ +*.sw? +.libs +*.pyc +*.gcda +*.gcno + +**/TAGS + +#configure +aclocal.m4 +autom4te.cache/ +config.log +config.status +config.guess +config.sub +configure +compile +depcomp +install-sh +missing +stamp-h1 +libtool +ltmain.sh +m4/*.m4 + +#tests +tests/testsuite.dir +tests/*/*_test + +tests/atconfig +tests/atlocal +tests/package.m4 +tests/testsuite +tests/testsuite.log + +writtenconfig/ + +#binary +src/softsim/softsim + +# Mac OS X +.DS_Store +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/settings.json diff --git a/BSD-3-Clause b/BSD-3-Clause new file mode 100644 index 0000000..3789c3e --- /dev/null +++ b/BSD-3-Clause @@ -0,0 +1,36 @@ +Valid-License-Identifier: BSD-3-Clause +SPDX-URL: https://spdx.org/licenses/BSD-3-Clause.html +Usage-Guide: + To use the BSD 3-clause "New" or "Revised" License put the following SPDX + tag/value pair into a comment according to the placement guidelines in + the licensing rules documentation: + SPDX-License-Identifier: BSD-3-Clause +License-Text: + +Copyright (c) . All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2a69a72 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,40 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +cmake_minimum_required(VERSION 3.13...3.19 FATAL_ERROR) + +project(softsim + VERSION 1.0.0 + LANGUAGES C +) + +option(CONFIG_USE_SYSTEM_HEAP "Use free/malloc instead of port_free/port_malloc" OFF) + +if(CONFIG_USE_SYSTEM_HEAP) + add_compile_definitions(-DCONFIG_USE_SYSTEM_HEAP) +endif() + +add_subdirectory(src) + +install(TARGETS storage uicc milenage crypto + CONFIGURATIONS Debug + ARCHIVE DESTINATION ${CMAKE_SOURCE_DIR}/lib_${TARGET_CPU}/Debug + PUBLIC_HEADER DESTINATION ${CMAKE_SOURCE_DIR}/lib_${TARGET_CPU}/include +) + +install(TARGETS storage uicc milenage crypto + CONFIGURATIONS Release + ARCHIVE DESTINATION ${CMAKE_SOURCE_DIR}/lib_${TARGET_CPU}/Release/ + PUBLIC_HEADER DESTINATION ${CMAKE_SOURCE_DIR}/lib_${TARGET_CPU}/include +) + +install(TARGETS storage uicc milenage crypto + CONFIGURATIONS MinSizeRel + ARCHIVE DESTINATION ${CMAKE_SOURCE_DIR}/lib_${TARGET_CPU}/ReleaseOs/ + PUBLIC_HEADER DESTINATION ${CMAKE_SOURCE_DIR}/lib_${TARGET_CPU}/include +) + +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING) + include(CTest) + add_subdirectory(tests) +endif() diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..6f0f01b --- /dev/null +++ b/Makefile.am @@ -0,0 +1,15 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6 + +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +SUBDIRS = \ + include \ + src \ + tests \ + $(NULL) diff --git a/README.md b/README.md new file mode 100644 index 0000000..4fc5eb8 --- /dev/null +++ b/README.md @@ -0,0 +1,152 @@ +Onomondo software SIM/USIM implementation +========================================= + +This repository contains the [current state of work towards] a pure software +implementation/emulation of the most relevant SIM/UICC/USIM functionalities. + + +License +------- + +[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) + +The Onomondo UICC Repository is provided under: + +- SPDX-License-Identifier: GPL-3.0-only + +Being under the terms of the GNU General Public License version 3 only, +according with: + +- LICENSE + +For files licensed under BSD license, refer to BSD-3-Clause file in the root directory of this source tree. + + +GIT Repository +-------------- + +The canonical git+ssh access of the repository is `git@github.com:onomondo/onomondo-uicc.git` + + +External Dependencies +--------------------- + +We try to not create any external dependencies. This has both technical and licensing +reasons: We cannot expect to cross-compile most external dependencies to the deep embedded +"close to bare iron" environments of a cellular modem or microcontroller. + +If we reuse existing code, it must be under a permissive license (e.g. BSD, MIT), such +as for example the MILENAGE implementation, which we can take from wpa_supplicant, which is +BSD/GPL dual-licensed. + + +Testing +------- + +We expect to have both + +* unit tests for individual low-level functions using cunit or autotest in this very repository. +* higher level integration tests written in python, based on pySim, interfacing with the + virtual SIM card via the TCP based **vpcd** as can be found as part of the open source + [vsmartcard project](https://frankmorgner.github.io/vsmartcard/virtualsmartcard/README.html) + +Getting Started +--------------- + +### Building from source + +Install autoconf, automake, a C compiler and make +(on Debian: `apt install build-essential autoconf automake`), +then run: + +``` +$ autoreconf -i +$ ./configure +$ make +``` + +### Installing run time dependencies + +* Install the VPCD, which links smartsim into the PC/SC smart card APIs and pcsc-tools to for `gscriptor` and `pcsc_scan` (used for interacting with the sim card). + (On Debian: `apt install vsmartcard-vpcd pcsc-tools`) +* Install pysim [according to its description](https://git.osmocom.org/pysim/about/). + +### Running smartsim + +Wake up PC/SC, by briefly (or even permanently) running `pcsc_scan`, or a brief run of `socat - /run/pcscd/pcscd.comm`. + +``` +$ ./src/softsim/softsim + VPCD INFO softsim! + VPCD INFO connected. + FS INFO no file selected + STORAGE INFO requested file definition for 3f00 rom host file system: ./files/3f00.def + FS INFO no file selected + STORAGE INFO requested file definition for 3f00 rom host file system: ./files/3f00.def +... +``` + +When the program is running, `pcsc_scan` should show the presence of a card in the Virtual PCD reader: + +``` + Reader 0: Virtual PCD 00 00 + Event number: 19 + Card state: Card inserted, Shared Mode, + ATR: 3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 30 34 05 4B A9 +[...] +``` + +With `gscriptor`, selecting the Virtual PCD enables running the test scripts in `./gscriptor/`. + +pySim offers interaction with the running softsim card: + +``` +$ pySim-shell.py -p 0 +Using PC/SC reader interface +Waiting for card... +Autodetected card type: sysmoISIM-SJA2 +Info: Card is of type: UICC-SIM +[...] +pySIM-shell (MF)> +``` + +Operation compared to ETSI specifications +----------------------------------------- + +The SoftSIM largely operates as described by the relevant specifications +(ETSI TS 102 series and ISO 7816) +to the extent implemented and necessary. + +Aspects of its operation that the specifications do not describe are outlined here, +with details in the code's in-line documentation: + +* The life cycle of the card is expressed in the master file's life cycle bit: + while it is in creation or initialization state, access controls are suspended to allow bottstrapping the file system. + Consequently, the master file needs to be in one of these two states when it is created, + and the initialization is finished by executing the ACTIVATE command on the master file. + + This is aligned with how ISO 7816 expresses privileges (in that termination of the master file is equated to termination of card usage). + +* Data that needs to be persisted on the card + but is not accessed by the applications through the file system + still utilizes the file system as a consistent layer of abstraction for all the SoftSIM's persistent storage requirements. + + Data such as PINs and key material is stored in files whose access controls never allow access. + (Access control does not apply to read and write operations originating within the SoftSIM). + These files may use an extended range of file IDs (32bit rather than 16bit) that is in acccessible through regular file commands such as SELECT. + +* Requirements on the file system, summarized: + + * All files: Access control is exclusively managed through EF.ARR references. + * MF: At creation, life cycle is set to 3. + * ADF.USIM / EF.UST: Service 27 needs (GSM access) to be enabled. + +* Overview of proprietary files: + * A001: SIM authentication keys + * A002: SIM authentication sequence numbers + * A003: PINs and their state + * A004: TARs and keys for remote commands (OTA RFM) + +* Permissions granted to the remotes in OTA RFM are expressed in terms of PINs (CHVs) to allow unified management of permissions. + The PINs active with any given OTA TAR are hard-coded in `setup_ctx_from_tar` until the need for further customization arises; + by default, the ADM1 PIN is set. diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..387287b --- /dev/null +++ b/configure.ac @@ -0,0 +1,113 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +dnl Process this file with autoconf to produce a configure script +AC_INIT([softsim], + [1.0], + [info@sysmocom.de]) + +dnl *This* is the root dir, even if an install-sh exists in ../ or ../../ +AC_CONFIG_AUX_DIR([.]) + +AM_INIT_AUTOMAKE([dist-bzip2]) +AC_CONFIG_TESTDIR(tests) +AC_CONFIG_MACRO_DIRS([m4]) + +CFLAGS="$CFLAGS -std=c99" + +dnl kernel style compile messages +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +dnl checks for programs +AC_PROG_MAKE_SET +AC_PROG_CC +AC_PROG_INSTALL +LT_INIT + +dnl checks for libraries +AC_SEARCH_LIBS([dlopen], [dl dld], [LIBRARY_DL="$LIBS";LIBS=""]) +AC_SUBST(LIBRARY_DL) + +dnl checks for header files +AC_HEADER_STDC + +dnl Checks for typedefs, structures and compiler characteristics + +AC_ARG_ENABLE(sanitize, + [AS_HELP_STRING( + [--enable-sanitize], + [Compile with address sanitizer enabled], + )], + [sanitize=$enableval], [sanitize="no"]) +if test x"$sanitize" = x"yes" +then + CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined" + CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined" +fi + +AC_ARG_ENABLE(werror, + [AS_HELP_STRING( + [--enable-werror], + [Turn all compiler warnings into errors, with exceptions: + a) deprecation (allow upstream to mark deprecation without breaking builds); + b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds) + ] + )], + [werror=$enableval], [werror="no"]) +if test x"$werror" = x"yes" +then + WERROR_FLAGS="-Werror" + WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations" + WERROR_FLAGS+=" -Wno-error=cpp" # "#warning" + CFLAGS="$CFLAGS $WERROR_FLAGS" + CPPFLAGS="$CPPFLAGS $WERROR_FLAGS" +fi + +# The following test is taken from WebKit's webkit.m4 +saved_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS -fvisibility=hidden " +AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden]) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])], + [ AC_MSG_RESULT([yes]) + SYMBOL_VISIBILITY="-fvisibility=hidden"], + AC_MSG_RESULT([no])) +CFLAGS="$saved_CFLAGS" +AC_SUBST(SYMBOL_VISIBILITY) + +AC_ARG_ENABLE(profile, + [AS_HELP_STRING([--enable-profile], [Compile with profiling support enabled], )], + [profile=$enableval], [profile="no"]) +if test x"$profile" = x"yes" +then + CFLAGS="$CFLAGS -pg" + CPPFLAGS="$CPPFLAGS -pg" +fi + +AC_MSG_RESULT([CFLAGS="$CFLAGS"]) +AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"]) + +dnl Generate the output +AM_CONFIG_HEADER(config.h) + +AC_OUTPUT( + include/Makefile + include/onomondo/Makefile + include/onomondo/softsim/Makefile + src/Makefile + src/softsim/Makefile + src/softsim/crypto/Makefile + src/softsim/milenage/Makefile + src/softsim/uicc/Makefile + tests/Makefile + tests/atlocal + tests/list/Makefile + tests/btlv/Makefile + tests/ctlv/Makefile + tests/tlv8/Makefile + tests/utils/Makefile + tests/fcp/Makefile + tests/sms/Makefile + tests/aes/Makefile + tests/des/Makefile + tests/ota/Makefile + Makefile) diff --git a/files-creation/all.pysim b/files-creation/all.pysim new file mode 100644 index 0000000..160e51b --- /dev/null +++ b/files-creation/all.pysim @@ -0,0 +1,32 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +set debug true + +@@ softsim_create_files_minimal.pysim + +# When starting from an empty card, pysim does not recognize any structure; +# after creating the essential files in 01_create_files_minimal.pysim, it +# should reconsider. +equip + +@@ softsim_create_files.pysim + +select MF +select EF.ARR # 2f06 +@@ softsim_fill_files_efarr.pysim + +@@ softsim_fill_files_mf.pysim +@@ softsim_fill_files_usim.pysim + +select MF +select ADF.USIM +select EF.ARR # 6f06 +@@ softsim_fill_files_efarr.pysim + +# All files are created now; activating to switch access controls live + +activate_file MF +# The recommended way is to reset here -- but pySim doesn't do that, and +# selecting MF works just as well to rescan the access control information +select MF diff --git a/files-creation/softsim_create_files.pysim b/files-creation/softsim_create_files.pysim new file mode 100644 index 0000000..92fc8c4 --- /dev/null +++ b/files-creation/softsim_create_files.pysim @@ -0,0 +1,155 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +######################### +# MF and files under MF # +######################### + +#ETSI TS 102 221, section 13.3 +#Access conditions: 01 -- READ:ALW, UPDATE:PIN, DEACTIVATE/ACTIVATE:ADM +echo "create 2f05, EF.PL" +create_ef --shareable --ef-arr-file-id 2f06 --ef-arr-record-nr 1 --structure transparent --file-size 10 --short-file-id 5 2f05 + +#ETSI TS 102 221, section 13.4 +#Access conditions: 02 -- READ:ALW, UPDATE:ADM, DEACTIVATE/ACTIVATE:ADM +echo "create 2f06, EF.ARR" +create_ef --shareable --ef-arr-file-id 2f06 --ef-arr-record-nr 2 --structure linear_fixed --file-size 640 --record-length 40 --short-file-id 6 2f06 + +#ETSI TS 102 221, section 13.6 +#Access conditions: 02 -- READ:ALW, UPDATE:ADM, DEACTIVATE/ACTIVATE:ADM +echo "create 2f08, EF.UMPC" +create_ef --shareable --ef-arr-file-id 2f06 --ef-arr-record-nr 2 --structure transparent --file-size 5 --short-file-id 8 2f08 + +#SOFTSIM PROPRITARY +#Access conditions: 06 -- READ:ADM, UPDATE:ADM, DEACTIVATE/ACTIVATE:ADM +echo "create a001, EF.AUTHKEYS (propritary)" +create_ef --shareable --ef-arr-file-id 2f06 --ef-arr-record-nr 6 --structure transparent --file-size 33 a001 + +#SOFTSIM PROPRITARY +#Access conditions: 06 -- READ:ADM, UPDATE:ADM, DEACTIVATE/ACTIVATE:ADM +echo "create a002, EF.AUTHSEQ (propritary)" +create_ef --shareable --ef-arr-file-id 2f06 --ef-arr-record-nr 6 --structure transparent --file-size 264 a002 + +#SOFTSIM PROPRITARY +#Access conditions: 06 -- READ:ADM, UPDATE:ADM, DEACTIVATE/ACTIVATE:ADM +echo "create a003, EF.PIN (propritary)" +create_ef --shareable --ef-arr-file-id 2f06 --ef-arr-record-nr 6 --structure linear_fixed --file-size 66 --record-length 22 a003 + +#SOFTSIM PROPRITARY +#Access conditions: 06 -- READ:ADM, UPDATE:ADM, DEACTIVATE/ACTIVATE:ADM +echo "create a004, EF containing TAR keys (propritary)" +create_ef --shareable --ef-arr-file-id 2f06 --ef-arr-record-nr 6 --structure linear_fixed --file-size 114 --record-length 38 a004 + +#SOFTSIM PROPRITARY +#Access conditions: 06 -- READ:ADM, UPDATE:ADM, DEACTIVATE/ACTIVATE:ADM +echo "create a005, EF containing CNTR states (propritary)" +create_ef --shareable --ef-arr-file-id 2f06 --ef-arr-record-nr 6 --structure linear_fixed --file-size 33 --record-length 11 a005 + +######################### +# ADF.USIM # +######################### + +echo "create 7ff0 ADF.USIM" +#Access conditions: 15 -- Nothing is allowed +create_df --shareable --ef-arr-file-id 2f06 --ef-arr-record-nr 15 --aid a0000000871002ffffffff8907090000 7ff0 + +# Make ADF.USIM selectable and visible in pySim-shell +equip + +select ADF.USIM + +#3GPP TS 31.102, section 4.2.1 +#Access conditions: 01 -- READ:ALW, UPDATE:PIN, DEACTIVATE/ACTIVATE:ADM +echo "create 6f05, EF.LI" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 1 --structure transparent --file-size 10 --short-file-id 2 6f05 + +#3GPP TS 31.102, section 4.2.2 +#Access conditions: 05 -- READ:PIN, UPDATE:ADM, DEACTIVATE/ACTIVATE:ADM +echo "create 6f07, EF.IMSI" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 5 --structure transparent --file-size 9 --short-file-id 7 6f07 + +#3GPP TS 31.102, section 4.2.3 +#Access conditions: 04 -- READ:PIN, UPDATE:PIN, DEACTIVATE/ACTIVATE:ADM +echo "create 6f08, EF.Keys" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 4 --structure transparent --file-size 33 --short-file-id 8 6f08 + +#3GPP TS 31.102, section 4.2.4 +#Access conditions: 04 -- READ:PIN, UPDATE:PIN, DEACTIVATE/ACTIVATE:ADM +echo "create 6f09, EF.KeysPS" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 4 --structure transparent --file-size 33 --short-file-id 9 6f09 + +#3GPP TS 31.102, section 4.2.6 +#Access conditions: 05 -- READ:PIN, UPDATE:ADM, DEACTIVATE/ACTIVATE:ADM +echo "create 6f31, EF.HPPLMN" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 5 --structure transparent --file-size 1 --short-file-id 18 6f31 + +#3GPP TS 31.102, section 4.2.8 +#Access conditions: 05 -- READ:PIN, UPDATE:ADM, DEACTIVATE/ACTIVATE:ADM +echo "create 6f38, EF.UST" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 5 --structure transparent --file-size 15 6f38 + +#3GPP TS 31.102, section 4.2.15 +#Access conditions: 05 -- READ:PIN, UPDATE:ADM, DEACTIVATE/ACTIVATE:ADM +echo "create 6f78, EF.ACC" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 5 --structure transparent --file-size 2 --short-file-id 6 6f78 + +#3GPP TS 31.102, section 4.2.16 +#Access conditions: 04 -- READ:PIN, UPDATE:PIN, DEACTIVATE/ACTIVATE:ADM +echo "create 6f7b, EF.FPLMN" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 4 --structure transparent --file-size 12 --short-file-id 13 6f7b + +#3GPP TS 31.102, section 4.2.17 +#Access conditions: 04 -- READ:PIN, UPDATE:PIN, DEACTIVATE/ACTIVATE:ADM +echo "create 6f7e, EF.LOCI" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 4 --structure transparent --file-size 11 --short-file-id 11 6f7e + +#3GPP TS 31.102, section 4.2.18 +#Access conditions: 02 -- READ:ALW, UPDATE:ADM, DEACTIVATE/ACTIVATE:ADM +echo "create 6fad, EF.AD" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 2 --structure transparent --file-size 4 6fad + +#3GPP TS 31.102, section 4.2.21 +#Access conditions: 02 -- READ:ALW, UPDATE:ADM, DEACTIVATE/ACTIVATE:ADM +echo "create 6fb7, EF.ECC" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 2 --structure linear_fixed --file-size 80 --record-length 16 --short-file-id 1 6fb7 + +#3GPP TS 31.102, section 4.2.23 +#Access conditions: 04 -- READ:PIN, UPDATE:PIN, DEACTIVATE/ACTIVATE:ADM +echo "create 6f73, EF.PSLOCI" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 4 --structure transparent --file-size 14 --short-file-id 12 6f73 + +#3GPP TS 31.102, section 4.2.27 +#Access conditions: 04 -- READ:PIN, UPDATE:PIN, DEACTIVATE/ACTIVATE:ADM +echo "create 6f42, EF.SMSP" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 2 --structure linear_fixed --file-size 104 --record-length 52 6f42 + +#3GPP TS 31.102, section 4.2.51 +#Access conditions: 04 -- READ:PIN, UPDATE:PIN, DEACTIVATE/ACTIVATE:ADM +echo "create 6f5b, EF.START-HFN" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 4 --structure transparent --file-size 6 --short-file-id 15 6f5b + +#3GPP TS 31.102, section 4.2.52 +#Access conditions: 05 -- READ:PIN, UPDATE:ADM, DEACTIVATE/ACTIVATE:ADM +echo "create 6f5c, EF.THRESHOLD" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 5 --structure transparent --file-size 3 --short-file-id 16 6f5c + +#3GPP TS 31.102, section 4.2.55 +#Access conditions: 02 -- READ:ALW, UPDATE:ADM, DEACTIVATE/ACTIVATE:ADM +#(Note that this is synchronized with MF/2f06 for consistency of ef-arr-record-nr values) +echo "create 6f06, EF.ARR" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 2 --structure linear_fixed --file-size 640 --record-length 40 --short-file-id 23 6f06 + +#3GPP TS 31.102, section 4.2.57 +#Access conditions: 04 -- READ:PIN, UPDATE:PIN, DEACTIVATE/ACTIVATE:ADM +echo "create 6fc4, EF.NETPAR" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 4 --structure transparent --file-size 64 6fc4 + +#3GPP TS 31.102, section 4.2.91 +#Access conditions: 04 -- READ:PIN, UPDATE:PIN, DEACTIVATE/ACTIVATE:ADM +echo "create 6fe3, EF.EPSLOCI" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 4 --structure transparent --file-size 18 --short-file-id 3 6fe3 + +#3GPP TS 31.102, section 4.2.92 +#Access conditions: 04 -- READ:PIN, UPDATE:PIN, DEACTIVATE/ACTIVATE:ADM +echo "create 6fe4, EF.EPSNSC" +create_ef --shareable --ef-arr-file-id 6f06 --ef-arr-record-nr 4 --structure linear_fixed --file-size 54 --record-length 54 --short-file-id 24 6fe4 diff --git a/files-creation/softsim_create_files_minimal.pysim b/files-creation/softsim_create_files_minimal.pysim new file mode 100644 index 0000000..d29a787 --- /dev/null +++ b/files-creation/softsim_create_files_minimal.pysim @@ -0,0 +1,26 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +######################### +# MF and files under MF # +######################### + +# This script creates a minimal set of files so that pySim-shell is able to +# recognize the card as an UICC card. +# +# The card is created in "Initialization" mode, with permission checking +# disabled. + +echo "create 3f00, MF" +#Access conditions: 15 -- nothing allowed +apdu 00E000001D621B8202782183023f00A5098001F18701008801008a01038b032f060f --expect-sw 9000 + +#ETSI TS 102 221, section 13.1 +#Access conditions: 02 -- READ:ALW, UPDATE:ADM, DEACTIVATE/ACTIVATE:ADM +echo "create 2f00, EF.DIR" +apdu 00E000001b621982044221002683022F008A01058B032F06028002004C8801F0 --expect-sw 9000 + +#ETSI TS 102 221, section 13.2 +#Access conditions: 03 -- READ:ALW, UPDATE:NEV, DEACTIVATE/ACTIVATE:ADM +echo "create 2fe2, EF.ICCID" +apdu 00E000001962178202412183022FE28A01058B032F06038002000A880110 --expect-sw 9000 diff --git a/files-creation/softsim_fill_files_efarr.pysim b/files-creation/softsim_fill_files_efarr.pysim new file mode 100644 index 0000000..5ed0fcf --- /dev/null +++ b/files-creation/softsim_fill_files_efarr.pysim @@ -0,0 +1,40 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +########################## +# EF.ARR -- default ACLs # +########################## + +# All rules have ACTIVATE/DEACTIVATE:ADM + +### Records for EFs + +# 1: Access conditions: READ:ALW, UPDATE:PIN +update_record_decoded 1 '[[{"access_mode": ["read_search_compare"]}, {"always": null}], [{"access_mode": ["update_erase"]}, {"control_reference_template": "PIN1"}], [{"access_mode": ["activate_file", "deactivate_file"]}, {"control_reference_template": "ADM1"}]]' + +# 2: Access conditions: READ:ALW, UPDATE:ADM +update_record_decoded 2 '[[{"access_mode": ["read_search_compare"]}, {"always": null}], [{"access_mode": ["update_erase"]}, {"control_reference_template": "ADM1"}], [{"access_mode": ["activate_file", "deactivate_file"]}, {"control_reference_template": "ADM1"}]]' + +# 3: Access conditions: READ:ALW, UPDATE:NEV +update_record_decoded 3 '[[{"access_mode": ["read_search_compare"]}, {"always": null}], [{"access_mode": ["activate_file", "deactivate_file"]}, {"control_reference_template": "ADM1"}]]' + +# 4: Access conditions: READ:PIN, UPDATE:PIN, DEACTIVATE/ACTIVATE:ADM +update_record_decoded 4 '[[{"access_mode": ["read_search_compare"]}, {"control_reference_template": "PIN1"}], [{"access_mode": ["update_erase"]}, {"control_reference_template": "PIN1"}], [{"access_mode": ["activate_file", "deactivate_file"]}, {"control_reference_template": "ADM1"}]]' + +# 5: Access conditions: READ:PIN, UPDATE:ADM, DEACTIVATE/ACTIVATE:ADM +update_record_decoded 5 '[[{"access_mode": ["read_search_compare"]}, {"control_reference_template": "PIN1"}], [{"access_mode": ["update_erase"]}, {"control_reference_template": "ADM1"}], [{"access_mode": ["activate_file", "deactivate_file"]}, {"control_reference_template": "ADM1"}]]' + +# 6: Access conditions: READ:ADM, UPDATE:ADM, DEACTIVATE/ACTIVATE:ADM +update_record_decoded 6 '[[{"access_mode": ["read_search_compare"]}, {"control_reference_template": "ADM1"}], [{"access_mode": ["update_erase"]}, {"control_reference_template": "ADM1"}], [{"access_mode": ["activate_file", "deactivate_file"]}, {"control_reference_template": "ADM1"}]]' + +### Records for DFs + +# Note that any access modes given here would be named after the bits' +# semantics when applied to an EF, but are meant as their equivalent rules for +# DFs (see ) + +# 15: No access checked access at all. (Ie. no creating child files or DFs, no +# activating or deactivating the DF; deleting files can still be allowed as TS +# 102 222 cards only check for the rules on self for file deletion, not on the +# parent) +update_record_decoded 15 '[]' diff --git a/files-creation/softsim_fill_files_mf.pysim b/files-creation/softsim_fill_files_mf.pysim new file mode 100644 index 0000000..0d11829 --- /dev/null +++ b/files-creation/softsim_fill_files_mf.pysim @@ -0,0 +1,67 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +######################### +# MF and files under MF # +######################### + +#ETSI TS 102 221, section 13.1 +echo "populate 2f00, EF.DIR" +select MF +select EF.DIR +update_record 1 61194F10A0000000871002FFFFFFFF890709000050055553696D31FFFFFFFFFFFFFFFFFFFFFF + +#ETSI TS 102 221, section 13.2 +echo "populate 2fe2, EF.ICCID" +select MF +select EF.ICCID +update_binary 00112233445566778899 + +#ETSI TS 102 221, section 13.6 +echo "populate 2f08, EF.UMPC" +select MF +select EF.UMPC +update_binary 3C05020000 + +#SOFTSIM PROPRITARY +echo "populete a001, EF.AUTHKEYS (propritary)" +# 16 byte K|16 byte OP/OPc|1 byte flag| +# flag: 0x01 = OP/OPc is OP, 0x00 = OP/OPc is OPc +select MF +select A001 +update_binary 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f00 + +#SOFTSIM PROPRITARY +echo "populete a002, EF.AUTHSEQ (propritary)" +# File format: +# |256 byte SEQ|8 byte delta| +select MF +select A002 +update_binary 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000 + +#SOFTSIM PROPRITARY +echo "populete a003, EF.PIN (propritary)" +# File format: see struct pin_context in uicc_pin.c +select MF +select A003 +update_record 1 0003000a000131323334ffffffff3132333435363738 +update_record 2 0003000a008131323334ffffffff3132333435363738 +update_record 3 01030000000a31323334ffffffffffffffffffffffff + +#SOFTSIM PROPRITARY +echo "populate a004, TARs (propritary)" +# File format: see struct tar_record in uicc_remote_command.c +select MF +select A004 +# 3 byte TAR|1 byte MSL|1 byte KIC|1 byte KID|16 byte Kc|16 byte Kd| +# In this example: TAR=b00011, MSL=06, KIC=03, KID=03, Kc=00112233445566778899aabbccddeeff, Kd=0123456789abcdef0123456701234567 +update_record 1 b0001106030300112233445566778899aabbccddeeff0123456789abcdef0123456701234567 + +#SOFTSIM PROPRITARY +echo "populate a005, TAR/CNTR (propritary)" +# File format: see struct cntr_record in uicc_remote_command.c +select MF +select A005 +# 3 byte TAR|3 byte TAR mask|5 byte CNTR| +# In this example: TAR=b00011, TAR mask=ffffff, CNTR=0000000000 +update_record 1 b00011ffffff0000000000 diff --git a/files-creation/softsim_fill_files_usim.pysim b/files-creation/softsim_fill_files_usim.pysim new file mode 100644 index 0000000..fa43962 --- /dev/null +++ b/files-creation/softsim_fill_files_usim.pysim @@ -0,0 +1,109 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +######################### +# ADF.USIM # +######################### + +#3GPP TS 31.102, section 4.2.2 +echo "populate 6f07, EF.IMSI" +select MF +select ADF.USIM +select EF.IMSI +update_binary 080910100000000010 + +#3GPP TS 31.102, section 4.2.3 +echo "populate 6f08, EF.Keys" +select MF +select ADF.USIM +select EF.Keys +update_binary 07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + +#3GPP TS 31.102, section 4.2.4 +echo "populate 6f09, EF.KeysPS" +select MF +select ADF.USIM +select EF.KeysPS +update_binary 07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + +#3GPP TS 31.102, section 4.2.6 +echo "populate 6f31, EF.HPPLMN" +select MF +select ADF.USIM +select EF.HPPLMN +update_binary 05 + +#3GPP TS 31.102, section 4.2.8 +echo "populate 6f38, EF.UST" +select MF +select ADF.USIM +select EF.UST +update_binary 0008000c2100000000001000000000 + +#3GPP TS 31.102, section 4.2.15 +echo "populate 6f78, EF.ACC" +select MF +select ADF.USIM +select EF.ACC +update_binary 03ff + +#3GPP TS 31.102, section 4.2.17 +echo "populate 6f7e, EF.LOCI" +select MF +select ADF.USIM +select EF.LOCI +update_binary ffffffffffffff0000ff01 + +#3GPP TS 31.102, section 4.2.18 +echo "populate 6fad, EF.AD" +select MF +select ADF.USIM +select EF.AD +update_binary 01000803 + + +#3GPP TS 31.102, section 4.2.21 +echo "populate 6f73, EF.ECC" +select MF +select ADF.USIM +select EF.ECC +update_record 1 ffffffffffffffffffffffffffffff00 +update_record 2 ffffffffffffffffffffffffffffff00 +update_record 3 ffffffffffffffffffffffffffffff00 +update_record 4 ffffffffffffffffffffffffffffff00 +update_record 5 ffffffffffffffffffffffffffffff00 + +#3GPP TS 31.102, section 4.2.23 +echo "populate 6f07, EF.PSLOCI" +select MF +select ADF.USIM +select EF.PSLOCI +update_binary ffffffffffffffffff000000ff01 + +#3GPP TS 31.102, section 4.2.27 +echo "populate 6f42, EF.SMSP" +select MF +select ADF.USIM +select EF.SMSP +update_record 1 ffffffffffffffffffffffffffffffffffffffffffffffffe5ffffffffffffffffffffffff0791447779078484ffffffffff00a8 + +#3GPP TS 31.102, section 4.2.51 +echo "populate 6f5b, EF.START-HFN" +select MF +select ADF.USIM +select EF.START-HFN +update_binary f00000f00000 + +#3GPP TS 31.102, section 4.2.91 +echo "populate 6fe3, EF.EPSLOCI" +select MF +select ADF.USIM +select EF.EPSLOCI +update_binary ffffffffffffffffffffffffffffff000001 + +#3GPP TS 31.102, section 4.2.92 +echo "populate 6fe4, EF.EPSNSC" +select MF +select ADF.USIM +select EF.EPSNSC +update_record 1 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff diff --git a/files/3f00.def b/files/3f00.def new file mode 100644 index 0000000..b060e0c --- /dev/null +++ b/files/3f00.def @@ -0,0 +1 @@ +621b8202782183023f00a5098001f18701008801008a01058b032f060f \ No newline at end of file diff --git a/files/3f00/2f00 b/files/3f00/2f00 new file mode 100644 index 0000000..a83ffbc --- /dev/null +++ b/files/3f00/2f00 @@ -0,0 +1 @@ +61194f10a0000000871002ffffffff890709000050055553696d31ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \ No newline at end of file diff --git a/files/3f00/2f00.def b/files/3f00/2f00.def new file mode 100644 index 0000000..6d2b800 --- /dev/null +++ b/files/3f00/2f00.def @@ -0,0 +1 @@ +621a8205422100260283022f008a01058b032f06028002004c8801f0 \ No newline at end of file diff --git a/files/3f00/2f05 b/files/3f00/2f05 new file mode 100644 index 0000000..d69d556 --- /dev/null +++ b/files/3f00/2f05 @@ -0,0 +1 @@ +ffffffffffffffffffff \ No newline at end of file diff --git a/files/3f00/2f05.def b/files/3f00/2f05.def new file mode 100644 index 0000000..23e8bad --- /dev/null +++ b/files/3f00/2f05.def @@ -0,0 +1 @@ +62178202412183022f058a01058b032f06018002000a880128 \ No newline at end of file diff --git a/files/3f00/2f06 b/files/3f00/2f06 new file mode 100644 index 0000000..51718bf --- /dev/null +++ b/files/3f00/2f06 @@ -0,0 +1 @@ +8001019000800102a406830101950108800100a40683010a950108ffffffffffffffffffffffffff8001019000800102a40683010a950108800100a40683010a950108ffffffffffffffffffffffffff8001019000800100a40683010a950108ffffffffffffffffffffffffffffffffffffffffffffffff800101a406830101950108800102a406830101950108800100a40683010a950108ffffffffffffff800101a406830101950108800102a40683010a950108800100a40683010a950108ffffffffffffff800101a40683010a950108800102a40683010a950108800100a40683010a950108ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \ No newline at end of file diff --git a/files/3f00/2f06.def b/files/3f00/2f06.def new file mode 100644 index 0000000..47d0007 --- /dev/null +++ b/files/3f00/2f06.def @@ -0,0 +1 @@ +621a8205422100281083022f068a01058b032f060280020280880130 \ No newline at end of file diff --git a/files/3f00/2f08 b/files/3f00/2f08 new file mode 100644 index 0000000..4139135 --- /dev/null +++ b/files/3f00/2f08 @@ -0,0 +1 @@ +3c05020000 \ No newline at end of file diff --git a/files/3f00/2f08.def b/files/3f00/2f08.def new file mode 100644 index 0000000..4145416 --- /dev/null +++ b/files/3f00/2f08.def @@ -0,0 +1 @@ +62178202412183022f088a01058b032f060280020005880140 \ No newline at end of file diff --git a/files/3f00/2fe2 b/files/3f00/2fe2 new file mode 100644 index 0000000..7bb7e37 --- /dev/null +++ b/files/3f00/2fe2 @@ -0,0 +1 @@ +00112233445566778899 \ No newline at end of file diff --git a/files/3f00/2fe2.def b/files/3f00/2fe2.def new file mode 100644 index 0000000..29b4bfa --- /dev/null +++ b/files/3f00/2fe2.def @@ -0,0 +1 @@ +62178202412183022fe28a01058b032f06038002000a880110 \ No newline at end of file diff --git a/files/3f00/5f100001 b/files/3f00/5f100001 new file mode 100644 index 0000000..2d49116 --- /dev/null +++ b/files/3f00/5f100001 @@ -0,0 +1 @@ +ffff2fe2ffffffff2f052f06ffff2f08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2f00ffff \ No newline at end of file diff --git a/files/3f00/5f100001.def b/files/3f00/5f100001.def new file mode 100644 index 0000000..e09275b --- /dev/null +++ b/files/3f00/5f100001.def @@ -0,0 +1 @@ +62108205022100021f83045f10000180013e \ No newline at end of file diff --git a/files/3f00/7ff0.def b/files/3f00/7ff0.def new file mode 100644 index 0000000..a0d1490 --- /dev/null +++ b/files/3f00/7ff0.def @@ -0,0 +1 @@ +62228202782183027ff08410a0000000871002ffffffff89070900008a01058b032f060f \ No newline at end of file diff --git a/files/3f00/7ff0/5f100001 b/files/3f00/7ff0/5f100001 new file mode 100644 index 0000000..fe8213c --- /dev/null +++ b/files/3f00/7ff0/5f100001 @@ -0,0 +1 @@ +6fb76f056fe3ffffffff6f786f076f086f09ffff6f7e6f736f7bffff6f5b6f5cffff6f31ffffffffffffffff6f066fe4ffffffffffffffffffffffffffff \ No newline at end of file diff --git a/files/3f00/7ff0/5f100001.def b/files/3f00/7ff0/5f100001.def new file mode 100644 index 0000000..e09275b --- /dev/null +++ b/files/3f00/7ff0/5f100001.def @@ -0,0 +1 @@ +62108205022100021f83045f10000180013e \ No newline at end of file diff --git a/files/3f00/7ff0/6f05 b/files/3f00/7ff0/6f05 new file mode 100644 index 0000000..d69d556 --- /dev/null +++ b/files/3f00/7ff0/6f05 @@ -0,0 +1 @@ +ffffffffffffffffffff \ No newline at end of file diff --git a/files/3f00/7ff0/6f05.def b/files/3f00/7ff0/6f05.def new file mode 100644 index 0000000..26b4598 --- /dev/null +++ b/files/3f00/7ff0/6f05.def @@ -0,0 +1 @@ +62178202412183026f058a01058b036f06018002000a880110 \ No newline at end of file diff --git a/files/3f00/7ff0/6f06 b/files/3f00/7ff0/6f06 new file mode 100644 index 0000000..51718bf --- /dev/null +++ b/files/3f00/7ff0/6f06 @@ -0,0 +1 @@ +8001019000800102a406830101950108800100a40683010a950108ffffffffffffffffffffffffff8001019000800102a40683010a950108800100a40683010a950108ffffffffffffffffffffffffff8001019000800100a40683010a950108ffffffffffffffffffffffffffffffffffffffffffffffff800101a406830101950108800102a406830101950108800100a40683010a950108ffffffffffffff800101a406830101950108800102a40683010a950108800100a40683010a950108ffffffffffffff800101a40683010a950108800102a40683010a950108800100a40683010a950108ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \ No newline at end of file diff --git a/files/3f00/7ff0/6f06.def b/files/3f00/7ff0/6f06.def new file mode 100644 index 0000000..8a31284 --- /dev/null +++ b/files/3f00/7ff0/6f06.def @@ -0,0 +1 @@ +621a8205422100281083026f068a01058b036f0602800202808801b8 \ No newline at end of file diff --git a/files/3f00/7ff0/6f07 b/files/3f00/7ff0/6f07 new file mode 100644 index 0000000..764e341 --- /dev/null +++ b/files/3f00/7ff0/6f07 @@ -0,0 +1 @@ +080910100000000010 \ No newline at end of file diff --git a/files/3f00/7ff0/6f07.def b/files/3f00/7ff0/6f07.def new file mode 100644 index 0000000..52a01b7 --- /dev/null +++ b/files/3f00/7ff0/6f07.def @@ -0,0 +1 @@ +62178202412183026f078a01058b036f060580020009880138 \ No newline at end of file diff --git a/files/3f00/7ff0/6f08 b/files/3f00/7ff0/6f08 new file mode 100644 index 0000000..eb6ae0c --- /dev/null +++ b/files/3f00/7ff0/6f08 @@ -0,0 +1 @@ +07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \ No newline at end of file diff --git a/files/3f00/7ff0/6f08.def b/files/3f00/7ff0/6f08.def new file mode 100644 index 0000000..7883b73 --- /dev/null +++ b/files/3f00/7ff0/6f08.def @@ -0,0 +1 @@ +62178202412183026f088a01058b036f060480020021880140 \ No newline at end of file diff --git a/files/3f00/7ff0/6f09 b/files/3f00/7ff0/6f09 new file mode 100644 index 0000000..eb6ae0c --- /dev/null +++ b/files/3f00/7ff0/6f09 @@ -0,0 +1 @@ +07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \ No newline at end of file diff --git a/files/3f00/7ff0/6f09.def b/files/3f00/7ff0/6f09.def new file mode 100644 index 0000000..97289c6 --- /dev/null +++ b/files/3f00/7ff0/6f09.def @@ -0,0 +1 @@ +62178202412183026f098a01058b036f060480020021880148 \ No newline at end of file diff --git a/files/3f00/7ff0/6f31 b/files/3f00/7ff0/6f31 new file mode 100644 index 0000000..6bd088a --- /dev/null +++ b/files/3f00/7ff0/6f31 @@ -0,0 +1 @@ +05 \ No newline at end of file diff --git a/files/3f00/7ff0/6f31.def b/files/3f00/7ff0/6f31.def new file mode 100644 index 0000000..972e5ac --- /dev/null +++ b/files/3f00/7ff0/6f31.def @@ -0,0 +1 @@ +62178202412183026f318a01058b036f060580020001880190 \ No newline at end of file diff --git a/files/3f00/7ff0/6f38 b/files/3f00/7ff0/6f38 new file mode 100644 index 0000000..27db631 --- /dev/null +++ b/files/3f00/7ff0/6f38 @@ -0,0 +1 @@ +0008000c2100000000001000000000 \ No newline at end of file diff --git a/files/3f00/7ff0/6f38.def b/files/3f00/7ff0/6f38.def new file mode 100644 index 0000000..5024ea6 --- /dev/null +++ b/files/3f00/7ff0/6f38.def @@ -0,0 +1 @@ +62168202412183026f388a01058b036f06058002000f8800 \ No newline at end of file diff --git a/files/3f00/7ff0/6f42 b/files/3f00/7ff0/6f42 new file mode 100644 index 0000000..0e121ba --- /dev/null +++ b/files/3f00/7ff0/6f42 @@ -0,0 +1 @@ +ffffffffffffffffffffffffffffffffffffffffffffffffe5ffffffffffffffffffffffff0791447779078484ffffffffff00a8ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \ No newline at end of file diff --git a/files/3f00/7ff0/6f42.def b/files/3f00/7ff0/6f42.def new file mode 100644 index 0000000..a89426d --- /dev/null +++ b/files/3f00/7ff0/6f42.def @@ -0,0 +1 @@ +62198205422100340283026f428a01058b036f0602800200688800 \ No newline at end of file diff --git a/files/3f00/7ff0/6f5b b/files/3f00/7ff0/6f5b new file mode 100644 index 0000000..e9a5cc7 --- /dev/null +++ b/files/3f00/7ff0/6f5b @@ -0,0 +1 @@ +f00000f00000 \ No newline at end of file diff --git a/files/3f00/7ff0/6f5b.def b/files/3f00/7ff0/6f5b.def new file mode 100644 index 0000000..a2aa102 --- /dev/null +++ b/files/3f00/7ff0/6f5b.def @@ -0,0 +1 @@ +62178202412183026f5b8a01058b036f060480020006880178 \ No newline at end of file diff --git a/files/3f00/7ff0/6f5c b/files/3f00/7ff0/6f5c new file mode 100644 index 0000000..de53aff --- /dev/null +++ b/files/3f00/7ff0/6f5c @@ -0,0 +1 @@ +ffffff \ No newline at end of file diff --git a/files/3f00/7ff0/6f5c.def b/files/3f00/7ff0/6f5c.def new file mode 100644 index 0000000..7d36bd8 --- /dev/null +++ b/files/3f00/7ff0/6f5c.def @@ -0,0 +1 @@ +62178202412183026f5c8a01058b036f060580020003880180 \ No newline at end of file diff --git a/files/3f00/7ff0/6f73 b/files/3f00/7ff0/6f73 new file mode 100644 index 0000000..da0c286 --- /dev/null +++ b/files/3f00/7ff0/6f73 @@ -0,0 +1 @@ +ffffffffffffffffff000000ff01 \ No newline at end of file diff --git a/files/3f00/7ff0/6f73.def b/files/3f00/7ff0/6f73.def new file mode 100644 index 0000000..1ca2c71 --- /dev/null +++ b/files/3f00/7ff0/6f73.def @@ -0,0 +1 @@ +62178202412183026f738a01058b036f06048002000e880160 \ No newline at end of file diff --git a/files/3f00/7ff0/6f78 b/files/3f00/7ff0/6f78 new file mode 100644 index 0000000..8fe809b --- /dev/null +++ b/files/3f00/7ff0/6f78 @@ -0,0 +1 @@ +03ff \ No newline at end of file diff --git a/files/3f00/7ff0/6f78.def b/files/3f00/7ff0/6f78.def new file mode 100644 index 0000000..e94cea7 --- /dev/null +++ b/files/3f00/7ff0/6f78.def @@ -0,0 +1 @@ +62178202412183026f788a01058b036f060580020002880130 \ No newline at end of file diff --git a/files/3f00/7ff0/6f7b b/files/3f00/7ff0/6f7b new file mode 100644 index 0000000..186f479 --- /dev/null +++ b/files/3f00/7ff0/6f7b @@ -0,0 +1 @@ +ffffffffffffffffffffffff \ No newline at end of file diff --git a/files/3f00/7ff0/6f7b.def b/files/3f00/7ff0/6f7b.def new file mode 100644 index 0000000..b12d499 --- /dev/null +++ b/files/3f00/7ff0/6f7b.def @@ -0,0 +1 @@ +62178202412183026f7b8a01058b036f06048002000c880168 \ No newline at end of file diff --git a/files/3f00/7ff0/6f7e b/files/3f00/7ff0/6f7e new file mode 100644 index 0000000..ecc5f63 --- /dev/null +++ b/files/3f00/7ff0/6f7e @@ -0,0 +1 @@ +ffffffffffffff0000ff01 \ No newline at end of file diff --git a/files/3f00/7ff0/6f7e.def b/files/3f00/7ff0/6f7e.def new file mode 100644 index 0000000..866439f --- /dev/null +++ b/files/3f00/7ff0/6f7e.def @@ -0,0 +1 @@ +62178202412183026f7e8a01058b036f06048002000b880158 \ No newline at end of file diff --git a/files/3f00/7ff0/6fad b/files/3f00/7ff0/6fad new file mode 100644 index 0000000..94e2c1e --- /dev/null +++ b/files/3f00/7ff0/6fad @@ -0,0 +1 @@ +01000803 \ No newline at end of file diff --git a/files/3f00/7ff0/6fad.def b/files/3f00/7ff0/6fad.def new file mode 100644 index 0000000..7cfde7f --- /dev/null +++ b/files/3f00/7ff0/6fad.def @@ -0,0 +1 @@ +62168202412183026fad8a01058b036f0602800200048800 \ No newline at end of file diff --git a/files/3f00/7ff0/6fb7 b/files/3f00/7ff0/6fb7 new file mode 100644 index 0000000..d20b678 --- /dev/null +++ b/files/3f00/7ff0/6fb7 @@ -0,0 +1 @@ +ffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffff00 \ No newline at end of file diff --git a/files/3f00/7ff0/6fb7.def b/files/3f00/7ff0/6fb7.def new file mode 100644 index 0000000..7dcf8c2 --- /dev/null +++ b/files/3f00/7ff0/6fb7.def @@ -0,0 +1 @@ +621a8205422100100583026fb78a01058b036f060280020050880108 \ No newline at end of file diff --git a/files/3f00/7ff0/6fc4 b/files/3f00/7ff0/6fc4 new file mode 100644 index 0000000..af7d470 --- /dev/null +++ b/files/3f00/7ff0/6fc4 @@ -0,0 +1 @@ +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \ No newline at end of file diff --git a/files/3f00/7ff0/6fc4.def b/files/3f00/7ff0/6fc4.def new file mode 100644 index 0000000..b972cab --- /dev/null +++ b/files/3f00/7ff0/6fc4.def @@ -0,0 +1 @@ +62168202412183026fc48a01058b036f0604800200408800 \ No newline at end of file diff --git a/files/3f00/7ff0/6fe3 b/files/3f00/7ff0/6fe3 new file mode 100644 index 0000000..5a1e83e --- /dev/null +++ b/files/3f00/7ff0/6fe3 @@ -0,0 +1 @@ +ffffffffffffffffffffffffffffff000001 \ No newline at end of file diff --git a/files/3f00/7ff0/6fe3.def b/files/3f00/7ff0/6fe3.def new file mode 100644 index 0000000..ba8fe20 --- /dev/null +++ b/files/3f00/7ff0/6fe3.def @@ -0,0 +1 @@ +62178202412183026fe38a01058b036f060480020012880118 \ No newline at end of file diff --git a/files/3f00/7ff0/6fe4 b/files/3f00/7ff0/6fe4 new file mode 100644 index 0000000..2e27436 --- /dev/null +++ b/files/3f00/7ff0/6fe4 @@ -0,0 +1 @@ +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \ No newline at end of file diff --git a/files/3f00/7ff0/6fe4.def b/files/3f00/7ff0/6fe4.def new file mode 100644 index 0000000..f4c0218 --- /dev/null +++ b/files/3f00/7ff0/6fe4.def @@ -0,0 +1 @@ +621a8205422100360183026fe48a01058b036f0604800200368801c0 \ No newline at end of file diff --git a/files/3f00/a001 b/files/3f00/a001 new file mode 100644 index 0000000..8ca7424 --- /dev/null +++ b/files/3f00/a001 @@ -0,0 +1 @@ +000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f00 \ No newline at end of file diff --git a/files/3f00/a001.def b/files/3f00/a001.def new file mode 100644 index 0000000..3949416 --- /dev/null +++ b/files/3f00/a001.def @@ -0,0 +1 @@ +6216820241218302a0018a01058b032f0606800200218800 \ No newline at end of file diff --git a/files/3f00/a002 b/files/3f00/a002 new file mode 100644 index 0000000..300ef93 --- /dev/null +++ b/files/3f00/a002 @@ -0,0 +1 @@ +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000 \ No newline at end of file diff --git a/files/3f00/a002.def b/files/3f00/a002.def new file mode 100644 index 0000000..4d09b83 --- /dev/null +++ b/files/3f00/a002.def @@ -0,0 +1 @@ +6216820241218302a0028a01058b032f0606800201088800 \ No newline at end of file diff --git a/files/3f00/a003 b/files/3f00/a003 new file mode 100644 index 0000000..4bbb731 --- /dev/null +++ b/files/3f00/a003 @@ -0,0 +1 @@ +0003000a000131323334ffffffff31323334353637380003000a008131323334ffffffff313233343536373801030000000a31323334ffffffffffffffffffffffff \ No newline at end of file diff --git a/files/3f00/a003.def b/files/3f00/a003.def new file mode 100644 index 0000000..b316072 --- /dev/null +++ b/files/3f00/a003.def @@ -0,0 +1 @@ +6219820542210016038302a0038a01058b032f0606800200428800 \ No newline at end of file diff --git a/files/3f00/a004 b/files/3f00/a004 new file mode 100644 index 0000000..32fd2d4 --- /dev/null +++ b/files/3f00/a004 @@ -0,0 +1 @@ +b0001106030300112233445566778899aabbccddeeff0123456789abcdef0123456701234567ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \ No newline at end of file diff --git a/files/3f00/a004.def b/files/3f00/a004.def new file mode 100644 index 0000000..dc7d010 --- /dev/null +++ b/files/3f00/a004.def @@ -0,0 +1 @@ +6219820542210026038302a0048a01058b032f0606800200728800 \ No newline at end of file diff --git a/files/3f00/a005 b/files/3f00/a005 new file mode 100644 index 0000000..e83df2e --- /dev/null +++ b/files/3f00/a005 @@ -0,0 +1 @@ +b00011ffffff0000000000ffffffffffffffffffffffffffffffffffffffffffff \ No newline at end of file diff --git a/files/3f00/a005.def b/files/3f00/a005.def new file mode 100644 index 0000000..2f6fdf0 --- /dev/null +++ b/files/3f00/a005.def @@ -0,0 +1 @@ +621982054221000b038302a0058a01058b032f0606800200218800 \ No newline at end of file diff --git a/files/3f00/a1df1d01 b/files/3f00/a1df1d01 new file mode 100644 index 0000000..0fe5c82 --- /dev/null +++ b/files/3f00/a1df1d01 @@ -0,0 +1 @@ +a0000000871002ffffffff89070900007ff0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \ No newline at end of file diff --git a/files/3f00/a1df1d01.def b/files/3f00/a1df1d01.def new file mode 100644 index 0000000..700382e --- /dev/null +++ b/files/3f00/a1df1d01.def @@ -0,0 +1 @@ +6211820502210012108304a1df1d0180020120 \ No newline at end of file diff --git a/gscriptor/chv_change_test.scriptor b/gscriptor/chv_change_test.scriptor new file mode 100644 index 0000000..bfe48b6 --- /dev/null +++ b/gscriptor/chv_change_test.scriptor @@ -0,0 +1,13 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +reset + +# authenticate first +00 20 00 00 08 01 02 03 04 00 00 00 00 + +# change CHV +00 24 00 00 10 01 02 03 04 00 00 00 00 AA BB CC DD 00 00 00 00 + +# authenticate with new CHV +00 20 00 00 08 AA BB CC DD 00 00 00 00 diff --git a/gscriptor/chv_test.scriptor b/gscriptor/chv_test.scriptor new file mode 100644 index 0000000..177ca3f --- /dev/null +++ b/gscriptor/chv_test.scriptor @@ -0,0 +1,22 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +reset + +# two tries with wrong pin +00 20 00 00 08 11 11 11 11 00 00 00 00 +00 20 00 00 08 11 11 11 11 00 00 00 00 + +# ask how many tries are left +00 20 00 00 00 + +# correct pin, reset counter +00 20 00 00 08 01 02 03 04 00 00 00 00 + +# three tries with wrong pin, will block CHV +00 20 00 00 08 11 11 11 11 00 00 00 00 +00 20 00 00 08 11 11 11 11 00 00 00 00 +00 20 00 00 08 11 11 11 11 00 00 00 00 + +# correct pin, should not work as CHV is blocked +00 20 00 00 08 01 02 03 04 00 00 00 00 diff --git a/gscriptor/chv_unblock_test.scriptor b/gscriptor/chv_unblock_test.scriptor new file mode 100644 index 0000000..e3e4b90 --- /dev/null +++ b/gscriptor/chv_unblock_test.scriptor @@ -0,0 +1,24 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +reset + +# block PIN by entering the wrong code 3 times +00 20 00 00 08 11 11 11 11 00 00 00 00 +00 20 00 00 08 11 11 11 11 00 00 00 00 +00 20 00 00 08 11 11 11 11 00 00 00 00 + +# unblock PIN +00 2c 00 00 10 01 02 03 04 05 06 07 08 aa bb cc dd 00 00 00 00 + +# authenticate with new PIN +00 20 00 00 08 aa bb cc dd 00 00 00 00 + +# block PUK by trying to unblock with invalid code +00 2c 00 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 2c 00 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 2c 00 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 2c 00 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +# ask how many tries are left +00 2c 00 00 00 \ No newline at end of file diff --git a/gscriptor/create_delete_file_test.scriptor b/gscriptor/create_delete_file_test.scriptor new file mode 100644 index 0000000..e35ee6f --- /dev/null +++ b/gscriptor/create_delete_file_test.scriptor @@ -0,0 +1,31 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +reset + +#verify ADM PIN +#00 20 00 0A 08 31 32 33 34 30 30 30 30 + +#create 2f05 (EF) +00 E0 00 00 21 62 1F 82 02 41 21 83 02 2F 05 8A 01 05 8B 03 2F 06 05 80 02 00 0A 88 01 28 A5 06 D0 01 30 D2 01 0F + +#delete 2f05 (EF) +00 E4 00 00 02 2F 05 + +#create 7f20 (DF.GSM) +00 E0 00 00 17 62 15 82 02 78 21 83 02 7F 20 8A 01 05 8B 03 2F 06 01 A5 03 D2 01 07 + +#create an ADF inside DF.GSM (not a real scenario, but practical for this test) +00 E0 00 00 3B 62 39 82 02 78 21 83 02 7F FF 84 10 A0 00 00 00 87 10 02 FF FF FF FF 89 07 09 00 00 8A 01 05 AB 15 80 01 40 97 00 80 01 01 A4 06 83 01 0A 95 01 08 80 01 06 90 00 A5 03 D2 01 07 + +#select MF and then DF.GSM and then the ADF we just created +00 A4 00 00 02 3F 00 +00 A4 00 00 02 7f 20 +00 A4 04 00 07 a0 00 00 00 87 10 02 + +#create some other file (with short file descriptor) +00 E0 00 00 21 62 20 82 04 42 21 00 6E 83 02 6F 06 8A 01 05 8B 03 6F 06 06 80 02 06 E0 88 00 A5 06 D0 01 20 D2 01 03 + +#delete 7f20 (DF.GSM) and everything that is in it. +00 A4 00 00 02 3F 00 +00 E4 00 00 02 7F 20 \ No newline at end of file diff --git a/gscriptor/proactive_sim_ota_sms.scriptor b/gscriptor/proactive_sim_ota_sms.scriptor new file mode 100644 index 0000000..15b448c --- /dev/null +++ b/gscriptor/proactive_sim_ota_sms.scriptor @@ -0,0 +1,19 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +reset + +#Send terminal profile to activate CAT +80 10 00 00 11 FF FF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +#single SM +#80 C2 00 00 56 D1 54 82 02 83 81 8B 4E 40 08 81 55 66 77 88 7F F6 00 11 29 12 00 00 04 3D 02 70 00 00 38 15 06 01 25 25 B0 00 10 80 76 6F 57 F0 F8 9B BD BC 09 AF 97 B8 B7 EF 7E DC 6C 8B D2 A3 5A 57 14 70 37 49 75 00 3B FD 77 AC 39 53 1C C4 82 71 4E 75 47 A3 F8 5C C5 DC 10 + +#single SM +#80 C2 00 00 3E D1 3C 82 02 83 81 8B 36 40 08 81 55 66 77 88 7F F6 00 11 29 12 00 00 04 25 02 70 00 00 20 15 06 01 25 25 B0 00 10 12 5A ED 5C 84 3A 10 53 EA 6E 1E C2 F6 9C 92 44 D6 1E 48 4B 31 81 BE 07 + +#two SM in two parts +80 C2 00 00 3E D1 3C 82 02 83 81 8B 36 40 08 81 55 66 77 88 7F F6 00 11 29 12 00 00 04 25 05 00 03 f4 02 01 06 01 25 25 B0 00 10 12 5A ED 5C 84 3A 10 53 EA 6E 1E C2 F6 9C 92 44 D6 1E 48 4B 31 81 BE 07 +80 C2 00 00 3E D1 3C 82 02 83 81 8B 36 40 08 81 55 66 77 88 7F F6 00 11 29 12 00 00 04 25 05 00 03 f4 02 02 06 01 25 25 B0 00 10 12 5A ED 5C 84 3A 10 53 EA 6E 1E C2 F6 9C 92 44 D6 1E 48 4B 31 81 BE 07 + +#00 C0 00 00 10 \ No newline at end of file diff --git a/gscriptor/read_binary_test.scriptor b/gscriptor/read_binary_test.scriptor new file mode 100644 index 0000000..bb033db --- /dev/null +++ b/gscriptor/read_binary_test.scriptor @@ -0,0 +1,9 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +reset + +# select and read ICCID +00 a4 00 00 02 3f 00 +00 a4 00 00 02 2f e2 +00 b0 00 00 0a \ No newline at end of file diff --git a/gscriptor/read_record_test.scriptor b/gscriptor/read_record_test.scriptor new file mode 100644 index 0000000..56839df --- /dev/null +++ b/gscriptor/read_record_test.scriptor @@ -0,0 +1,18 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +reset + +# select and read EF.DIR +00 a4 00 00 02 3f 00 +00 a4 00 00 02 2f 00 +00 b2 01 04 2b + +# next record (same data again) +00 b2 00 02 2b + +# next record (next record) +00 b2 00 02 2b + +# previous record (same data as the beginning) +00 b2 00 03 2b \ No newline at end of file diff --git a/gscriptor/search_record_test.scriptor b/gscriptor/search_record_test.scriptor new file mode 100644 index 0000000..cc8d41c --- /dev/null +++ b/gscriptor/search_record_test.scriptor @@ -0,0 +1,76 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +# The following expects a file with the following records (longer records may be used) +# 1: 800101a40683010a950108800106900080016097008401d4a40683010a950108ffffffffffffffffffffff +#2: 80011aa40683010a9501088001019000ffffffffffffffffffffffffffffffffffffffffffffffffffffff +#3: 800101a40683010195010880011aa40683010a9501088401d4a40683010a950108ffffffffffffffffffff +#4: 800101900080011aa40683010a9501088401d4a40683010a950108ffffffffffffffffffffffffffffffff +#5: 800107a40683010a950108ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + +reset + +A0 A4 00 00 02 3F 00 +A0 A4 00 00 02 2F 06 + +# simple forward search +# expect matches at: 01 03 +A0 A2 01 04 05 80 01 01 a4 06 +00 c0 00 00 02 + +# simple forward search, begin at record 3 +# expect matches at: 03 +A0 A2 02 04 05 80 01 01 a4 06 +00 c0 00 00 01 + +# simple backward search, begin at record 2 +# expect matches at: 01 +A0 A2 02 05 05 80 01 01 a4 06 +00 c0 00 00 01 + +# simple backward search, begin at record 5 +# expect matches at: 03 01 +A0 A2 05 05 05 80 01 01 a4 06 +00 c0 00 00 02 + +# enhanced search: simple forward search, begin at record 1 +# expect matches at: 03 01 +A0 A2 01 06 07 04 00 80 01 01 a4 06 +00 c0 00 00 02 + +# enhanced search: simple forward search, begin at record 3 +# expect matches at: 03 +A0 A2 03 06 07 04 00 80 01 01 a4 06 +00 c0 00 00 01 + +# enhanced search: simple backward search, begin at record 2 +# expect matches at: 01 +A0 A2 02 06 07 05 00 80 01 01 a4 06 +00 c0 00 00 01 + +# enhanced search: simple backward search, begin at record 5 +# expect matches at: 03 01 +A0 A2 05 06 07 05 00 80 01 01 a4 06 +00 c0 00 00 02 + +# enhanced search: simple forward search, begin at record 1 +# expect matches at: 03 01 +A0 A2 01 06 07 04 00 80 01 01 a4 06 +00 c0 00 00 02 + +# enhanced search: simple forward search, begin at record 1, offset 3 +# expect matches at: 01 02 03 05 +A0 A2 01 06 06 04 03 a4 06 83 01 +00 c0 00 00 04 + +# enhanced search: simple forward search, begin at record 1, offset 3 +# expect matches at: 05 +A0 A2 01 06 06 0c 07 a4 06 83 01 +00 c0 00 00 01 + + + + + + + diff --git a/gscriptor/select_test.scriptor b/gscriptor/select_test.scriptor new file mode 100644 index 0000000..d00eaa7 --- /dev/null +++ b/gscriptor/select_test.scriptor @@ -0,0 +1,26 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +reset + +#select MF without requesting FCP +00 a4 00 00 02 3f 00 + +#select MF with requesting FCP +00 a4 00 04 02 3f 00 + +#get response (FCP) +00 c0 00 00 20 + +#select two EF +00 a4 00 00 02 2f 00 +00 a4 00 00 02 2f e2 + +#select by DF NAME +00 a4 00 00 02 3f 00 +00 a4 04 04 10 a0 00 00 00 87 10 02 ff ff ff ff 89 07 09 00 00 + +#pick up the response, try with a wrong length first to see if the +#second try works properly. +00 c0 00 00 32 +00 c0 00 00 32 \ No newline at end of file diff --git a/gscriptor/terminal_profile.scriptor b/gscriptor/terminal_profile.scriptor new file mode 100644 index 0000000..dfbe388 --- /dev/null +++ b/gscriptor/terminal_profile.scriptor @@ -0,0 +1,6 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +reset + +80 10 00 00 1e ff ff ff ff 7f 9d 00 df ff 00 00 1f e2 00 00 00 c3 6b 00 07 00 00 40 00 50 00 00 00 00 08 diff --git a/gscriptor/update_binary_test.scriptor b/gscriptor/update_binary_test.scriptor new file mode 100644 index 0000000..3f26f92 --- /dev/null +++ b/gscriptor/update_binary_test.scriptor @@ -0,0 +1,9 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +reset + +# select and read ICCID +00 a4 00 00 02 3f 00 +00 a4 00 00 02 2f e2 +00 d6 00 00 0a 98 88 12 01 AB CD 40 76 33 F5 \ No newline at end of file diff --git a/gscriptor/update_record_test.scriptor b/gscriptor/update_record_test.scriptor new file mode 100644 index 0000000..28fd63c --- /dev/null +++ b/gscriptor/update_record_test.scriptor @@ -0,0 +1,15 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +reset + +# select and read EF.DIR +00 a4 00 00 02 3f 00 +00 a4 00 00 02 2f 00 +00 b2 01 04 2b + +# update record +00 dc 00 02 2b 61 29 4F 10 A0 00 00 00 87 10 02 AA BB CC DD 89 07 09 00 00 50 05 55 53 69 6D 31 73 0E A0 0C 80 01 17 81 02 5F 60 82 03 45 41 50 + +# re-read record +00 b2 01 04 2b \ No newline at end of file diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 0000000..6975e9e --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1,6 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +SUBDIRS = \ + onomondo \ + $(NULL) diff --git a/include/onomondo/Makefile.am b/include/onomondo/Makefile.am new file mode 100644 index 0000000..f862312 --- /dev/null +++ b/include/onomondo/Makefile.am @@ -0,0 +1,6 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +SUBDIRS = \ + softsim \ + $(NULL) diff --git a/include/onomondo/softsim/Makefile.am b/include/onomondo/softsim/Makefile.am new file mode 100644 index 0000000..670287c --- /dev/null +++ b/include/onomondo/softsim/Makefile.am @@ -0,0 +1,12 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +noinst_HEADERS = \ + file.h \ + list.h \ + log.h \ + mem.h \ + softsim.h \ + storage.h \ + utils.h \ + $(NULL) diff --git a/include/onomondo/softsim/file.h b/include/onomondo/softsim/file.h new file mode 100644 index 0000000..6bd8526 --- /dev/null +++ b/include/onomondo/softsim/file.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include +struct ss_buf; +struct ss_fcp_file_descr; + +/** A handle to a file that has been accessed far enough to ensure that it is + * present and has its `fcp_file_descr` loaded. + * + * Initialized handles are, through their lists, always part of a chain down to + * the MF (in their .list.previous). + * + * When an `ss_file` is created through a "select" operation, all its indirect + * details (`aid`, `fci`, `fcp_decoded`) are present, both in the file and its + * parents. (`fcp_file_descr` is always present). + * + * The `access` property is not populated in "select" operations, but through + * the dedicated \ref ss_access_populate, which is invoked from the commands + * implementing selection for the terminal. + * + * Handles created through other operations (mainly in internal use) may have + * NULL pointers in these places. + */ +struct ss_file { + struct ss_list list; + uint32_t fid; + struct ss_buf *aid; /* also called 'DF name' */ + struct ss_buf *fci; /* The full file control information (FCI) in encoded form. + * + * This is the full data sent in response to SELECT + * calls; while ISO-IEC 7816-4 allows different + * information in here (eg. FMD), TS 102 221 usually only + * expects FCP data here. */ + struct ss_list *fcp_decoded; /* The decoded form of the FCP that is part of the file control information */ + struct ss_fcp_file_descr *fcp_file_descr; + struct ss_list *access; /**< The access rule governing file operations for + * the SE that selected the file (or general access if + * no SE apply). A value of NULL indicates that no + * access rules have (or could be) loaded. */ +}; + +struct ss_file *ss_get_file_from_path(const struct ss_list *path); +struct ss_file *ss_get_parent_file_from_path(const struct ss_list *path); diff --git a/include/onomondo/softsim/list.h b/include/onomondo/softsim/list.h new file mode 100644 index 0000000..b875b45 --- /dev/null +++ b/include/onomondo/softsim/list.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include + +/*! List element of a double linked list */ +struct ss_list { + /*! reference to the previous element */ + struct ss_list *previous; + /*! referenc to the next element */ + struct ss_list *next; +}; + +/*! Initialize list (pre and nxt point to themselves). + * \param[out] list pointer to the begin of the list. */ +static inline void ss_list_init(struct ss_list *list) +{ + list->previous = list; + list->next = list->previous; +} + +/*! Check if list properly initialized (next/previous pointer are not NULL). + * \param[in] list pointer to the begin of the list. + * \returns true when initialized, false otherwise. */ +static inline bool ss_list_initialized(const struct ss_list *list) +{ + return list->next != NULL && list->previous != NULL; +} + +/*! Put an element on the end of the list. + * \param[inout] list pointer to the begin of the list. + * \param[inout] elem pointer to the list member of the element to add. */ +static inline void ss_list_put(struct ss_list *list, struct ss_list *elem) +{ + struct ss_list *old_list_end = list->previous; + + /* Connect element to the end of the list */ + list->previous = elem; + elem->next = list; + + /* Connect old end to the new element */ + old_list_end->next = elem; + elem->previous = old_list_end; +} + +/*! Push an element on the beginning of the list. + * \param[inout] list pointer to the begin of the list. + * \param[inout] elem pointer to the list member of the element to add. */ +static inline void ss_list_push(struct ss_list *list, struct ss_list *elem) +{ + struct ss_list *old_list_begin = list->next; + + /* Connect element to the beginning of the list */ + list->next = elem; + elem->previous = list; + + /* Connect old beginning to the new element */ + old_list_begin->previous = elem; + elem->next = old_list_begin; +} + +/*! Get the next struct for the next list element (helper macro for SS_LIST_FOR_EACH). + * \param[in] list pointer to the begin of the list. + * \param[in] struct_type type description of the element struct. + * \param[in] struct_member name of the list begin member inside the element struct. + * \returns pointer to list element. */ +#define SS_LIST_GET_NEXT(list, struct_type, struct_member) (void*)((list)->next - offsetof(struct_type, struct_member)) + +/*! Get the struct for the current list element (helper macro for SS_LIST_FOR_EACH). + * \param[in] list pointer to the begin of the list. + * \param[in] struct_type type description of the element struct. + * \param[in] struct_member name of the list begin member inside the element struct. + * \returns pointer to list element. */ +#define SS_LIST_GET(list, struct_type, struct_member) (void*)(list - offsetof(struct_type, struct_member)) + +/* iterate over all structs managed by the list */ +#define SS_LIST_FOR_EACH(list_begin, struct_cursor, struct_type, struct_member) \ + for (struct_cursor = SS_LIST_GET_NEXT(list_begin, struct_type, struct_member); \ + struct_cursor != SS_LIST_GET(list_begin, struct_type, struct_member); \ + struct_cursor = SS_LIST_GET_NEXT(&(struct_cursor)->struct_member, struct_type, struct_member)) + +/* iterate over all structs managed by the list, but keep a precursor in case + * the cursor gets freed during the iteration. */ +#define SS_LIST_FOR_EACH_SAVE(list_begin, struct_cursor, struct_precursor, struct_type, struct_member) \ + for (struct_cursor = SS_LIST_GET_NEXT(list_begin, struct_type, struct_member), \ + struct_precursor = SS_LIST_GET_NEXT((list_begin)->next, struct_type, struct_member); \ + struct_cursor != SS_LIST_GET(list_begin, struct_type, struct_member); \ + struct_cursor = struct_precursor, \ + struct_precursor = SS_LIST_GET_NEXT(&(struct_precursor)->struct_member, struct_type, struct_member)) + +/*! Remove an element from the list by unlinking it from the list. + * \param[inout] elem pointer to the list member of the element to remove. */ +static inline void ss_list_remove(struct ss_list *elem) +{ + struct ss_list *next_elem = elem->next; + struct ss_list *previous_elem = elem->previous; + + next_elem->previous = previous_elem; + previous_elem->next = next_elem; + + elem->next = NULL; + elem->previous = NULL; +} + +/*! Check whether the list is empty or not. + * \param[in] list pointer to the begin of the list. + * \returns true when the list is empty, false otherwise. */ +static inline bool ss_list_empty(const struct ss_list *list) +{ + return list->next == list; +} diff --git a/include/onomondo/softsim/log.h b/include/onomondo/softsim/log.h new file mode 100644 index 0000000..abe8f3e --- /dev/null +++ b/include/onomondo/softsim/log.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include + +/*! macro to print a log line. + * \param[in] subsys log subsystem identifier. + * \param[in] level log level identifier. + * \param[in] fmt formtstring. + * \param[in] args formatstring arguments. */ +#define SS_LOGP(subsys, level, fmt, args...) \ + ss_logp(subsys, level, __FILE__, __LINE__, fmt, ## args) + +void ss_logp(uint32_t subsys, uint32_t level, const char *file, int line, const char *format, ...) + __attribute__ ((format (printf, 5, 6))); + + +enum log_subsys { + SBTLV, + SCTLV, + SVPCD, + SIFACE, + SUICC, + SCMD, + SLCHAN, + SFS, + SSTORAGE, + SACCESS, + SADMIN, + SSFI, + SDFNAME, + SFILE, + SPIN, + SAUTH, + SPROACT, + STLV8, + SSMS, + SREMOTECMD, + SREFRESH, + _NUM_LOG_SUBSYS +}; + +enum log_level { + LERROR, + LINFO, + LDEBUG, + _NUM_LOG_LEVEL +}; diff --git a/include/onomondo/softsim/mem.h b/include/onomondo/softsim/mem.h new file mode 100644 index 0000000..d4cb948 --- /dev/null +++ b/include/onomondo/softsim/mem.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include + +#define SS_ALLOC(obj) malloc(sizeof(obj)) +#define SS_ALLOC_N(n) malloc(n) +#define SS_FREE(obj) free(obj) diff --git a/include/onomondo/softsim/softsim.h b/include/onomondo/softsim/softsim.h new file mode 100644 index 0000000..ef680ef --- /dev/null +++ b/include/onomondo/softsim/softsim.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once +#include +#include + +struct ss_context; + +struct ss_context *ss_new_ctx(void); +void ss_free_ctx(struct ss_context *ctx); +void ss_reset(struct ss_context *ctx); +void ss_poll(struct ss_context *ctx); +size_t ss_atr(struct ss_context *ctx, uint8_t *atr_buf, size_t atr_buf_len); +size_t ss_transact(struct ss_context *ctx, + uint8_t *response_buf, size_t response_buf_len, + uint8_t *request_buf, size_t *request_len); diff --git a/include/onomondo/softsim/storage.h b/include/onomondo/softsim/storage.h new file mode 100644 index 0000000..a511092 --- /dev/null +++ b/include/onomondo/softsim/storage.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +struct ss_buf; +struct ss_list; + +int ss_storage_get_file_def(struct ss_list *path); +struct ss_buf *ss_storage_read_file(const struct ss_list *path, + size_t read_offset, size_t read_len); +size_t ss_storage_get_file_len(const struct ss_list *path); +int ss_storage_write_file(const struct ss_list *path, const uint8_t *data, + size_t write_offset, size_t write_len); +int ss_storage_delete(const struct ss_list *path); +int ss_storage_update_def(const struct ss_list *path); +int ss_storage_create_file(const struct ss_list *path, size_t file_len); +int ss_storage_create_dir(const struct ss_list *path); diff --git a/include/onomondo/softsim/utils.h b/include/onomondo/softsim/utils.h new file mode 100644 index 0000000..4af5475 --- /dev/null +++ b/include/onomondo/softsim/utils.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include +#include +#include "mem.h" + +#define SS_ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +char *ss_hexdump(const uint8_t *data, size_t len); +size_t ss_binary_from_hexstr(uint8_t *binary, size_t binary_len, const char *hexstr); + + +struct ss_buf { + uint8_t *data; + size_t len; +}; + +/*! Generate a hexdump string from an ss_buf object. + * \param[in] buf pointer to ss_buf object. + * \returns pointer to generated human readable string. */ +static inline char *ss_buf_hexdump(const struct ss_buf *buf) +{ + if (!buf) + return "(null)"; + return ss_hexdump(buf->data, buf->len); +} + +/*! Allocate a new ss_buf object. + * \param[in] len number of bytes to allocate inside ss_buf. + * \returns pointer to newly allocated ss_buf object. */ +static inline struct ss_buf *ss_buf_alloc(size_t len) +{ + struct ss_buf *sb = SS_ALLOC_N(sizeof(*sb) + len); + assert(sb); + + sb->data = (uint8_t *) sb + sizeof(*sb); + sb->len = len; + + return sb; +} + +/*! Allocate a new ss_buf and copy from user provided memory. + * \param[in] in user provided memory to copy. + * \param[in] len amount of bytes to copy from user provided memory. + * \returns pointer to newly allocated ss_buf object. */ +static inline struct ss_buf *ss_buf_alloc_and_cpy(const uint8_t *in, size_t len) +{ + struct ss_buf *sb = ss_buf_alloc(len); + memcpy(sb->data, in, len); + return sb; +} + +/*! Allocate a new ss_buf and copy from another ss_buf object. + * \param[in] buf ss_buf object to copy from. + * \returns pointer to newly allocated ss_buf object. */ +static inline struct ss_buf *ss_buf_dup(const struct ss_buf *buf) +{ + struct ss_buf *buf_dup = ss_buf_alloc(buf->len); + memcpy(buf_dup->data, buf->data, buf->len); + return buf_dup; +} + +/*! Free an ss_buf object. + * \param[in] pointer to ss_buf object to free. */ +static inline void ss_buf_free(struct ss_buf *buf) +{ + SS_FREE(buf); +} + +struct ss_buf *ss_buf_from_hexstr(const char *hexstr); +uint32_t ss_uint32_from_array(const uint8_t *array, size_t len); +void ss_array_from_uint32(uint8_t *array, size_t len, uint32_t in); +uint64_t ss_uint64_from_array(const uint8_t *array, size_t len); +void ss_array_from_uint64(uint8_t *array, size_t len, uint64_t in); +size_t ss_optimal_len_for_uint32(uint32_t in); + +uint64_t ss_uint64_load_from_be(const uint8_t *storage); +void ss_uint64_store_to_be(uint8_t *storage, uint64_t number); +size_t ss_strnlen(const char *s, size_t maxlen); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..162396a --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,4 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +add_subdirectory(softsim) diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..f862312 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,6 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +SUBDIRS = \ + softsim \ + $(NULL) diff --git a/src/softsim/CMakeLists.txt b/src/softsim/CMakeLists.txt new file mode 100644 index 0000000..207997d --- /dev/null +++ b/src/softsim/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +add_subdirectory(crypto) +add_subdirectory(milenage) +add_subdirectory(uicc) + +add_library(storage STATIC storage.c) + +target_include_directories(storage PUBLIC ${CMAKE_SOURCE_DIR}/include) diff --git a/src/softsim/Makefile.am b/src/softsim/Makefile.am new file mode 100644 index 0000000..b910268 --- /dev/null +++ b/src/softsim/Makefile.am @@ -0,0 +1,38 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +SUBDIRS = uicc \ + crypto \ + milenage \ + . + +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(NULL) + +AM_LDFLAGS = \ + $(NULL) + +bin_PROGRAMS = \ + softsim \ + $(NULL) + +softsim_SOURCES = \ + storage.c \ + main.c \ + $(NULL) + +noinst_HEADERS = \ + $(NULL) + +softsim_LDADD = \ + uicc/libuicc.a \ + milenage/libmilenage.a \ + crypto/libcrypto.a \ + $(NULL) diff --git a/src/softsim/crypto/CMakeLists.txt b/src/softsim/crypto/CMakeLists.txt new file mode 100644 index 0000000..fb9a8cf --- /dev/null +++ b/src/softsim/crypto/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +add_library(crypto + STATIC + aes-encblock.c + aes-internal-dec.c + aes-internal-enc.c + aes-internal.c + aes-wrap.c + des-internal.c +) + +target_include_directories(crypto + PUBLIC + ${CMAKE_SOURCE_DIR}/include + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) diff --git a/src/softsim/crypto/Makefile.am b/src/softsim/crypto/Makefile.am new file mode 100644 index 0000000..85794ea --- /dev/null +++ b/src/softsim/crypto/Makefile.am @@ -0,0 +1,34 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(NULL) + +AM_LDFLAGS = \ + $(NULL) + +noinst_LIBRARIES = libcrypto.a + +libcrypto_a_SOURCES = \ + aes-encblock.c \ + aes-internal-dec.c \ + aes-internal-enc.c \ + aes-internal.c \ + aes-wrap.c \ + des-internal.c + +noinst_HEADERS = \ + aes.h \ + aes_i.h \ + aes_wrap.h \ + common.h \ + crypto.h \ + des_i.h \ + includes.h diff --git a/src/softsim/crypto/aes-encblock.c b/src/softsim/crypto/aes-encblock.c new file mode 100644 index 0000000..a521621 --- /dev/null +++ b/src/softsim/crypto/aes-encblock.c @@ -0,0 +1,32 @@ +/* + * AES encrypt_block + * + * Copyright (c) 2003-2007, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "aes.h" +#include "aes_wrap.h" + +/** + * aes_128_encrypt_block - Perform one AES 128-bit block operation + * @key: Key for AES + * @in: Input data (16 bytes) + * @out: Output of the AES block operation (16 bytes) + * Returns: 0 on success, -1 on failure + */ +int aes_128_encrypt_block(const u8 *key, const u8 *in, u8 *out) +{ + void *ctx; + ctx = aes_encrypt_init(key, 16); + if (ctx == NULL) + return -1; + aes_encrypt(ctx, in, out); + aes_encrypt_deinit(ctx); + return 0; +} diff --git a/src/softsim/crypto/aes-internal-dec.c b/src/softsim/crypto/aes-internal-dec.c new file mode 100644 index 0000000..720c703 --- /dev/null +++ b/src/softsim/crypto/aes-internal-dec.c @@ -0,0 +1,161 @@ +/* + * AES (Rijndael) cipher - decrypt + * + * Modifications to public domain implementation: + * - cleanup + * - use C pre-processor to make it easier to change S table access + * - added option (AES_SMALL_TABLES) for reducing code size by about 8 kB at + * cost of reduced throughput (quite small difference on Pentium 4, + * 10-25% when using -O1 or -O2 optimization) + * + * Copyright (c) 2003-2012, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "crypto.h" +#include "aes_i.h" + +/** + * Expand the cipher key into the decryption key schedule. + * + * @return the number of rounds for the given cipher key size. + */ +static int rijndaelKeySetupDec(u32 rk[], const u8 cipherKey[], int keyBits) +{ + int Nr, i, j; + u32 temp; + + /* expand the cipher key: */ + Nr = rijndaelKeySetupEnc(rk, cipherKey, keyBits); + if (Nr < 0) + return Nr; + /* invert the order of the round keys: */ + for (i = 0, j = 4*Nr; i < j; i += 4, j -= 4) { + temp = rk[i ]; rk[i ] = rk[j ]; rk[j ] = temp; + temp = rk[i + 1]; rk[i + 1] = rk[j + 1]; rk[j + 1] = temp; + temp = rk[i + 2]; rk[i + 2] = rk[j + 2]; rk[j + 2] = temp; + temp = rk[i + 3]; rk[i + 3] = rk[j + 3]; rk[j + 3] = temp; + } + /* apply the inverse MixColumn transform to all round keys but the + * first and the last: */ + for (i = 1; i < Nr; i++) { + rk += 4; + for (j = 0; j < 4; j++) { + rk[j] = TD0_(TE4((rk[j] >> 24) )) ^ + TD1_(TE4((rk[j] >> 16) & 0xff)) ^ + TD2_(TE4((rk[j] >> 8) & 0xff)) ^ + TD3_(TE4((rk[j] ) & 0xff)); + } + } + + return Nr; +} + +void * aes_decrypt_init(const u8 *key, size_t len) +{ + u32 *rk; + int res; + rk = os_malloc(AES_PRIV_SIZE); + if (rk == NULL) + return NULL; + res = rijndaelKeySetupDec(rk, key, len * 8); + if (res < 0) { + os_free(rk); + return NULL; + } + rk[AES_PRIV_NR_POS] = res; + return rk; +} + +static void rijndaelDecrypt(const u32 rk[/*44*/], int Nr, const u8 ct[16], + u8 pt[16]) +{ + u32 s0, s1, s2, s3, t0, t1, t2, t3; +#ifndef FULL_UNROLL + int r; +#endif /* ?FULL_UNROLL */ + + /* + * map byte array block to cipher state + * and add initial round key: + */ + s0 = GETU32(ct ) ^ rk[0]; + s1 = GETU32(ct + 4) ^ rk[1]; + s2 = GETU32(ct + 8) ^ rk[2]; + s3 = GETU32(ct + 12) ^ rk[3]; + +#define ROUND(i,d,s) \ +d##0 = TD0(s##0) ^ TD1(s##3) ^ TD2(s##2) ^ TD3(s##1) ^ rk[4 * i]; \ +d##1 = TD0(s##1) ^ TD1(s##0) ^ TD2(s##3) ^ TD3(s##2) ^ rk[4 * i + 1]; \ +d##2 = TD0(s##2) ^ TD1(s##1) ^ TD2(s##0) ^ TD3(s##3) ^ rk[4 * i + 2]; \ +d##3 = TD0(s##3) ^ TD1(s##2) ^ TD2(s##1) ^ TD3(s##0) ^ rk[4 * i + 3] + +#ifdef FULL_UNROLL + + ROUND(1,t,s); + ROUND(2,s,t); + ROUND(3,t,s); + ROUND(4,s,t); + ROUND(5,t,s); + ROUND(6,s,t); + ROUND(7,t,s); + ROUND(8,s,t); + ROUND(9,t,s); + if (Nr > 10) { + ROUND(10,s,t); + ROUND(11,t,s); + if (Nr > 12) { + ROUND(12,s,t); + ROUND(13,t,s); + } + } + + rk += Nr << 2; + +#else /* !FULL_UNROLL */ + + /* Nr - 1 full rounds: */ + r = Nr >> 1; + for (;;) { + ROUND(1,t,s); + rk += 8; + if (--r == 0) + break; + ROUND(0,s,t); + } + +#endif /* ?FULL_UNROLL */ + +#undef ROUND + + /* + * apply last round and + * map cipher state to byte array block: + */ + s0 = TD41(t0) ^ TD42(t3) ^ TD43(t2) ^ TD44(t1) ^ rk[0]; + PUTU32(pt , s0); + s1 = TD41(t1) ^ TD42(t0) ^ TD43(t3) ^ TD44(t2) ^ rk[1]; + PUTU32(pt + 4, s1); + s2 = TD41(t2) ^ TD42(t1) ^ TD43(t0) ^ TD44(t3) ^ rk[2]; + PUTU32(pt + 8, s2); + s3 = TD41(t3) ^ TD42(t2) ^ TD43(t1) ^ TD44(t0) ^ rk[3]; + PUTU32(pt + 12, s3); +} + +void aes_decrypt(void *ctx, const u8 *crypt, u8 *plain) +{ + u32 *rk = ctx; + rijndaelDecrypt(ctx, rk[AES_PRIV_NR_POS], crypt, plain); +} + + +void aes_decrypt_deinit(void *ctx) +{ + os_memset(ctx, 0, AES_PRIV_SIZE); + os_free(ctx); +} diff --git a/src/softsim/crypto/aes-internal-enc.c b/src/softsim/crypto/aes-internal-enc.c new file mode 100644 index 0000000..f3c61b8 --- /dev/null +++ b/src/softsim/crypto/aes-internal-enc.c @@ -0,0 +1,126 @@ +/* + * AES (Rijndael) cipher - encrypt + * + * Modifications to public domain implementation: + * - cleanup + * - use C pre-processor to make it easier to change S table access + * - added option (AES_SMALL_TABLES) for reducing code size by about 8 kB at + * cost of reduced throughput (quite small difference on Pentium 4, + * 10-25% when using -O1 or -O2 optimization) + * + * Copyright (c) 2003-2012, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "crypto.h" +#include "aes_i.h" + +static void rijndaelEncrypt(const u32 rk[], int Nr, const u8 pt[16], u8 ct[16]) +{ + u32 s0, s1, s2, s3, t0, t1, t2, t3; +#ifndef FULL_UNROLL + int r; +#endif /* ?FULL_UNROLL */ + + /* + * map byte array block to cipher state + * and add initial round key: + */ + s0 = GETU32(pt ) ^ rk[0]; + s1 = GETU32(pt + 4) ^ rk[1]; + s2 = GETU32(pt + 8) ^ rk[2]; + s3 = GETU32(pt + 12) ^ rk[3]; + +#define ROUND(i,d,s) \ +d##0 = TE0(s##0) ^ TE1(s##1) ^ TE2(s##2) ^ TE3(s##3) ^ rk[4 * i]; \ +d##1 = TE0(s##1) ^ TE1(s##2) ^ TE2(s##3) ^ TE3(s##0) ^ rk[4 * i + 1]; \ +d##2 = TE0(s##2) ^ TE1(s##3) ^ TE2(s##0) ^ TE3(s##1) ^ rk[4 * i + 2]; \ +d##3 = TE0(s##3) ^ TE1(s##0) ^ TE2(s##1) ^ TE3(s##2) ^ rk[4 * i + 3] + +#ifdef FULL_UNROLL + + ROUND(1,t,s); + ROUND(2,s,t); + ROUND(3,t,s); + ROUND(4,s,t); + ROUND(5,t,s); + ROUND(6,s,t); + ROUND(7,t,s); + ROUND(8,s,t); + ROUND(9,t,s); + if (Nr > 10) { + ROUND(10,s,t); + ROUND(11,t,s); + if (Nr > 12) { + ROUND(12,s,t); + ROUND(13,t,s); + } + } + + rk += Nr << 2; + +#else /* !FULL_UNROLL */ + + /* Nr - 1 full rounds: */ + r = Nr >> 1; + for (;;) { + ROUND(1,t,s); + rk += 8; + if (--r == 0) + break; + ROUND(0,s,t); + } + +#endif /* ?FULL_UNROLL */ + +#undef ROUND + + /* + * apply last round and + * map cipher state to byte array block: + */ + s0 = TE41(t0) ^ TE42(t1) ^ TE43(t2) ^ TE44(t3) ^ rk[0]; + PUTU32(ct , s0); + s1 = TE41(t1) ^ TE42(t2) ^ TE43(t3) ^ TE44(t0) ^ rk[1]; + PUTU32(ct + 4, s1); + s2 = TE41(t2) ^ TE42(t3) ^ TE43(t0) ^ TE44(t1) ^ rk[2]; + PUTU32(ct + 8, s2); + s3 = TE41(t3) ^ TE42(t0) ^ TE43(t1) ^ TE44(t2) ^ rk[3]; + PUTU32(ct + 12, s3); +} + + +void * aes_encrypt_init(const u8 *key, size_t len) +{ + u32 *rk; + int res; + rk = os_malloc(AES_PRIV_SIZE); + if (rk == NULL) + return NULL; + res = rijndaelKeySetupEnc(rk, key, len * 8); + if (res < 0) { + os_free(rk); + return NULL; + } + rk[AES_PRIV_NR_POS] = res; + return rk; +} + + +void aes_encrypt(void *ctx, const u8 *plain, u8 *crypt) +{ + u32 *rk = ctx; + rijndaelEncrypt(ctx, rk[AES_PRIV_NR_POS], plain, crypt); +} + + +void aes_encrypt_deinit(void *ctx) +{ + os_memset(ctx, 0, AES_PRIV_SIZE); + os_free(ctx); +} diff --git a/src/softsim/crypto/aes-internal.c b/src/softsim/crypto/aes-internal.c new file mode 100644 index 0000000..bd4535d --- /dev/null +++ b/src/softsim/crypto/aes-internal.c @@ -0,0 +1,845 @@ +/* + * AES (Rijndael) cipher + * + * Modifications to public domain implementation: + * - cleanup + * - use C pre-processor to make it easier to change S table access + * - added option (AES_SMALL_TABLES) for reducing code size by about 8 kB at + * cost of reduced throughput (quite small difference on Pentium 4, + * 10-25% when using -O1 or -O2 optimization) + * + * Copyright (c) 2003-2012, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "crypto.h" +#include "aes_i.h" + +/* + * rijndael-alg-fst.c + * + * @version 3.0 (December 2000) + * + * Optimised ANSI C code for the Rijndael cipher (now AES) + * + * @author Vincent Rijmen + * @author Antoon Bosselaers + * @author Paulo Barreto + * + * This code is hereby placed in the public domain. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +/* +Te0[x] = S [x].[02, 01, 01, 03]; +Te1[x] = S [x].[03, 02, 01, 01]; +Te2[x] = S [x].[01, 03, 02, 01]; +Te3[x] = S [x].[01, 01, 03, 02]; +Te4[x] = S [x].[01, 01, 01, 01]; + +Td0[x] = Si[x].[0e, 09, 0d, 0b]; +Td1[x] = Si[x].[0b, 0e, 09, 0d]; +Td2[x] = Si[x].[0d, 0b, 0e, 09]; +Td3[x] = Si[x].[09, 0d, 0b, 0e]; +Td4[x] = Si[x].[01, 01, 01, 01]; +*/ + +const u32 Te0[256] = { + 0xc66363a5U, 0xf87c7c84U, 0xee777799U, 0xf67b7b8dU, + 0xfff2f20dU, 0xd66b6bbdU, 0xde6f6fb1U, 0x91c5c554U, + 0x60303050U, 0x02010103U, 0xce6767a9U, 0x562b2b7dU, + 0xe7fefe19U, 0xb5d7d762U, 0x4dababe6U, 0xec76769aU, + 0x8fcaca45U, 0x1f82829dU, 0x89c9c940U, 0xfa7d7d87U, + 0xeffafa15U, 0xb25959ebU, 0x8e4747c9U, 0xfbf0f00bU, + 0x41adadecU, 0xb3d4d467U, 0x5fa2a2fdU, 0x45afafeaU, + 0x239c9cbfU, 0x53a4a4f7U, 0xe4727296U, 0x9bc0c05bU, + 0x75b7b7c2U, 0xe1fdfd1cU, 0x3d9393aeU, 0x4c26266aU, + 0x6c36365aU, 0x7e3f3f41U, 0xf5f7f702U, 0x83cccc4fU, + 0x6834345cU, 0x51a5a5f4U, 0xd1e5e534U, 0xf9f1f108U, + 0xe2717193U, 0xabd8d873U, 0x62313153U, 0x2a15153fU, + 0x0804040cU, 0x95c7c752U, 0x46232365U, 0x9dc3c35eU, + 0x30181828U, 0x379696a1U, 0x0a05050fU, 0x2f9a9ab5U, + 0x0e070709U, 0x24121236U, 0x1b80809bU, 0xdfe2e23dU, + 0xcdebeb26U, 0x4e272769U, 0x7fb2b2cdU, 0xea75759fU, + 0x1209091bU, 0x1d83839eU, 0x582c2c74U, 0x341a1a2eU, + 0x361b1b2dU, 0xdc6e6eb2U, 0xb45a5aeeU, 0x5ba0a0fbU, + 0xa45252f6U, 0x763b3b4dU, 0xb7d6d661U, 0x7db3b3ceU, + 0x5229297bU, 0xdde3e33eU, 0x5e2f2f71U, 0x13848497U, + 0xa65353f5U, 0xb9d1d168U, 0x00000000U, 0xc1eded2cU, + 0x40202060U, 0xe3fcfc1fU, 0x79b1b1c8U, 0xb65b5bedU, + 0xd46a6abeU, 0x8dcbcb46U, 0x67bebed9U, 0x7239394bU, + 0x944a4adeU, 0x984c4cd4U, 0xb05858e8U, 0x85cfcf4aU, + 0xbbd0d06bU, 0xc5efef2aU, 0x4faaaae5U, 0xedfbfb16U, + 0x864343c5U, 0x9a4d4dd7U, 0x66333355U, 0x11858594U, + 0x8a4545cfU, 0xe9f9f910U, 0x04020206U, 0xfe7f7f81U, + 0xa05050f0U, 0x783c3c44U, 0x259f9fbaU, 0x4ba8a8e3U, + 0xa25151f3U, 0x5da3a3feU, 0x804040c0U, 0x058f8f8aU, + 0x3f9292adU, 0x219d9dbcU, 0x70383848U, 0xf1f5f504U, + 0x63bcbcdfU, 0x77b6b6c1U, 0xafdada75U, 0x42212163U, + 0x20101030U, 0xe5ffff1aU, 0xfdf3f30eU, 0xbfd2d26dU, + 0x81cdcd4cU, 0x180c0c14U, 0x26131335U, 0xc3ecec2fU, + 0xbe5f5fe1U, 0x359797a2U, 0x884444ccU, 0x2e171739U, + 0x93c4c457U, 0x55a7a7f2U, 0xfc7e7e82U, 0x7a3d3d47U, + 0xc86464acU, 0xba5d5de7U, 0x3219192bU, 0xe6737395U, + 0xc06060a0U, 0x19818198U, 0x9e4f4fd1U, 0xa3dcdc7fU, + 0x44222266U, 0x542a2a7eU, 0x3b9090abU, 0x0b888883U, + 0x8c4646caU, 0xc7eeee29U, 0x6bb8b8d3U, 0x2814143cU, + 0xa7dede79U, 0xbc5e5ee2U, 0x160b0b1dU, 0xaddbdb76U, + 0xdbe0e03bU, 0x64323256U, 0x743a3a4eU, 0x140a0a1eU, + 0x924949dbU, 0x0c06060aU, 0x4824246cU, 0xb85c5ce4U, + 0x9fc2c25dU, 0xbdd3d36eU, 0x43acacefU, 0xc46262a6U, + 0x399191a8U, 0x319595a4U, 0xd3e4e437U, 0xf279798bU, + 0xd5e7e732U, 0x8bc8c843U, 0x6e373759U, 0xda6d6db7U, + 0x018d8d8cU, 0xb1d5d564U, 0x9c4e4ed2U, 0x49a9a9e0U, + 0xd86c6cb4U, 0xac5656faU, 0xf3f4f407U, 0xcfeaea25U, + 0xca6565afU, 0xf47a7a8eU, 0x47aeaee9U, 0x10080818U, + 0x6fbabad5U, 0xf0787888U, 0x4a25256fU, 0x5c2e2e72U, + 0x381c1c24U, 0x57a6a6f1U, 0x73b4b4c7U, 0x97c6c651U, + 0xcbe8e823U, 0xa1dddd7cU, 0xe874749cU, 0x3e1f1f21U, + 0x964b4bddU, 0x61bdbddcU, 0x0d8b8b86U, 0x0f8a8a85U, + 0xe0707090U, 0x7c3e3e42U, 0x71b5b5c4U, 0xcc6666aaU, + 0x904848d8U, 0x06030305U, 0xf7f6f601U, 0x1c0e0e12U, + 0xc26161a3U, 0x6a35355fU, 0xae5757f9U, 0x69b9b9d0U, + 0x17868691U, 0x99c1c158U, 0x3a1d1d27U, 0x279e9eb9U, + 0xd9e1e138U, 0xebf8f813U, 0x2b9898b3U, 0x22111133U, + 0xd26969bbU, 0xa9d9d970U, 0x078e8e89U, 0x339494a7U, + 0x2d9b9bb6U, 0x3c1e1e22U, 0x15878792U, 0xc9e9e920U, + 0x87cece49U, 0xaa5555ffU, 0x50282878U, 0xa5dfdf7aU, + 0x038c8c8fU, 0x59a1a1f8U, 0x09898980U, 0x1a0d0d17U, + 0x65bfbfdaU, 0xd7e6e631U, 0x844242c6U, 0xd06868b8U, + 0x824141c3U, 0x299999b0U, 0x5a2d2d77U, 0x1e0f0f11U, + 0x7bb0b0cbU, 0xa85454fcU, 0x6dbbbbd6U, 0x2c16163aU, +}; +#ifndef AES_SMALL_TABLES +const u32 Te1[256] = { + 0xa5c66363U, 0x84f87c7cU, 0x99ee7777U, 0x8df67b7bU, + 0x0dfff2f2U, 0xbdd66b6bU, 0xb1de6f6fU, 0x5491c5c5U, + 0x50603030U, 0x03020101U, 0xa9ce6767U, 0x7d562b2bU, + 0x19e7fefeU, 0x62b5d7d7U, 0xe64dababU, 0x9aec7676U, + 0x458fcacaU, 0x9d1f8282U, 0x4089c9c9U, 0x87fa7d7dU, + 0x15effafaU, 0xebb25959U, 0xc98e4747U, 0x0bfbf0f0U, + 0xec41adadU, 0x67b3d4d4U, 0xfd5fa2a2U, 0xea45afafU, + 0xbf239c9cU, 0xf753a4a4U, 0x96e47272U, 0x5b9bc0c0U, + 0xc275b7b7U, 0x1ce1fdfdU, 0xae3d9393U, 0x6a4c2626U, + 0x5a6c3636U, 0x417e3f3fU, 0x02f5f7f7U, 0x4f83ccccU, + 0x5c683434U, 0xf451a5a5U, 0x34d1e5e5U, 0x08f9f1f1U, + 0x93e27171U, 0x73abd8d8U, 0x53623131U, 0x3f2a1515U, + 0x0c080404U, 0x5295c7c7U, 0x65462323U, 0x5e9dc3c3U, + 0x28301818U, 0xa1379696U, 0x0f0a0505U, 0xb52f9a9aU, + 0x090e0707U, 0x36241212U, 0x9b1b8080U, 0x3ddfe2e2U, + 0x26cdebebU, 0x694e2727U, 0xcd7fb2b2U, 0x9fea7575U, + 0x1b120909U, 0x9e1d8383U, 0x74582c2cU, 0x2e341a1aU, + 0x2d361b1bU, 0xb2dc6e6eU, 0xeeb45a5aU, 0xfb5ba0a0U, + 0xf6a45252U, 0x4d763b3bU, 0x61b7d6d6U, 0xce7db3b3U, + 0x7b522929U, 0x3edde3e3U, 0x715e2f2fU, 0x97138484U, + 0xf5a65353U, 0x68b9d1d1U, 0x00000000U, 0x2cc1ededU, + 0x60402020U, 0x1fe3fcfcU, 0xc879b1b1U, 0xedb65b5bU, + 0xbed46a6aU, 0x468dcbcbU, 0xd967bebeU, 0x4b723939U, + 0xde944a4aU, 0xd4984c4cU, 0xe8b05858U, 0x4a85cfcfU, + 0x6bbbd0d0U, 0x2ac5efefU, 0xe54faaaaU, 0x16edfbfbU, + 0xc5864343U, 0xd79a4d4dU, 0x55663333U, 0x94118585U, + 0xcf8a4545U, 0x10e9f9f9U, 0x06040202U, 0x81fe7f7fU, + 0xf0a05050U, 0x44783c3cU, 0xba259f9fU, 0xe34ba8a8U, + 0xf3a25151U, 0xfe5da3a3U, 0xc0804040U, 0x8a058f8fU, + 0xad3f9292U, 0xbc219d9dU, 0x48703838U, 0x04f1f5f5U, + 0xdf63bcbcU, 0xc177b6b6U, 0x75afdadaU, 0x63422121U, + 0x30201010U, 0x1ae5ffffU, 0x0efdf3f3U, 0x6dbfd2d2U, + 0x4c81cdcdU, 0x14180c0cU, 0x35261313U, 0x2fc3ececU, + 0xe1be5f5fU, 0xa2359797U, 0xcc884444U, 0x392e1717U, + 0x5793c4c4U, 0xf255a7a7U, 0x82fc7e7eU, 0x477a3d3dU, + 0xacc86464U, 0xe7ba5d5dU, 0x2b321919U, 0x95e67373U, + 0xa0c06060U, 0x98198181U, 0xd19e4f4fU, 0x7fa3dcdcU, + 0x66442222U, 0x7e542a2aU, 0xab3b9090U, 0x830b8888U, + 0xca8c4646U, 0x29c7eeeeU, 0xd36bb8b8U, 0x3c281414U, + 0x79a7dedeU, 0xe2bc5e5eU, 0x1d160b0bU, 0x76addbdbU, + 0x3bdbe0e0U, 0x56643232U, 0x4e743a3aU, 0x1e140a0aU, + 0xdb924949U, 0x0a0c0606U, 0x6c482424U, 0xe4b85c5cU, + 0x5d9fc2c2U, 0x6ebdd3d3U, 0xef43acacU, 0xa6c46262U, + 0xa8399191U, 0xa4319595U, 0x37d3e4e4U, 0x8bf27979U, + 0x32d5e7e7U, 0x438bc8c8U, 0x596e3737U, 0xb7da6d6dU, + 0x8c018d8dU, 0x64b1d5d5U, 0xd29c4e4eU, 0xe049a9a9U, + 0xb4d86c6cU, 0xfaac5656U, 0x07f3f4f4U, 0x25cfeaeaU, + 0xafca6565U, 0x8ef47a7aU, 0xe947aeaeU, 0x18100808U, + 0xd56fbabaU, 0x88f07878U, 0x6f4a2525U, 0x725c2e2eU, + 0x24381c1cU, 0xf157a6a6U, 0xc773b4b4U, 0x5197c6c6U, + 0x23cbe8e8U, 0x7ca1ddddU, 0x9ce87474U, 0x213e1f1fU, + 0xdd964b4bU, 0xdc61bdbdU, 0x860d8b8bU, 0x850f8a8aU, + 0x90e07070U, 0x427c3e3eU, 0xc471b5b5U, 0xaacc6666U, + 0xd8904848U, 0x05060303U, 0x01f7f6f6U, 0x121c0e0eU, + 0xa3c26161U, 0x5f6a3535U, 0xf9ae5757U, 0xd069b9b9U, + 0x91178686U, 0x5899c1c1U, 0x273a1d1dU, 0xb9279e9eU, + 0x38d9e1e1U, 0x13ebf8f8U, 0xb32b9898U, 0x33221111U, + 0xbbd26969U, 0x70a9d9d9U, 0x89078e8eU, 0xa7339494U, + 0xb62d9b9bU, 0x223c1e1eU, 0x92158787U, 0x20c9e9e9U, + 0x4987ceceU, 0xffaa5555U, 0x78502828U, 0x7aa5dfdfU, + 0x8f038c8cU, 0xf859a1a1U, 0x80098989U, 0x171a0d0dU, + 0xda65bfbfU, 0x31d7e6e6U, 0xc6844242U, 0xb8d06868U, + 0xc3824141U, 0xb0299999U, 0x775a2d2dU, 0x111e0f0fU, + 0xcb7bb0b0U, 0xfca85454U, 0xd66dbbbbU, 0x3a2c1616U, +}; +const u32 Te2[256] = { + 0x63a5c663U, 0x7c84f87cU, 0x7799ee77U, 0x7b8df67bU, + 0xf20dfff2U, 0x6bbdd66bU, 0x6fb1de6fU, 0xc55491c5U, + 0x30506030U, 0x01030201U, 0x67a9ce67U, 0x2b7d562bU, + 0xfe19e7feU, 0xd762b5d7U, 0xabe64dabU, 0x769aec76U, + 0xca458fcaU, 0x829d1f82U, 0xc94089c9U, 0x7d87fa7dU, + 0xfa15effaU, 0x59ebb259U, 0x47c98e47U, 0xf00bfbf0U, + 0xadec41adU, 0xd467b3d4U, 0xa2fd5fa2U, 0xafea45afU, + 0x9cbf239cU, 0xa4f753a4U, 0x7296e472U, 0xc05b9bc0U, + 0xb7c275b7U, 0xfd1ce1fdU, 0x93ae3d93U, 0x266a4c26U, + 0x365a6c36U, 0x3f417e3fU, 0xf702f5f7U, 0xcc4f83ccU, + 0x345c6834U, 0xa5f451a5U, 0xe534d1e5U, 0xf108f9f1U, + 0x7193e271U, 0xd873abd8U, 0x31536231U, 0x153f2a15U, + 0x040c0804U, 0xc75295c7U, 0x23654623U, 0xc35e9dc3U, + 0x18283018U, 0x96a13796U, 0x050f0a05U, 0x9ab52f9aU, + 0x07090e07U, 0x12362412U, 0x809b1b80U, 0xe23ddfe2U, + 0xeb26cdebU, 0x27694e27U, 0xb2cd7fb2U, 0x759fea75U, + 0x091b1209U, 0x839e1d83U, 0x2c74582cU, 0x1a2e341aU, + 0x1b2d361bU, 0x6eb2dc6eU, 0x5aeeb45aU, 0xa0fb5ba0U, + 0x52f6a452U, 0x3b4d763bU, 0xd661b7d6U, 0xb3ce7db3U, + 0x297b5229U, 0xe33edde3U, 0x2f715e2fU, 0x84971384U, + 0x53f5a653U, 0xd168b9d1U, 0x00000000U, 0xed2cc1edU, + 0x20604020U, 0xfc1fe3fcU, 0xb1c879b1U, 0x5bedb65bU, + 0x6abed46aU, 0xcb468dcbU, 0xbed967beU, 0x394b7239U, + 0x4ade944aU, 0x4cd4984cU, 0x58e8b058U, 0xcf4a85cfU, + 0xd06bbbd0U, 0xef2ac5efU, 0xaae54faaU, 0xfb16edfbU, + 0x43c58643U, 0x4dd79a4dU, 0x33556633U, 0x85941185U, + 0x45cf8a45U, 0xf910e9f9U, 0x02060402U, 0x7f81fe7fU, + 0x50f0a050U, 0x3c44783cU, 0x9fba259fU, 0xa8e34ba8U, + 0x51f3a251U, 0xa3fe5da3U, 0x40c08040U, 0x8f8a058fU, + 0x92ad3f92U, 0x9dbc219dU, 0x38487038U, 0xf504f1f5U, + 0xbcdf63bcU, 0xb6c177b6U, 0xda75afdaU, 0x21634221U, + 0x10302010U, 0xff1ae5ffU, 0xf30efdf3U, 0xd26dbfd2U, + 0xcd4c81cdU, 0x0c14180cU, 0x13352613U, 0xec2fc3ecU, + 0x5fe1be5fU, 0x97a23597U, 0x44cc8844U, 0x17392e17U, + 0xc45793c4U, 0xa7f255a7U, 0x7e82fc7eU, 0x3d477a3dU, + 0x64acc864U, 0x5de7ba5dU, 0x192b3219U, 0x7395e673U, + 0x60a0c060U, 0x81981981U, 0x4fd19e4fU, 0xdc7fa3dcU, + 0x22664422U, 0x2a7e542aU, 0x90ab3b90U, 0x88830b88U, + 0x46ca8c46U, 0xee29c7eeU, 0xb8d36bb8U, 0x143c2814U, + 0xde79a7deU, 0x5ee2bc5eU, 0x0b1d160bU, 0xdb76addbU, + 0xe03bdbe0U, 0x32566432U, 0x3a4e743aU, 0x0a1e140aU, + 0x49db9249U, 0x060a0c06U, 0x246c4824U, 0x5ce4b85cU, + 0xc25d9fc2U, 0xd36ebdd3U, 0xacef43acU, 0x62a6c462U, + 0x91a83991U, 0x95a43195U, 0xe437d3e4U, 0x798bf279U, + 0xe732d5e7U, 0xc8438bc8U, 0x37596e37U, 0x6db7da6dU, + 0x8d8c018dU, 0xd564b1d5U, 0x4ed29c4eU, 0xa9e049a9U, + 0x6cb4d86cU, 0x56faac56U, 0xf407f3f4U, 0xea25cfeaU, + 0x65afca65U, 0x7a8ef47aU, 0xaee947aeU, 0x08181008U, + 0xbad56fbaU, 0x7888f078U, 0x256f4a25U, 0x2e725c2eU, + 0x1c24381cU, 0xa6f157a6U, 0xb4c773b4U, 0xc65197c6U, + 0xe823cbe8U, 0xdd7ca1ddU, 0x749ce874U, 0x1f213e1fU, + 0x4bdd964bU, 0xbddc61bdU, 0x8b860d8bU, 0x8a850f8aU, + 0x7090e070U, 0x3e427c3eU, 0xb5c471b5U, 0x66aacc66U, + 0x48d89048U, 0x03050603U, 0xf601f7f6U, 0x0e121c0eU, + 0x61a3c261U, 0x355f6a35U, 0x57f9ae57U, 0xb9d069b9U, + 0x86911786U, 0xc15899c1U, 0x1d273a1dU, 0x9eb9279eU, + 0xe138d9e1U, 0xf813ebf8U, 0x98b32b98U, 0x11332211U, + 0x69bbd269U, 0xd970a9d9U, 0x8e89078eU, 0x94a73394U, + 0x9bb62d9bU, 0x1e223c1eU, 0x87921587U, 0xe920c9e9U, + 0xce4987ceU, 0x55ffaa55U, 0x28785028U, 0xdf7aa5dfU, + 0x8c8f038cU, 0xa1f859a1U, 0x89800989U, 0x0d171a0dU, + 0xbfda65bfU, 0xe631d7e6U, 0x42c68442U, 0x68b8d068U, + 0x41c38241U, 0x99b02999U, 0x2d775a2dU, 0x0f111e0fU, + 0xb0cb7bb0U, 0x54fca854U, 0xbbd66dbbU, 0x163a2c16U, +}; +const u32 Te3[256] = { + + 0x6363a5c6U, 0x7c7c84f8U, 0x777799eeU, 0x7b7b8df6U, + 0xf2f20dffU, 0x6b6bbdd6U, 0x6f6fb1deU, 0xc5c55491U, + 0x30305060U, 0x01010302U, 0x6767a9ceU, 0x2b2b7d56U, + 0xfefe19e7U, 0xd7d762b5U, 0xababe64dU, 0x76769aecU, + 0xcaca458fU, 0x82829d1fU, 0xc9c94089U, 0x7d7d87faU, + 0xfafa15efU, 0x5959ebb2U, 0x4747c98eU, 0xf0f00bfbU, + 0xadadec41U, 0xd4d467b3U, 0xa2a2fd5fU, 0xafafea45U, + 0x9c9cbf23U, 0xa4a4f753U, 0x727296e4U, 0xc0c05b9bU, + 0xb7b7c275U, 0xfdfd1ce1U, 0x9393ae3dU, 0x26266a4cU, + 0x36365a6cU, 0x3f3f417eU, 0xf7f702f5U, 0xcccc4f83U, + 0x34345c68U, 0xa5a5f451U, 0xe5e534d1U, 0xf1f108f9U, + 0x717193e2U, 0xd8d873abU, 0x31315362U, 0x15153f2aU, + 0x04040c08U, 0xc7c75295U, 0x23236546U, 0xc3c35e9dU, + 0x18182830U, 0x9696a137U, 0x05050f0aU, 0x9a9ab52fU, + 0x0707090eU, 0x12123624U, 0x80809b1bU, 0xe2e23ddfU, + 0xebeb26cdU, 0x2727694eU, 0xb2b2cd7fU, 0x75759feaU, + 0x09091b12U, 0x83839e1dU, 0x2c2c7458U, 0x1a1a2e34U, + 0x1b1b2d36U, 0x6e6eb2dcU, 0x5a5aeeb4U, 0xa0a0fb5bU, + 0x5252f6a4U, 0x3b3b4d76U, 0xd6d661b7U, 0xb3b3ce7dU, + 0x29297b52U, 0xe3e33eddU, 0x2f2f715eU, 0x84849713U, + 0x5353f5a6U, 0xd1d168b9U, 0x00000000U, 0xeded2cc1U, + 0x20206040U, 0xfcfc1fe3U, 0xb1b1c879U, 0x5b5bedb6U, + 0x6a6abed4U, 0xcbcb468dU, 0xbebed967U, 0x39394b72U, + 0x4a4ade94U, 0x4c4cd498U, 0x5858e8b0U, 0xcfcf4a85U, + 0xd0d06bbbU, 0xefef2ac5U, 0xaaaae54fU, 0xfbfb16edU, + 0x4343c586U, 0x4d4dd79aU, 0x33335566U, 0x85859411U, + 0x4545cf8aU, 0xf9f910e9U, 0x02020604U, 0x7f7f81feU, + 0x5050f0a0U, 0x3c3c4478U, 0x9f9fba25U, 0xa8a8e34bU, + 0x5151f3a2U, 0xa3a3fe5dU, 0x4040c080U, 0x8f8f8a05U, + 0x9292ad3fU, 0x9d9dbc21U, 0x38384870U, 0xf5f504f1U, + 0xbcbcdf63U, 0xb6b6c177U, 0xdada75afU, 0x21216342U, + 0x10103020U, 0xffff1ae5U, 0xf3f30efdU, 0xd2d26dbfU, + 0xcdcd4c81U, 0x0c0c1418U, 0x13133526U, 0xecec2fc3U, + 0x5f5fe1beU, 0x9797a235U, 0x4444cc88U, 0x1717392eU, + 0xc4c45793U, 0xa7a7f255U, 0x7e7e82fcU, 0x3d3d477aU, + 0x6464acc8U, 0x5d5de7baU, 0x19192b32U, 0x737395e6U, + 0x6060a0c0U, 0x81819819U, 0x4f4fd19eU, 0xdcdc7fa3U, + 0x22226644U, 0x2a2a7e54U, 0x9090ab3bU, 0x8888830bU, + 0x4646ca8cU, 0xeeee29c7U, 0xb8b8d36bU, 0x14143c28U, + 0xdede79a7U, 0x5e5ee2bcU, 0x0b0b1d16U, 0xdbdb76adU, + 0xe0e03bdbU, 0x32325664U, 0x3a3a4e74U, 0x0a0a1e14U, + 0x4949db92U, 0x06060a0cU, 0x24246c48U, 0x5c5ce4b8U, + 0xc2c25d9fU, 0xd3d36ebdU, 0xacacef43U, 0x6262a6c4U, + 0x9191a839U, 0x9595a431U, 0xe4e437d3U, 0x79798bf2U, + 0xe7e732d5U, 0xc8c8438bU, 0x3737596eU, 0x6d6db7daU, + 0x8d8d8c01U, 0xd5d564b1U, 0x4e4ed29cU, 0xa9a9e049U, + 0x6c6cb4d8U, 0x5656faacU, 0xf4f407f3U, 0xeaea25cfU, + 0x6565afcaU, 0x7a7a8ef4U, 0xaeaee947U, 0x08081810U, + 0xbabad56fU, 0x787888f0U, 0x25256f4aU, 0x2e2e725cU, + 0x1c1c2438U, 0xa6a6f157U, 0xb4b4c773U, 0xc6c65197U, + 0xe8e823cbU, 0xdddd7ca1U, 0x74749ce8U, 0x1f1f213eU, + 0x4b4bdd96U, 0xbdbddc61U, 0x8b8b860dU, 0x8a8a850fU, + 0x707090e0U, 0x3e3e427cU, 0xb5b5c471U, 0x6666aaccU, + 0x4848d890U, 0x03030506U, 0xf6f601f7U, 0x0e0e121cU, + 0x6161a3c2U, 0x35355f6aU, 0x5757f9aeU, 0xb9b9d069U, + 0x86869117U, 0xc1c15899U, 0x1d1d273aU, 0x9e9eb927U, + 0xe1e138d9U, 0xf8f813ebU, 0x9898b32bU, 0x11113322U, + 0x6969bbd2U, 0xd9d970a9U, 0x8e8e8907U, 0x9494a733U, + 0x9b9bb62dU, 0x1e1e223cU, 0x87879215U, 0xe9e920c9U, + 0xcece4987U, 0x5555ffaaU, 0x28287850U, 0xdfdf7aa5U, + 0x8c8c8f03U, 0xa1a1f859U, 0x89898009U, 0x0d0d171aU, + 0xbfbfda65U, 0xe6e631d7U, 0x4242c684U, 0x6868b8d0U, + 0x4141c382U, 0x9999b029U, 0x2d2d775aU, 0x0f0f111eU, + 0xb0b0cb7bU, 0x5454fca8U, 0xbbbbd66dU, 0x16163a2cU, +}; +const u32 Te4[256] = { + 0x63636363U, 0x7c7c7c7cU, 0x77777777U, 0x7b7b7b7bU, + 0xf2f2f2f2U, 0x6b6b6b6bU, 0x6f6f6f6fU, 0xc5c5c5c5U, + 0x30303030U, 0x01010101U, 0x67676767U, 0x2b2b2b2bU, + 0xfefefefeU, 0xd7d7d7d7U, 0xababababU, 0x76767676U, + 0xcacacacaU, 0x82828282U, 0xc9c9c9c9U, 0x7d7d7d7dU, + 0xfafafafaU, 0x59595959U, 0x47474747U, 0xf0f0f0f0U, + 0xadadadadU, 0xd4d4d4d4U, 0xa2a2a2a2U, 0xafafafafU, + 0x9c9c9c9cU, 0xa4a4a4a4U, 0x72727272U, 0xc0c0c0c0U, + 0xb7b7b7b7U, 0xfdfdfdfdU, 0x93939393U, 0x26262626U, + 0x36363636U, 0x3f3f3f3fU, 0xf7f7f7f7U, 0xccccccccU, + 0x34343434U, 0xa5a5a5a5U, 0xe5e5e5e5U, 0xf1f1f1f1U, + 0x71717171U, 0xd8d8d8d8U, 0x31313131U, 0x15151515U, + 0x04040404U, 0xc7c7c7c7U, 0x23232323U, 0xc3c3c3c3U, + 0x18181818U, 0x96969696U, 0x05050505U, 0x9a9a9a9aU, + 0x07070707U, 0x12121212U, 0x80808080U, 0xe2e2e2e2U, + 0xebebebebU, 0x27272727U, 0xb2b2b2b2U, 0x75757575U, + 0x09090909U, 0x83838383U, 0x2c2c2c2cU, 0x1a1a1a1aU, + 0x1b1b1b1bU, 0x6e6e6e6eU, 0x5a5a5a5aU, 0xa0a0a0a0U, + 0x52525252U, 0x3b3b3b3bU, 0xd6d6d6d6U, 0xb3b3b3b3U, + 0x29292929U, 0xe3e3e3e3U, 0x2f2f2f2fU, 0x84848484U, + 0x53535353U, 0xd1d1d1d1U, 0x00000000U, 0xededededU, + 0x20202020U, 0xfcfcfcfcU, 0xb1b1b1b1U, 0x5b5b5b5bU, + 0x6a6a6a6aU, 0xcbcbcbcbU, 0xbebebebeU, 0x39393939U, + 0x4a4a4a4aU, 0x4c4c4c4cU, 0x58585858U, 0xcfcfcfcfU, + 0xd0d0d0d0U, 0xefefefefU, 0xaaaaaaaaU, 0xfbfbfbfbU, + 0x43434343U, 0x4d4d4d4dU, 0x33333333U, 0x85858585U, + 0x45454545U, 0xf9f9f9f9U, 0x02020202U, 0x7f7f7f7fU, + 0x50505050U, 0x3c3c3c3cU, 0x9f9f9f9fU, 0xa8a8a8a8U, + 0x51515151U, 0xa3a3a3a3U, 0x40404040U, 0x8f8f8f8fU, + 0x92929292U, 0x9d9d9d9dU, 0x38383838U, 0xf5f5f5f5U, + 0xbcbcbcbcU, 0xb6b6b6b6U, 0xdadadadaU, 0x21212121U, + 0x10101010U, 0xffffffffU, 0xf3f3f3f3U, 0xd2d2d2d2U, + 0xcdcdcdcdU, 0x0c0c0c0cU, 0x13131313U, 0xececececU, + 0x5f5f5f5fU, 0x97979797U, 0x44444444U, 0x17171717U, + 0xc4c4c4c4U, 0xa7a7a7a7U, 0x7e7e7e7eU, 0x3d3d3d3dU, + 0x64646464U, 0x5d5d5d5dU, 0x19191919U, 0x73737373U, + 0x60606060U, 0x81818181U, 0x4f4f4f4fU, 0xdcdcdcdcU, + 0x22222222U, 0x2a2a2a2aU, 0x90909090U, 0x88888888U, + 0x46464646U, 0xeeeeeeeeU, 0xb8b8b8b8U, 0x14141414U, + 0xdedededeU, 0x5e5e5e5eU, 0x0b0b0b0bU, 0xdbdbdbdbU, + 0xe0e0e0e0U, 0x32323232U, 0x3a3a3a3aU, 0x0a0a0a0aU, + 0x49494949U, 0x06060606U, 0x24242424U, 0x5c5c5c5cU, + 0xc2c2c2c2U, 0xd3d3d3d3U, 0xacacacacU, 0x62626262U, + 0x91919191U, 0x95959595U, 0xe4e4e4e4U, 0x79797979U, + 0xe7e7e7e7U, 0xc8c8c8c8U, 0x37373737U, 0x6d6d6d6dU, + 0x8d8d8d8dU, 0xd5d5d5d5U, 0x4e4e4e4eU, 0xa9a9a9a9U, + 0x6c6c6c6cU, 0x56565656U, 0xf4f4f4f4U, 0xeaeaeaeaU, + 0x65656565U, 0x7a7a7a7aU, 0xaeaeaeaeU, 0x08080808U, + 0xbabababaU, 0x78787878U, 0x25252525U, 0x2e2e2e2eU, + 0x1c1c1c1cU, 0xa6a6a6a6U, 0xb4b4b4b4U, 0xc6c6c6c6U, + 0xe8e8e8e8U, 0xddddddddU, 0x74747474U, 0x1f1f1f1fU, + 0x4b4b4b4bU, 0xbdbdbdbdU, 0x8b8b8b8bU, 0x8a8a8a8aU, + 0x70707070U, 0x3e3e3e3eU, 0xb5b5b5b5U, 0x66666666U, + 0x48484848U, 0x03030303U, 0xf6f6f6f6U, 0x0e0e0e0eU, + 0x61616161U, 0x35353535U, 0x57575757U, 0xb9b9b9b9U, + 0x86868686U, 0xc1c1c1c1U, 0x1d1d1d1dU, 0x9e9e9e9eU, + 0xe1e1e1e1U, 0xf8f8f8f8U, 0x98989898U, 0x11111111U, + 0x69696969U, 0xd9d9d9d9U, 0x8e8e8e8eU, 0x94949494U, + 0x9b9b9b9bU, 0x1e1e1e1eU, 0x87878787U, 0xe9e9e9e9U, + 0xcecececeU, 0x55555555U, 0x28282828U, 0xdfdfdfdfU, + 0x8c8c8c8cU, 0xa1a1a1a1U, 0x89898989U, 0x0d0d0d0dU, + 0xbfbfbfbfU, 0xe6e6e6e6U, 0x42424242U, 0x68686868U, + 0x41414141U, 0x99999999U, 0x2d2d2d2dU, 0x0f0f0f0fU, + 0xb0b0b0b0U, 0x54545454U, 0xbbbbbbbbU, 0x16161616U, +}; +#endif /* AES_SMALL_TABLES */ +const u32 Td0[256] = { + 0x51f4a750U, 0x7e416553U, 0x1a17a4c3U, 0x3a275e96U, + 0x3bab6bcbU, 0x1f9d45f1U, 0xacfa58abU, 0x4be30393U, + 0x2030fa55U, 0xad766df6U, 0x88cc7691U, 0xf5024c25U, + 0x4fe5d7fcU, 0xc52acbd7U, 0x26354480U, 0xb562a38fU, + 0xdeb15a49U, 0x25ba1b67U, 0x45ea0e98U, 0x5dfec0e1U, + 0xc32f7502U, 0x814cf012U, 0x8d4697a3U, 0x6bd3f9c6U, + 0x038f5fe7U, 0x15929c95U, 0xbf6d7aebU, 0x955259daU, + 0xd4be832dU, 0x587421d3U, 0x49e06929U, 0x8ec9c844U, + 0x75c2896aU, 0xf48e7978U, 0x99583e6bU, 0x27b971ddU, + 0xbee14fb6U, 0xf088ad17U, 0xc920ac66U, 0x7dce3ab4U, + 0x63df4a18U, 0xe51a3182U, 0x97513360U, 0x62537f45U, + 0xb16477e0U, 0xbb6bae84U, 0xfe81a01cU, 0xf9082b94U, + 0x70486858U, 0x8f45fd19U, 0x94de6c87U, 0x527bf8b7U, + 0xab73d323U, 0x724b02e2U, 0xe31f8f57U, 0x6655ab2aU, + 0xb2eb2807U, 0x2fb5c203U, 0x86c57b9aU, 0xd33708a5U, + 0x302887f2U, 0x23bfa5b2U, 0x02036abaU, 0xed16825cU, + 0x8acf1c2bU, 0xa779b492U, 0xf307f2f0U, 0x4e69e2a1U, + 0x65daf4cdU, 0x0605bed5U, 0xd134621fU, 0xc4a6fe8aU, + 0x342e539dU, 0xa2f355a0U, 0x058ae132U, 0xa4f6eb75U, + 0x0b83ec39U, 0x4060efaaU, 0x5e719f06U, 0xbd6e1051U, + 0x3e218af9U, 0x96dd063dU, 0xdd3e05aeU, 0x4de6bd46U, + 0x91548db5U, 0x71c45d05U, 0x0406d46fU, 0x605015ffU, + 0x1998fb24U, 0xd6bde997U, 0x894043ccU, 0x67d99e77U, + 0xb0e842bdU, 0x07898b88U, 0xe7195b38U, 0x79c8eedbU, + 0xa17c0a47U, 0x7c420fe9U, 0xf8841ec9U, 0x00000000U, + 0x09808683U, 0x322bed48U, 0x1e1170acU, 0x6c5a724eU, + 0xfd0efffbU, 0x0f853856U, 0x3daed51eU, 0x362d3927U, + 0x0a0fd964U, 0x685ca621U, 0x9b5b54d1U, 0x24362e3aU, + 0x0c0a67b1U, 0x9357e70fU, 0xb4ee96d2U, 0x1b9b919eU, + 0x80c0c54fU, 0x61dc20a2U, 0x5a774b69U, 0x1c121a16U, + 0xe293ba0aU, 0xc0a02ae5U, 0x3c22e043U, 0x121b171dU, + 0x0e090d0bU, 0xf28bc7adU, 0x2db6a8b9U, 0x141ea9c8U, + 0x57f11985U, 0xaf75074cU, 0xee99ddbbU, 0xa37f60fdU, + 0xf701269fU, 0x5c72f5bcU, 0x44663bc5U, 0x5bfb7e34U, + 0x8b432976U, 0xcb23c6dcU, 0xb6edfc68U, 0xb8e4f163U, + 0xd731dccaU, 0x42638510U, 0x13972240U, 0x84c61120U, + 0x854a247dU, 0xd2bb3df8U, 0xaef93211U, 0xc729a16dU, + 0x1d9e2f4bU, 0xdcb230f3U, 0x0d8652ecU, 0x77c1e3d0U, + 0x2bb3166cU, 0xa970b999U, 0x119448faU, 0x47e96422U, + 0xa8fc8cc4U, 0xa0f03f1aU, 0x567d2cd8U, 0x223390efU, + 0x87494ec7U, 0xd938d1c1U, 0x8ccaa2feU, 0x98d40b36U, + 0xa6f581cfU, 0xa57ade28U, 0xdab78e26U, 0x3fadbfa4U, + 0x2c3a9de4U, 0x5078920dU, 0x6a5fcc9bU, 0x547e4662U, + 0xf68d13c2U, 0x90d8b8e8U, 0x2e39f75eU, 0x82c3aff5U, + 0x9f5d80beU, 0x69d0937cU, 0x6fd52da9U, 0xcf2512b3U, + 0xc8ac993bU, 0x10187da7U, 0xe89c636eU, 0xdb3bbb7bU, + 0xcd267809U, 0x6e5918f4U, 0xec9ab701U, 0x834f9aa8U, + 0xe6956e65U, 0xaaffe67eU, 0x21bccf08U, 0xef15e8e6U, + 0xbae79bd9U, 0x4a6f36ceU, 0xea9f09d4U, 0x29b07cd6U, + 0x31a4b2afU, 0x2a3f2331U, 0xc6a59430U, 0x35a266c0U, + 0x744ebc37U, 0xfc82caa6U, 0xe090d0b0U, 0x33a7d815U, + 0xf104984aU, 0x41ecdaf7U, 0x7fcd500eU, 0x1791f62fU, + 0x764dd68dU, 0x43efb04dU, 0xccaa4d54U, 0xe49604dfU, + 0x9ed1b5e3U, 0x4c6a881bU, 0xc12c1fb8U, 0x4665517fU, + 0x9d5eea04U, 0x018c355dU, 0xfa877473U, 0xfb0b412eU, + 0xb3671d5aU, 0x92dbd252U, 0xe9105633U, 0x6dd64713U, + 0x9ad7618cU, 0x37a10c7aU, 0x59f8148eU, 0xeb133c89U, + 0xcea927eeU, 0xb761c935U, 0xe11ce5edU, 0x7a47b13cU, + 0x9cd2df59U, 0x55f2733fU, 0x1814ce79U, 0x73c737bfU, + 0x53f7cdeaU, 0x5ffdaa5bU, 0xdf3d6f14U, 0x7844db86U, + 0xcaaff381U, 0xb968c43eU, 0x3824342cU, 0xc2a3405fU, + 0x161dc372U, 0xbce2250cU, 0x283c498bU, 0xff0d9541U, + 0x39a80171U, 0x080cb3deU, 0xd8b4e49cU, 0x6456c190U, + 0x7bcb8461U, 0xd532b670U, 0x486c5c74U, 0xd0b85742U, +}; +#ifndef AES_SMALL_TABLES +const u32 Td1[256] = { + 0x5051f4a7U, 0x537e4165U, 0xc31a17a4U, 0x963a275eU, + 0xcb3bab6bU, 0xf11f9d45U, 0xabacfa58U, 0x934be303U, + 0x552030faU, 0xf6ad766dU, 0x9188cc76U, 0x25f5024cU, + 0xfc4fe5d7U, 0xd7c52acbU, 0x80263544U, 0x8fb562a3U, + 0x49deb15aU, 0x6725ba1bU, 0x9845ea0eU, 0xe15dfec0U, + 0x02c32f75U, 0x12814cf0U, 0xa38d4697U, 0xc66bd3f9U, + 0xe7038f5fU, 0x9515929cU, 0xebbf6d7aU, 0xda955259U, + 0x2dd4be83U, 0xd3587421U, 0x2949e069U, 0x448ec9c8U, + 0x6a75c289U, 0x78f48e79U, 0x6b99583eU, 0xdd27b971U, + 0xb6bee14fU, 0x17f088adU, 0x66c920acU, 0xb47dce3aU, + 0x1863df4aU, 0x82e51a31U, 0x60975133U, 0x4562537fU, + 0xe0b16477U, 0x84bb6baeU, 0x1cfe81a0U, 0x94f9082bU, + 0x58704868U, 0x198f45fdU, 0x8794de6cU, 0xb7527bf8U, + 0x23ab73d3U, 0xe2724b02U, 0x57e31f8fU, 0x2a6655abU, + 0x07b2eb28U, 0x032fb5c2U, 0x9a86c57bU, 0xa5d33708U, + 0xf2302887U, 0xb223bfa5U, 0xba02036aU, 0x5ced1682U, + 0x2b8acf1cU, 0x92a779b4U, 0xf0f307f2U, 0xa14e69e2U, + 0xcd65daf4U, 0xd50605beU, 0x1fd13462U, 0x8ac4a6feU, + 0x9d342e53U, 0xa0a2f355U, 0x32058ae1U, 0x75a4f6ebU, + 0x390b83ecU, 0xaa4060efU, 0x065e719fU, 0x51bd6e10U, + 0xf93e218aU, 0x3d96dd06U, 0xaedd3e05U, 0x464de6bdU, + 0xb591548dU, 0x0571c45dU, 0x6f0406d4U, 0xff605015U, + 0x241998fbU, 0x97d6bde9U, 0xcc894043U, 0x7767d99eU, + 0xbdb0e842U, 0x8807898bU, 0x38e7195bU, 0xdb79c8eeU, + 0x47a17c0aU, 0xe97c420fU, 0xc9f8841eU, 0x00000000U, + 0x83098086U, 0x48322bedU, 0xac1e1170U, 0x4e6c5a72U, + 0xfbfd0effU, 0x560f8538U, 0x1e3daed5U, 0x27362d39U, + 0x640a0fd9U, 0x21685ca6U, 0xd19b5b54U, 0x3a24362eU, + 0xb10c0a67U, 0x0f9357e7U, 0xd2b4ee96U, 0x9e1b9b91U, + 0x4f80c0c5U, 0xa261dc20U, 0x695a774bU, 0x161c121aU, + 0x0ae293baU, 0xe5c0a02aU, 0x433c22e0U, 0x1d121b17U, + 0x0b0e090dU, 0xadf28bc7U, 0xb92db6a8U, 0xc8141ea9U, + 0x8557f119U, 0x4caf7507U, 0xbbee99ddU, 0xfda37f60U, + 0x9ff70126U, 0xbc5c72f5U, 0xc544663bU, 0x345bfb7eU, + 0x768b4329U, 0xdccb23c6U, 0x68b6edfcU, 0x63b8e4f1U, + 0xcad731dcU, 0x10426385U, 0x40139722U, 0x2084c611U, + 0x7d854a24U, 0xf8d2bb3dU, 0x11aef932U, 0x6dc729a1U, + 0x4b1d9e2fU, 0xf3dcb230U, 0xec0d8652U, 0xd077c1e3U, + 0x6c2bb316U, 0x99a970b9U, 0xfa119448U, 0x2247e964U, + 0xc4a8fc8cU, 0x1aa0f03fU, 0xd8567d2cU, 0xef223390U, + 0xc787494eU, 0xc1d938d1U, 0xfe8ccaa2U, 0x3698d40bU, + 0xcfa6f581U, 0x28a57adeU, 0x26dab78eU, 0xa43fadbfU, + 0xe42c3a9dU, 0x0d507892U, 0x9b6a5fccU, 0x62547e46U, + 0xc2f68d13U, 0xe890d8b8U, 0x5e2e39f7U, 0xf582c3afU, + 0xbe9f5d80U, 0x7c69d093U, 0xa96fd52dU, 0xb3cf2512U, + 0x3bc8ac99U, 0xa710187dU, 0x6ee89c63U, 0x7bdb3bbbU, + 0x09cd2678U, 0xf46e5918U, 0x01ec9ab7U, 0xa8834f9aU, + 0x65e6956eU, 0x7eaaffe6U, 0x0821bccfU, 0xe6ef15e8U, + 0xd9bae79bU, 0xce4a6f36U, 0xd4ea9f09U, 0xd629b07cU, + 0xaf31a4b2U, 0x312a3f23U, 0x30c6a594U, 0xc035a266U, + 0x37744ebcU, 0xa6fc82caU, 0xb0e090d0U, 0x1533a7d8U, + 0x4af10498U, 0xf741ecdaU, 0x0e7fcd50U, 0x2f1791f6U, + 0x8d764dd6U, 0x4d43efb0U, 0x54ccaa4dU, 0xdfe49604U, + 0xe39ed1b5U, 0x1b4c6a88U, 0xb8c12c1fU, 0x7f466551U, + 0x049d5eeaU, 0x5d018c35U, 0x73fa8774U, 0x2efb0b41U, + 0x5ab3671dU, 0x5292dbd2U, 0x33e91056U, 0x136dd647U, + 0x8c9ad761U, 0x7a37a10cU, 0x8e59f814U, 0x89eb133cU, + 0xeecea927U, 0x35b761c9U, 0xede11ce5U, 0x3c7a47b1U, + 0x599cd2dfU, 0x3f55f273U, 0x791814ceU, 0xbf73c737U, + 0xea53f7cdU, 0x5b5ffdaaU, 0x14df3d6fU, 0x867844dbU, + 0x81caaff3U, 0x3eb968c4U, 0x2c382434U, 0x5fc2a340U, + 0x72161dc3U, 0x0cbce225U, 0x8b283c49U, 0x41ff0d95U, + 0x7139a801U, 0xde080cb3U, 0x9cd8b4e4U, 0x906456c1U, + 0x617bcb84U, 0x70d532b6U, 0x74486c5cU, 0x42d0b857U, +}; +const u32 Td2[256] = { + 0xa75051f4U, 0x65537e41U, 0xa4c31a17U, 0x5e963a27U, + 0x6bcb3babU, 0x45f11f9dU, 0x58abacfaU, 0x03934be3U, + 0xfa552030U, 0x6df6ad76U, 0x769188ccU, 0x4c25f502U, + 0xd7fc4fe5U, 0xcbd7c52aU, 0x44802635U, 0xa38fb562U, + 0x5a49deb1U, 0x1b6725baU, 0x0e9845eaU, 0xc0e15dfeU, + 0x7502c32fU, 0xf012814cU, 0x97a38d46U, 0xf9c66bd3U, + 0x5fe7038fU, 0x9c951592U, 0x7aebbf6dU, 0x59da9552U, + 0x832dd4beU, 0x21d35874U, 0x692949e0U, 0xc8448ec9U, + 0x896a75c2U, 0x7978f48eU, 0x3e6b9958U, 0x71dd27b9U, + 0x4fb6bee1U, 0xad17f088U, 0xac66c920U, 0x3ab47dceU, + 0x4a1863dfU, 0x3182e51aU, 0x33609751U, 0x7f456253U, + 0x77e0b164U, 0xae84bb6bU, 0xa01cfe81U, 0x2b94f908U, + 0x68587048U, 0xfd198f45U, 0x6c8794deU, 0xf8b7527bU, + 0xd323ab73U, 0x02e2724bU, 0x8f57e31fU, 0xab2a6655U, + 0x2807b2ebU, 0xc2032fb5U, 0x7b9a86c5U, 0x08a5d337U, + 0x87f23028U, 0xa5b223bfU, 0x6aba0203U, 0x825ced16U, + 0x1c2b8acfU, 0xb492a779U, 0xf2f0f307U, 0xe2a14e69U, + 0xf4cd65daU, 0xbed50605U, 0x621fd134U, 0xfe8ac4a6U, + 0x539d342eU, 0x55a0a2f3U, 0xe132058aU, 0xeb75a4f6U, + 0xec390b83U, 0xefaa4060U, 0x9f065e71U, 0x1051bd6eU, + + 0x8af93e21U, 0x063d96ddU, 0x05aedd3eU, 0xbd464de6U, + 0x8db59154U, 0x5d0571c4U, 0xd46f0406U, 0x15ff6050U, + 0xfb241998U, 0xe997d6bdU, 0x43cc8940U, 0x9e7767d9U, + 0x42bdb0e8U, 0x8b880789U, 0x5b38e719U, 0xeedb79c8U, + 0x0a47a17cU, 0x0fe97c42U, 0x1ec9f884U, 0x00000000U, + 0x86830980U, 0xed48322bU, 0x70ac1e11U, 0x724e6c5aU, + 0xfffbfd0eU, 0x38560f85U, 0xd51e3daeU, 0x3927362dU, + 0xd9640a0fU, 0xa621685cU, 0x54d19b5bU, 0x2e3a2436U, + 0x67b10c0aU, 0xe70f9357U, 0x96d2b4eeU, 0x919e1b9bU, + 0xc54f80c0U, 0x20a261dcU, 0x4b695a77U, 0x1a161c12U, + 0xba0ae293U, 0x2ae5c0a0U, 0xe0433c22U, 0x171d121bU, + 0x0d0b0e09U, 0xc7adf28bU, 0xa8b92db6U, 0xa9c8141eU, + 0x198557f1U, 0x074caf75U, 0xddbbee99U, 0x60fda37fU, + 0x269ff701U, 0xf5bc5c72U, 0x3bc54466U, 0x7e345bfbU, + 0x29768b43U, 0xc6dccb23U, 0xfc68b6edU, 0xf163b8e4U, + 0xdccad731U, 0x85104263U, 0x22401397U, 0x112084c6U, + 0x247d854aU, 0x3df8d2bbU, 0x3211aef9U, 0xa16dc729U, + 0x2f4b1d9eU, 0x30f3dcb2U, 0x52ec0d86U, 0xe3d077c1U, + 0x166c2bb3U, 0xb999a970U, 0x48fa1194U, 0x642247e9U, + 0x8cc4a8fcU, 0x3f1aa0f0U, 0x2cd8567dU, 0x90ef2233U, + 0x4ec78749U, 0xd1c1d938U, 0xa2fe8ccaU, 0x0b3698d4U, + 0x81cfa6f5U, 0xde28a57aU, 0x8e26dab7U, 0xbfa43fadU, + 0x9de42c3aU, 0x920d5078U, 0xcc9b6a5fU, 0x4662547eU, + 0x13c2f68dU, 0xb8e890d8U, 0xf75e2e39U, 0xaff582c3U, + 0x80be9f5dU, 0x937c69d0U, 0x2da96fd5U, 0x12b3cf25U, + 0x993bc8acU, 0x7da71018U, 0x636ee89cU, 0xbb7bdb3bU, + 0x7809cd26U, 0x18f46e59U, 0xb701ec9aU, 0x9aa8834fU, + 0x6e65e695U, 0xe67eaaffU, 0xcf0821bcU, 0xe8e6ef15U, + 0x9bd9bae7U, 0x36ce4a6fU, 0x09d4ea9fU, 0x7cd629b0U, + 0xb2af31a4U, 0x23312a3fU, 0x9430c6a5U, 0x66c035a2U, + 0xbc37744eU, 0xcaa6fc82U, 0xd0b0e090U, 0xd81533a7U, + 0x984af104U, 0xdaf741ecU, 0x500e7fcdU, 0xf62f1791U, + 0xd68d764dU, 0xb04d43efU, 0x4d54ccaaU, 0x04dfe496U, + 0xb5e39ed1U, 0x881b4c6aU, 0x1fb8c12cU, 0x517f4665U, + 0xea049d5eU, 0x355d018cU, 0x7473fa87U, 0x412efb0bU, + 0x1d5ab367U, 0xd25292dbU, 0x5633e910U, 0x47136dd6U, + 0x618c9ad7U, 0x0c7a37a1U, 0x148e59f8U, 0x3c89eb13U, + 0x27eecea9U, 0xc935b761U, 0xe5ede11cU, 0xb13c7a47U, + 0xdf599cd2U, 0x733f55f2U, 0xce791814U, 0x37bf73c7U, + 0xcdea53f7U, 0xaa5b5ffdU, 0x6f14df3dU, 0xdb867844U, + 0xf381caafU, 0xc43eb968U, 0x342c3824U, 0x405fc2a3U, + 0xc372161dU, 0x250cbce2U, 0x498b283cU, 0x9541ff0dU, + 0x017139a8U, 0xb3de080cU, 0xe49cd8b4U, 0xc1906456U, + 0x84617bcbU, 0xb670d532U, 0x5c74486cU, 0x5742d0b8U, +}; +const u32 Td3[256] = { + 0xf4a75051U, 0x4165537eU, 0x17a4c31aU, 0x275e963aU, + 0xab6bcb3bU, 0x9d45f11fU, 0xfa58abacU, 0xe303934bU, + 0x30fa5520U, 0x766df6adU, 0xcc769188U, 0x024c25f5U, + 0xe5d7fc4fU, 0x2acbd7c5U, 0x35448026U, 0x62a38fb5U, + 0xb15a49deU, 0xba1b6725U, 0xea0e9845U, 0xfec0e15dU, + 0x2f7502c3U, 0x4cf01281U, 0x4697a38dU, 0xd3f9c66bU, + 0x8f5fe703U, 0x929c9515U, 0x6d7aebbfU, 0x5259da95U, + 0xbe832dd4U, 0x7421d358U, 0xe0692949U, 0xc9c8448eU, + 0xc2896a75U, 0x8e7978f4U, 0x583e6b99U, 0xb971dd27U, + 0xe14fb6beU, 0x88ad17f0U, 0x20ac66c9U, 0xce3ab47dU, + 0xdf4a1863U, 0x1a3182e5U, 0x51336097U, 0x537f4562U, + 0x6477e0b1U, 0x6bae84bbU, 0x81a01cfeU, 0x082b94f9U, + 0x48685870U, 0x45fd198fU, 0xde6c8794U, 0x7bf8b752U, + 0x73d323abU, 0x4b02e272U, 0x1f8f57e3U, 0x55ab2a66U, + 0xeb2807b2U, 0xb5c2032fU, 0xc57b9a86U, 0x3708a5d3U, + 0x2887f230U, 0xbfa5b223U, 0x036aba02U, 0x16825cedU, + 0xcf1c2b8aU, 0x79b492a7U, 0x07f2f0f3U, 0x69e2a14eU, + 0xdaf4cd65U, 0x05bed506U, 0x34621fd1U, 0xa6fe8ac4U, + 0x2e539d34U, 0xf355a0a2U, 0x8ae13205U, 0xf6eb75a4U, + 0x83ec390bU, 0x60efaa40U, 0x719f065eU, 0x6e1051bdU, + 0x218af93eU, 0xdd063d96U, 0x3e05aeddU, 0xe6bd464dU, + 0x548db591U, 0xc45d0571U, 0x06d46f04U, 0x5015ff60U, + 0x98fb2419U, 0xbde997d6U, 0x4043cc89U, 0xd99e7767U, + 0xe842bdb0U, 0x898b8807U, 0x195b38e7U, 0xc8eedb79U, + 0x7c0a47a1U, 0x420fe97cU, 0x841ec9f8U, 0x00000000U, + 0x80868309U, 0x2bed4832U, 0x1170ac1eU, 0x5a724e6cU, + 0x0efffbfdU, 0x8538560fU, 0xaed51e3dU, 0x2d392736U, + 0x0fd9640aU, 0x5ca62168U, 0x5b54d19bU, 0x362e3a24U, + 0x0a67b10cU, 0x57e70f93U, 0xee96d2b4U, 0x9b919e1bU, + 0xc0c54f80U, 0xdc20a261U, 0x774b695aU, 0x121a161cU, + 0x93ba0ae2U, 0xa02ae5c0U, 0x22e0433cU, 0x1b171d12U, + 0x090d0b0eU, 0x8bc7adf2U, 0xb6a8b92dU, 0x1ea9c814U, + 0xf1198557U, 0x75074cafU, 0x99ddbbeeU, 0x7f60fda3U, + 0x01269ff7U, 0x72f5bc5cU, 0x663bc544U, 0xfb7e345bU, + 0x4329768bU, 0x23c6dccbU, 0xedfc68b6U, 0xe4f163b8U, + 0x31dccad7U, 0x63851042U, 0x97224013U, 0xc6112084U, + 0x4a247d85U, 0xbb3df8d2U, 0xf93211aeU, 0x29a16dc7U, + 0x9e2f4b1dU, 0xb230f3dcU, 0x8652ec0dU, 0xc1e3d077U, + 0xb3166c2bU, 0x70b999a9U, 0x9448fa11U, 0xe9642247U, + 0xfc8cc4a8U, 0xf03f1aa0U, 0x7d2cd856U, 0x3390ef22U, + 0x494ec787U, 0x38d1c1d9U, 0xcaa2fe8cU, 0xd40b3698U, + 0xf581cfa6U, 0x7ade28a5U, 0xb78e26daU, 0xadbfa43fU, + 0x3a9de42cU, 0x78920d50U, 0x5fcc9b6aU, 0x7e466254U, + 0x8d13c2f6U, 0xd8b8e890U, 0x39f75e2eU, 0xc3aff582U, + 0x5d80be9fU, 0xd0937c69U, 0xd52da96fU, 0x2512b3cfU, + 0xac993bc8U, 0x187da710U, 0x9c636ee8U, 0x3bbb7bdbU, + 0x267809cdU, 0x5918f46eU, 0x9ab701ecU, 0x4f9aa883U, + 0x956e65e6U, 0xffe67eaaU, 0xbccf0821U, 0x15e8e6efU, + 0xe79bd9baU, 0x6f36ce4aU, 0x9f09d4eaU, 0xb07cd629U, + 0xa4b2af31U, 0x3f23312aU, 0xa59430c6U, 0xa266c035U, + 0x4ebc3774U, 0x82caa6fcU, 0x90d0b0e0U, 0xa7d81533U, + 0x04984af1U, 0xecdaf741U, 0xcd500e7fU, 0x91f62f17U, + 0x4dd68d76U, 0xefb04d43U, 0xaa4d54ccU, 0x9604dfe4U, + 0xd1b5e39eU, 0x6a881b4cU, 0x2c1fb8c1U, 0x65517f46U, + 0x5eea049dU, 0x8c355d01U, 0x877473faU, 0x0b412efbU, + 0x671d5ab3U, 0xdbd25292U, 0x105633e9U, 0xd647136dU, + 0xd7618c9aU, 0xa10c7a37U, 0xf8148e59U, 0x133c89ebU, + 0xa927eeceU, 0x61c935b7U, 0x1ce5ede1U, 0x47b13c7aU, + 0xd2df599cU, 0xf2733f55U, 0x14ce7918U, 0xc737bf73U, + 0xf7cdea53U, 0xfdaa5b5fU, 0x3d6f14dfU, 0x44db8678U, + 0xaff381caU, 0x68c43eb9U, 0x24342c38U, 0xa3405fc2U, + 0x1dc37216U, 0xe2250cbcU, 0x3c498b28U, 0x0d9541ffU, + 0xa8017139U, 0x0cb3de08U, 0xb4e49cd8U, 0x56c19064U, + 0xcb84617bU, 0x32b670d5U, 0x6c5c7448U, 0xb85742d0U, +}; +const u32 Td4[256] = { + 0x52525252U, 0x09090909U, 0x6a6a6a6aU, 0xd5d5d5d5U, + 0x30303030U, 0x36363636U, 0xa5a5a5a5U, 0x38383838U, + 0xbfbfbfbfU, 0x40404040U, 0xa3a3a3a3U, 0x9e9e9e9eU, + 0x81818181U, 0xf3f3f3f3U, 0xd7d7d7d7U, 0xfbfbfbfbU, + 0x7c7c7c7cU, 0xe3e3e3e3U, 0x39393939U, 0x82828282U, + 0x9b9b9b9bU, 0x2f2f2f2fU, 0xffffffffU, 0x87878787U, + 0x34343434U, 0x8e8e8e8eU, 0x43434343U, 0x44444444U, + 0xc4c4c4c4U, 0xdedededeU, 0xe9e9e9e9U, 0xcbcbcbcbU, + 0x54545454U, 0x7b7b7b7bU, 0x94949494U, 0x32323232U, + 0xa6a6a6a6U, 0xc2c2c2c2U, 0x23232323U, 0x3d3d3d3dU, + 0xeeeeeeeeU, 0x4c4c4c4cU, 0x95959595U, 0x0b0b0b0bU, + 0x42424242U, 0xfafafafaU, 0xc3c3c3c3U, 0x4e4e4e4eU, + 0x08080808U, 0x2e2e2e2eU, 0xa1a1a1a1U, 0x66666666U, + 0x28282828U, 0xd9d9d9d9U, 0x24242424U, 0xb2b2b2b2U, + 0x76767676U, 0x5b5b5b5bU, 0xa2a2a2a2U, 0x49494949U, + 0x6d6d6d6dU, 0x8b8b8b8bU, 0xd1d1d1d1U, 0x25252525U, + 0x72727272U, 0xf8f8f8f8U, 0xf6f6f6f6U, 0x64646464U, + 0x86868686U, 0x68686868U, 0x98989898U, 0x16161616U, + 0xd4d4d4d4U, 0xa4a4a4a4U, 0x5c5c5c5cU, 0xccccccccU, + 0x5d5d5d5dU, 0x65656565U, 0xb6b6b6b6U, 0x92929292U, + 0x6c6c6c6cU, 0x70707070U, 0x48484848U, 0x50505050U, + 0xfdfdfdfdU, 0xededededU, 0xb9b9b9b9U, 0xdadadadaU, + 0x5e5e5e5eU, 0x15151515U, 0x46464646U, 0x57575757U, + 0xa7a7a7a7U, 0x8d8d8d8dU, 0x9d9d9d9dU, 0x84848484U, + 0x90909090U, 0xd8d8d8d8U, 0xababababU, 0x00000000U, + 0x8c8c8c8cU, 0xbcbcbcbcU, 0xd3d3d3d3U, 0x0a0a0a0aU, + 0xf7f7f7f7U, 0xe4e4e4e4U, 0x58585858U, 0x05050505U, + 0xb8b8b8b8U, 0xb3b3b3b3U, 0x45454545U, 0x06060606U, + 0xd0d0d0d0U, 0x2c2c2c2cU, 0x1e1e1e1eU, 0x8f8f8f8fU, + 0xcacacacaU, 0x3f3f3f3fU, 0x0f0f0f0fU, 0x02020202U, + 0xc1c1c1c1U, 0xafafafafU, 0xbdbdbdbdU, 0x03030303U, + 0x01010101U, 0x13131313U, 0x8a8a8a8aU, 0x6b6b6b6bU, + 0x3a3a3a3aU, 0x91919191U, 0x11111111U, 0x41414141U, + 0x4f4f4f4fU, 0x67676767U, 0xdcdcdcdcU, 0xeaeaeaeaU, + 0x97979797U, 0xf2f2f2f2U, 0xcfcfcfcfU, 0xcecececeU, + 0xf0f0f0f0U, 0xb4b4b4b4U, 0xe6e6e6e6U, 0x73737373U, + 0x96969696U, 0xacacacacU, 0x74747474U, 0x22222222U, + 0xe7e7e7e7U, 0xadadadadU, 0x35353535U, 0x85858585U, + 0xe2e2e2e2U, 0xf9f9f9f9U, 0x37373737U, 0xe8e8e8e8U, + 0x1c1c1c1cU, 0x75757575U, 0xdfdfdfdfU, 0x6e6e6e6eU, + 0x47474747U, 0xf1f1f1f1U, 0x1a1a1a1aU, 0x71717171U, + 0x1d1d1d1dU, 0x29292929U, 0xc5c5c5c5U, 0x89898989U, + 0x6f6f6f6fU, 0xb7b7b7b7U, 0x62626262U, 0x0e0e0e0eU, + 0xaaaaaaaaU, 0x18181818U, 0xbebebebeU, 0x1b1b1b1bU, + 0xfcfcfcfcU, 0x56565656U, 0x3e3e3e3eU, 0x4b4b4b4bU, + 0xc6c6c6c6U, 0xd2d2d2d2U, 0x79797979U, 0x20202020U, + 0x9a9a9a9aU, 0xdbdbdbdbU, 0xc0c0c0c0U, 0xfefefefeU, + 0x78787878U, 0xcdcdcdcdU, 0x5a5a5a5aU, 0xf4f4f4f4U, + 0x1f1f1f1fU, 0xddddddddU, 0xa8a8a8a8U, 0x33333333U, + 0x88888888U, 0x07070707U, 0xc7c7c7c7U, 0x31313131U, + 0xb1b1b1b1U, 0x12121212U, 0x10101010U, 0x59595959U, + 0x27272727U, 0x80808080U, 0xececececU, 0x5f5f5f5fU, + 0x60606060U, 0x51515151U, 0x7f7f7f7fU, 0xa9a9a9a9U, + 0x19191919U, 0xb5b5b5b5U, 0x4a4a4a4aU, 0x0d0d0d0dU, + 0x2d2d2d2dU, 0xe5e5e5e5U, 0x7a7a7a7aU, 0x9f9f9f9fU, + 0x93939393U, 0xc9c9c9c9U, 0x9c9c9c9cU, 0xefefefefU, + 0xa0a0a0a0U, 0xe0e0e0e0U, 0x3b3b3b3bU, 0x4d4d4d4dU, + 0xaeaeaeaeU, 0x2a2a2a2aU, 0xf5f5f5f5U, 0xb0b0b0b0U, + 0xc8c8c8c8U, 0xebebebebU, 0xbbbbbbbbU, 0x3c3c3c3cU, + 0x83838383U, 0x53535353U, 0x99999999U, 0x61616161U, + 0x17171717U, 0x2b2b2b2bU, 0x04040404U, 0x7e7e7e7eU, + 0xbabababaU, 0x77777777U, 0xd6d6d6d6U, 0x26262626U, + 0xe1e1e1e1U, 0x69696969U, 0x14141414U, 0x63636363U, + 0x55555555U, 0x21212121U, 0x0c0c0c0cU, 0x7d7d7d7dU, +}; +const u32 rcon[] = { + 0x01000000, 0x02000000, 0x04000000, 0x08000000, + 0x10000000, 0x20000000, 0x40000000, 0x80000000, + 0x1B000000, 0x36000000, /* for 128-bit blocks, Rijndael never uses more than 10 rcon values */ +}; +#else /* AES_SMALL_TABLES */ +const u8 Td4s[256] = { + 0x52U, 0x09U, 0x6aU, 0xd5U, 0x30U, 0x36U, 0xa5U, 0x38U, + 0xbfU, 0x40U, 0xa3U, 0x9eU, 0x81U, 0xf3U, 0xd7U, 0xfbU, + 0x7cU, 0xe3U, 0x39U, 0x82U, 0x9bU, 0x2fU, 0xffU, 0x87U, + 0x34U, 0x8eU, 0x43U, 0x44U, 0xc4U, 0xdeU, 0xe9U, 0xcbU, + 0x54U, 0x7bU, 0x94U, 0x32U, 0xa6U, 0xc2U, 0x23U, 0x3dU, + 0xeeU, 0x4cU, 0x95U, 0x0bU, 0x42U, 0xfaU, 0xc3U, 0x4eU, + 0x08U, 0x2eU, 0xa1U, 0x66U, 0x28U, 0xd9U, 0x24U, 0xb2U, + 0x76U, 0x5bU, 0xa2U, 0x49U, 0x6dU, 0x8bU, 0xd1U, 0x25U, + 0x72U, 0xf8U, 0xf6U, 0x64U, 0x86U, 0x68U, 0x98U, 0x16U, + 0xd4U, 0xa4U, 0x5cU, 0xccU, 0x5dU, 0x65U, 0xb6U, 0x92U, + 0x6cU, 0x70U, 0x48U, 0x50U, 0xfdU, 0xedU, 0xb9U, 0xdaU, + 0x5eU, 0x15U, 0x46U, 0x57U, 0xa7U, 0x8dU, 0x9dU, 0x84U, + 0x90U, 0xd8U, 0xabU, 0x00U, 0x8cU, 0xbcU, 0xd3U, 0x0aU, + 0xf7U, 0xe4U, 0x58U, 0x05U, 0xb8U, 0xb3U, 0x45U, 0x06U, + 0xd0U, 0x2cU, 0x1eU, 0x8fU, 0xcaU, 0x3fU, 0x0fU, 0x02U, + 0xc1U, 0xafU, 0xbdU, 0x03U, 0x01U, 0x13U, 0x8aU, 0x6bU, + 0x3aU, 0x91U, 0x11U, 0x41U, 0x4fU, 0x67U, 0xdcU, 0xeaU, + 0x97U, 0xf2U, 0xcfU, 0xceU, 0xf0U, 0xb4U, 0xe6U, 0x73U, + 0x96U, 0xacU, 0x74U, 0x22U, 0xe7U, 0xadU, 0x35U, 0x85U, + 0xe2U, 0xf9U, 0x37U, 0xe8U, 0x1cU, 0x75U, 0xdfU, 0x6eU, + 0x47U, 0xf1U, 0x1aU, 0x71U, 0x1dU, 0x29U, 0xc5U, 0x89U, + 0x6fU, 0xb7U, 0x62U, 0x0eU, 0xaaU, 0x18U, 0xbeU, 0x1bU, + 0xfcU, 0x56U, 0x3eU, 0x4bU, 0xc6U, 0xd2U, 0x79U, 0x20U, + 0x9aU, 0xdbU, 0xc0U, 0xfeU, 0x78U, 0xcdU, 0x5aU, 0xf4U, + 0x1fU, 0xddU, 0xa8U, 0x33U, 0x88U, 0x07U, 0xc7U, 0x31U, + 0xb1U, 0x12U, 0x10U, 0x59U, 0x27U, 0x80U, 0xecU, 0x5fU, + 0x60U, 0x51U, 0x7fU, 0xa9U, 0x19U, 0xb5U, 0x4aU, 0x0dU, + 0x2dU, 0xe5U, 0x7aU, 0x9fU, 0x93U, 0xc9U, 0x9cU, 0xefU, + 0xa0U, 0xe0U, 0x3bU, 0x4dU, 0xaeU, 0x2aU, 0xf5U, 0xb0U, + 0xc8U, 0xebU, 0xbbU, 0x3cU, 0x83U, 0x53U, 0x99U, 0x61U, + 0x17U, 0x2bU, 0x04U, 0x7eU, 0xbaU, 0x77U, 0xd6U, 0x26U, + 0xe1U, 0x69U, 0x14U, 0x63U, 0x55U, 0x21U, 0x0cU, 0x7dU, +}; +const u8 rcons[] = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36 + /* for 128-bit blocks, Rijndael never uses more than 10 rcon values */ +}; +#endif /* AES_SMALL_TABLES */ +/** + * Expand the cipher key into the encryption key schedule. + * + * @return the number of rounds for the given cipher key size. + */ +int rijndaelKeySetupEnc(u32 rk[], const u8 cipherKey[], int keyBits) +{ + int i; + u32 temp; + + rk[0] = GETU32(cipherKey ); + rk[1] = GETU32(cipherKey + 4); + rk[2] = GETU32(cipherKey + 8); + rk[3] = GETU32(cipherKey + 12); + + if (keyBits == 128) { + for (i = 0; i < 10; i++) { + temp = rk[3]; + rk[4] = rk[0] ^ TE421(temp) ^ TE432(temp) ^ + TE443(temp) ^ TE414(temp) ^ RCON(i); + rk[5] = rk[1] ^ rk[4]; + rk[6] = rk[2] ^ rk[5]; + rk[7] = rk[3] ^ rk[6]; + rk += 4; + } + return 10; + } + + rk[4] = GETU32(cipherKey + 16); + rk[5] = GETU32(cipherKey + 20); + + if (keyBits == 192) { + for (i = 0; i < 8; i++) { + temp = rk[5]; + rk[6] = rk[0] ^ TE421(temp) ^ TE432(temp) ^ + TE443(temp) ^ TE414(temp) ^ RCON(i); + rk[7] = rk[1] ^ rk[6]; + rk[8] = rk[2] ^ rk[7]; + rk[9] = rk[3] ^ rk[8]; + if (i == 7) + return 12; + rk[10] = rk[4] ^ rk[9]; + rk[11] = rk[5] ^ rk[10]; + rk += 6; + } + } + + rk[6] = GETU32(cipherKey + 24); + rk[7] = GETU32(cipherKey + 28); + + if (keyBits == 256) { + for (i = 0; i < 7; i++) { + temp = rk[7]; + rk[8] = rk[0] ^ TE421(temp) ^ TE432(temp) ^ + TE443(temp) ^ TE414(temp) ^ RCON(i); + rk[9] = rk[1] ^ rk[8]; + rk[10] = rk[2] ^ rk[9]; + rk[11] = rk[3] ^ rk[10]; + if (i == 6) + return 14; + temp = rk[11]; + rk[12] = rk[4] ^ TE411(temp) ^ TE422(temp) ^ + TE433(temp) ^ TE444(temp); + rk[13] = rk[5] ^ rk[12]; + rk[14] = rk[6] ^ rk[13]; + rk[15] = rk[7] ^ rk[14]; + rk += 8; + } + } + + return -1; +} diff --git a/src/softsim/crypto/aes-wrap.c b/src/softsim/crypto/aes-wrap.c new file mode 100644 index 0000000..89d6f94 --- /dev/null +++ b/src/softsim/crypto/aes-wrap.c @@ -0,0 +1,70 @@ +/* + * AES Key Wrap Algorithm (128-bit KEK) (RFC3394) + * + * Copyright (c) 2003-2007, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "aes.h" +#include "aes_wrap.h" + +/** + * aes_wrap - Wrap keys with AES Key Wrap Algorithm (128-bit KEK) (RFC3394) + * @kek: 16-octet Key encryption key (KEK) + * @n: Length of the plaintext key in 64-bit units; e.g., 2 = 128-bit = 16 + * bytes + * @plain: Plaintext key to be wrapped, n * 64 bits + * @cipher: Wrapped key, (n + 1) * 64 bits + * Returns: 0 on success, -1 on failure + */ +int aes_wrap(const u8 *kek, int n, const u8 *plain, u8 *cipher) +{ + u8 *a, *r, b[16]; + int i, j; + void *ctx; + + a = cipher; + r = cipher + 8; + + /* 1) Initialize variables. */ + os_memset(a, 0xa6, 8); + os_memcpy(r, plain, 8 * n); + + ctx = aes_encrypt_init(kek, 16); + if (ctx == NULL) + return -1; + + /* 2) Calculate intermediate values. + * For j = 0 to 5 + * For i=1 to n + * B = AES(K, A | R[i]) + * A = MSB(64, B) ^ t where t = (n*j)+i + * R[i] = LSB(64, B) + */ + for (j = 0; j <= 5; j++) { + r = cipher + 8; + for (i = 1; i <= n; i++) { + os_memcpy(b, a, 8); + os_memcpy(b + 8, r, 8); + aes_encrypt(ctx, b, b); + os_memcpy(a, b, 8); + a[7] ^= n * j + i; + os_memcpy(r, b + 8, 8); + r += 8; + } + } + aes_encrypt_deinit(ctx); + + /* 3) Output the results. + * + * These are already in @cipher due to the location of temporary + * variables. + */ + + return 0; +} diff --git a/src/softsim/crypto/aes.h b/src/softsim/crypto/aes.h new file mode 100644 index 0000000..2de59e0 --- /dev/null +++ b/src/softsim/crypto/aes.h @@ -0,0 +1,21 @@ +/* + * AES functions + * Copyright (c) 2003-2006, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef AES_H +#define AES_H + +#define AES_BLOCK_SIZE 16 + +void * aes_encrypt_init(const u8 *key, size_t len); +void aes_encrypt(void *ctx, const u8 *plain, u8 *crypt); +void aes_encrypt_deinit(void *ctx); +void * aes_decrypt_init(const u8 *key, size_t len); +void aes_decrypt(void *ctx, const u8 *crypt, u8 *plain); +void aes_decrypt_deinit(void *ctx); + +#endif /* AES_H */ diff --git a/src/softsim/crypto/aes_i.h b/src/softsim/crypto/aes_i.h new file mode 100644 index 0000000..b20ec92 --- /dev/null +++ b/src/softsim/crypto/aes_i.h @@ -0,0 +1,125 @@ +/* + * AES (Rijndael) cipher + * Copyright (c) 2003-2012, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef AES_I_H +#define AES_I_H + +#include "aes.h" + +/* #define FULL_UNROLL */ +#define AES_SMALL_TABLES + +extern const u32 Te0[256]; +extern const u32 Te1[256]; +extern const u32 Te2[256]; +extern const u32 Te3[256]; +extern const u32 Te4[256]; +extern const u32 Td0[256]; +extern const u32 Td1[256]; +extern const u32 Td2[256]; +extern const u32 Td3[256]; +extern const u32 Td4[256]; +extern const u32 rcon[10]; +extern const u8 Td4s[256]; +extern const u8 rcons[10]; + +#ifndef AES_SMALL_TABLES + +#define RCON(i) rcon[(i)] + +#define TE0(i) Te0[((i) >> 24) & 0xff] +#define TE1(i) Te1[((i) >> 16) & 0xff] +#define TE2(i) Te2[((i) >> 8) & 0xff] +#define TE3(i) Te3[(i) & 0xff] +#define TE41(i) (Te4[((i) >> 24) & 0xff] & 0xff000000) +#define TE42(i) (Te4[((i) >> 16) & 0xff] & 0x00ff0000) +#define TE43(i) (Te4[((i) >> 8) & 0xff] & 0x0000ff00) +#define TE44(i) (Te4[(i) & 0xff] & 0x000000ff) +#define TE421(i) (Te4[((i) >> 16) & 0xff] & 0xff000000) +#define TE432(i) (Te4[((i) >> 8) & 0xff] & 0x00ff0000) +#define TE443(i) (Te4[(i) & 0xff] & 0x0000ff00) +#define TE414(i) (Te4[((i) >> 24) & 0xff] & 0x000000ff) +#define TE411(i) (Te4[((i) >> 24) & 0xff] & 0xff000000) +#define TE422(i) (Te4[((i) >> 16) & 0xff] & 0x00ff0000) +#define TE433(i) (Te4[((i) >> 8) & 0xff] & 0x0000ff00) +#define TE444(i) (Te4[(i) & 0xff] & 0x000000ff) +#define TE4(i) (Te4[(i)] & 0x000000ff) + +#define TD0(i) Td0[((i) >> 24) & 0xff] +#define TD1(i) Td1[((i) >> 16) & 0xff] +#define TD2(i) Td2[((i) >> 8) & 0xff] +#define TD3(i) Td3[(i) & 0xff] +#define TD41(i) (Td4[((i) >> 24) & 0xff] & 0xff000000) +#define TD42(i) (Td4[((i) >> 16) & 0xff] & 0x00ff0000) +#define TD43(i) (Td4[((i) >> 8) & 0xff] & 0x0000ff00) +#define TD44(i) (Td4[(i) & 0xff] & 0x000000ff) +#define TD0_(i) Td0[(i) & 0xff] +#define TD1_(i) Td1[(i) & 0xff] +#define TD2_(i) Td2[(i) & 0xff] +#define TD3_(i) Td3[(i) & 0xff] + +#else /* AES_SMALL_TABLES */ + +#define RCON(i) ((u32) rcons[(i)] << 24) + +static inline u32 rotr(u32 val, int bits) +{ + return (val >> bits) | (val << (32 - bits)); +} + +#define TE0(i) Te0[((i) >> 24) & 0xff] +#define TE1(i) rotr(Te0[((i) >> 16) & 0xff], 8) +#define TE2(i) rotr(Te0[((i) >> 8) & 0xff], 16) +#define TE3(i) rotr(Te0[(i) & 0xff], 24) +#define TE41(i) ((Te0[((i) >> 24) & 0xff] << 8) & 0xff000000) +#define TE42(i) (Te0[((i) >> 16) & 0xff] & 0x00ff0000) +#define TE43(i) (Te0[((i) >> 8) & 0xff] & 0x0000ff00) +#define TE44(i) ((Te0[(i) & 0xff] >> 8) & 0x000000ff) +#define TE421(i) ((Te0[((i) >> 16) & 0xff] << 8) & 0xff000000) +#define TE432(i) (Te0[((i) >> 8) & 0xff] & 0x00ff0000) +#define TE443(i) (Te0[(i) & 0xff] & 0x0000ff00) +#define TE414(i) ((Te0[((i) >> 24) & 0xff] >> 8) & 0x000000ff) +#define TE411(i) ((Te0[((i) >> 24) & 0xff] << 8) & 0xff000000) +#define TE422(i) (Te0[((i) >> 16) & 0xff] & 0x00ff0000) +#define TE433(i) (Te0[((i) >> 8) & 0xff] & 0x0000ff00) +#define TE444(i) ((Te0[(i) & 0xff] >> 8) & 0x000000ff) +#define TE4(i) ((Te0[(i)] >> 8) & 0x000000ff) + +#define TD0(i) Td0[((i) >> 24) & 0xff] +#define TD1(i) rotr(Td0[((i) >> 16) & 0xff], 8) +#define TD2(i) rotr(Td0[((i) >> 8) & 0xff], 16) +#define TD3(i) rotr(Td0[(i) & 0xff], 24) +#define TD41(i) ((u32) Td4s[((i) >> 24) & 0xff] << 24) +#define TD42(i) ((u32) Td4s[((i) >> 16) & 0xff] << 16) +#define TD43(i) ((u32) Td4s[((i) >> 8) & 0xff] << 8) +#define TD44(i) ((u32) Td4s[(i) & 0xff]) +#define TD0_(i) Td0[(i) & 0xff] +#define TD1_(i) rotr(Td0[(i) & 0xff], 8) +#define TD2_(i) rotr(Td0[(i) & 0xff], 16) +#define TD3_(i) rotr(Td0[(i) & 0xff], 24) + +#endif /* AES_SMALL_TABLES */ + +#ifdef _MSC_VER +#define SWAP(x) (_lrotl(x, 8) & 0x00ff00ff | _lrotr(x, 8) & 0xff00ff00) +#define GETU32(p) SWAP(*((u32 *)(p))) +#define PUTU32(ct, st) { *((u32 *)(ct)) = SWAP((st)); } +#else +#define GETU32(pt) (((u32)(pt)[0] << 24) ^ ((u32)(pt)[1] << 16) ^ \ +((u32)(pt)[2] << 8) ^ ((u32)(pt)[3])) +#define PUTU32(ct, st) { \ +(ct)[0] = (u8)((st) >> 24); (ct)[1] = (u8)((st) >> 16); \ +(ct)[2] = (u8)((st) >> 8); (ct)[3] = (u8)(st); } +#endif + +#define AES_PRIV_SIZE (4 * 4 * 15 + 4) +#define AES_PRIV_NR_POS (4 * 15) + +int rijndaelKeySetupEnc(u32 rk[], const u8 cipherKey[], int keyBits); + +#endif /* AES_I_H */ diff --git a/src/softsim/crypto/aes_wrap.h b/src/softsim/crypto/aes_wrap.h new file mode 100644 index 0000000..0433c04 --- /dev/null +++ b/src/softsim/crypto/aes_wrap.h @@ -0,0 +1,64 @@ +/* + * AES-based functions + * + * - AES Key Wrap Algorithm (128-bit KEK) (RFC3394) + * - One-Key CBC MAC (OMAC1) hash with AES-128 + * - AES-128 CTR mode encryption + * - AES-128 EAX mode encryption/decryption + * - AES-128 CBC + * - AES-GCM + * - AES-CCM + * + * Copyright (c) 2003-2012, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef AES_WRAP_H +#define AES_WRAP_H + +int __must_check aes_wrap(const u8 *kek, int n, const u8 *plain, u8 *cipher); +int __must_check aes_unwrap(const u8 *kek, int n, const u8 *cipher, u8 *plain); +int __must_check omac1_aes_128_vector(const u8 *key, size_t num_elem, + const u8 *addr[], const size_t *len, + u8 *mac); +int __must_check omac1_aes_128(const u8 *key, const u8 *data, size_t data_len, + u8 *mac); +int __must_check aes_128_encrypt_block(const u8 *key, const u8 *in, u8 *out); +int __must_check aes_128_ctr_encrypt(const u8 *key, const u8 *nonce, + u8 *data, size_t data_len); +int __must_check aes_128_eax_encrypt(const u8 *key, + const u8 *nonce, size_t nonce_len, + const u8 *hdr, size_t hdr_len, + u8 *data, size_t data_len, u8 *tag); +int __must_check aes_128_eax_decrypt(const u8 *key, + const u8 *nonce, size_t nonce_len, + const u8 *hdr, size_t hdr_len, + u8 *data, size_t data_len, const u8 *tag); +int __must_check aes_128_cbc_encrypt(const u8 *key, const u8 *iv, u8 *data, + size_t data_len); +int __must_check aes_128_cbc_decrypt(const u8 *key, const u8 *iv, u8 *data, + size_t data_len); +int __must_check aes_gcm_ae(const u8 *key, size_t key_len, + const u8 *iv, size_t iv_len, + const u8 *plain, size_t plain_len, + const u8 *aad, size_t aad_len, + u8 *crypt, u8 *tag); +int __must_check aes_gcm_ad(const u8 *key, size_t key_len, + const u8 *iv, size_t iv_len, + const u8 *crypt, size_t crypt_len, + const u8 *aad, size_t aad_len, const u8 *tag, + u8 *plain); +int __must_check aes_gmac(const u8 *key, size_t key_len, + const u8 *iv, size_t iv_len, + const u8 *aad, size_t aad_len, u8 *tag); +int __must_check aes_ccm_ae(const u8 *key, size_t key_len, const u8 *nonce, + size_t M, const u8 *plain, size_t plain_len, + const u8 *aad, size_t aad_len, u8 *crypt, u8 *auth); +int __must_check aes_ccm_ad(const u8 *key, size_t key_len, const u8 *nonce, + size_t M, const u8 *crypt, size_t crypt_len, + const u8 *aad, size_t aad_len, const u8 *auth, + u8 *plain); + +#endif /* AES_WRAP_H */ diff --git a/src/softsim/crypto/common.h b/src/softsim/crypto/common.h new file mode 100644 index 0000000..113132a --- /dev/null +++ b/src/softsim/crypto/common.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include +#include + +#define MSG_DEBUG +#define wpa_hexdump(x, args...) +#define wpa_hexdump_key(x, args...) +#define wpa_printf(x, args...) + +#define os_memcpy(x, y, z) memcpy(x, y, z) +#define os_memcmp(x, y, z) memcmp(x, y, z) +#define os_memcmp_const(x, y, z) memcmp(x, y, z) +#define os_memset(x, y, z) memset(x, y, z) +#define os_malloc(x) malloc(x) +#define os_free(x) free(x) + +typedef uint8_t u8; +typedef uint32_t u32; + +static inline u32 WPA_GET_BE32(const u8 *a) +{ + return ((u32) a[0] << 24) | (a[1] << 16) | (a[2] << 8) | a[3]; +} + +static inline void WPA_PUT_BE32(u8 *a, u32 val) +{ + a[0] = (val >> 24) & 0xff; + a[1] = (val >> 16) & 0xff; + a[2] = (val >> 8) & 0xff; + a[3] = val & 0xff; +} +typedef uint64_t u64; + +#define __must_check diff --git a/src/softsim/crypto/crypto.h b/src/softsim/crypto/crypto.h new file mode 100644 index 0000000..cd7d42d --- /dev/null +++ b/src/softsim/crypto/crypto.h @@ -0,0 +1,780 @@ +/* + * Wrapper functions for crypto libraries + * Copyright (c) 2004-2013, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + * + * This file defines the cryptographic functions that need to be implemented + * for wpa_supplicant and hostapd. When TLS is not used, internal + * implementation of MD5, SHA1, and AES is used and no external libraries are + * required. When TLS is enabled (e.g., by enabling EAP-TLS or EAP-PEAP), the + * crypto library used by the TLS implementation is expected to be used for + * non-TLS needs, too, in order to save space by not implementing these + * functions twice. + * + * Wrapper code for using each crypto library is in its own file (crypto*.c) + * and one of these files is build and linked in to provide the functions + * defined here. + */ + +#ifndef CRYPTO_H +#define CRYPTO_H + +/** + * md4_vector - MD4 hash for data vector + * @num_elem: Number of elements in the data vector + * @addr: Pointers to the data areas + * @len: Lengths of the data blocks + * @mac: Buffer for the hash + * Returns: 0 on success, -1 on failure + */ +int md4_vector(size_t num_elem, const u8 *addr[], const size_t *len, u8 *mac); + +/** + * md5_vector - MD5 hash for data vector + * @num_elem: Number of elements in the data vector + * @addr: Pointers to the data areas + * @len: Lengths of the data blocks + * @mac: Buffer for the hash + * Returns: 0 on success, -1 on failure + */ +int md5_vector(size_t num_elem, const u8 *addr[], const size_t *len, u8 *mac); + + +/** + * sha1_vector - SHA-1 hash for data vector + * @num_elem: Number of elements in the data vector + * @addr: Pointers to the data areas + * @len: Lengths of the data blocks + * @mac: Buffer for the hash + * Returns: 0 on success, -1 on failure + */ +int sha1_vector(size_t num_elem, const u8 *addr[], const size_t *len, + u8 *mac); + +/** + * fips186_2-prf - NIST FIPS Publication 186-2 change notice 1 PRF + * @seed: Seed/key for the PRF + * @seed_len: Seed length in bytes + * @x: Buffer for PRF output + * @xlen: Output length in bytes + * Returns: 0 on success, -1 on failure + * + * This function implements random number generation specified in NIST FIPS + * Publication 186-2 for EAP-SIM. This PRF uses a function that is similar to + * SHA-1, but has different message padding. + */ +int __must_check fips186_2_prf(const u8 *seed, size_t seed_len, u8 *x, + size_t xlen); + +/** + * sha256_vector - SHA256 hash for data vector + * @num_elem: Number of elements in the data vector + * @addr: Pointers to the data areas + * @len: Lengths of the data blocks + * @mac: Buffer for the hash + * Returns: 0 on success, -1 on failure + */ +int sha256_vector(size_t num_elem, const u8 *addr[], const size_t *len, + u8 *mac); + +/** + * des_encrypt - Encrypt one block with DES + * @clear: 8 octets (in) + * @key: 7 octets (in) (no parity bits included) + * @cypher: 8 octets (out) + * Returns: 0 on success, -1 on failure + */ +int des_encrypt(const u8 *clear, const u8 *key, u8 *cypher); + +/** + * aes_encrypt_init - Initialize AES for encryption + * @key: Encryption key + * @len: Key length in bytes (usually 16, i.e., 128 bits) + * Returns: Pointer to context data or %NULL on failure + */ +void * aes_encrypt_init(const u8 *key, size_t len); + +/** + * aes_encrypt - Encrypt one AES block + * @ctx: Context pointer from aes_encrypt_init() + * @plain: Plaintext data to be encrypted (16 bytes) + * @crypt: Buffer for the encrypted data (16 bytes) + */ +void aes_encrypt(void *ctx, const u8 *plain, u8 *crypt); + +/** + * aes_encrypt_deinit - Deinitialize AES encryption + * @ctx: Context pointer from aes_encrypt_init() + */ +void aes_encrypt_deinit(void *ctx); + +/** + * aes_decrypt_init - Initialize AES for decryption + * @key: Decryption key + * @len: Key length in bytes (usually 16, i.e., 128 bits) + * Returns: Pointer to context data or %NULL on failure + */ +void * aes_decrypt_init(const u8 *key, size_t len); + +/** + * aes_decrypt - Decrypt one AES block + * @ctx: Context pointer from aes_encrypt_init() + * @crypt: Encrypted data (16 bytes) + * @plain: Buffer for the decrypted data (16 bytes) + */ +void aes_decrypt(void *ctx, const u8 *crypt, u8 *plain); + +/** + * aes_decrypt_deinit - Deinitialize AES decryption + * @ctx: Context pointer from aes_encrypt_init() + */ +void aes_decrypt_deinit(void *ctx); + + +enum crypto_hash_alg { + CRYPTO_HASH_ALG_MD5, CRYPTO_HASH_ALG_SHA1, + CRYPTO_HASH_ALG_HMAC_MD5, CRYPTO_HASH_ALG_HMAC_SHA1, + CRYPTO_HASH_ALG_SHA256, CRYPTO_HASH_ALG_HMAC_SHA256 +}; + +struct crypto_hash; + +/** + * crypto_hash_init - Initialize hash/HMAC function + * @alg: Hash algorithm + * @key: Key for keyed hash (e.g., HMAC) or %NULL if not needed + * @key_len: Length of the key in bytes + * Returns: Pointer to hash context to use with other hash functions or %NULL + * on failure + * + * This function is only used with internal TLSv1 implementation + * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need + * to implement this. + */ +struct crypto_hash * crypto_hash_init(enum crypto_hash_alg alg, const u8 *key, + size_t key_len); + +/** + * crypto_hash_update - Add data to hash calculation + * @ctx: Context pointer from crypto_hash_init() + * @data: Data buffer to add + * @len: Length of the buffer + * + * This function is only used with internal TLSv1 implementation + * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need + * to implement this. + */ +void crypto_hash_update(struct crypto_hash *ctx, const u8 *data, size_t len); + +/** + * crypto_hash_finish - Complete hash calculation + * @ctx: Context pointer from crypto_hash_init() + * @hash: Buffer for hash value or %NULL if caller is just freeing the hash + * context + * @len: Pointer to length of the buffer or %NULL if caller is just freeing the + * hash context; on return, this is set to the actual length of the hash value + * Returns: 0 on success, -1 if buffer is too small (len set to needed length), + * or -2 on other failures (including failed crypto_hash_update() operations) + * + * This function calculates the hash value and frees the context buffer that + * was used for hash calculation. + * + * This function is only used with internal TLSv1 implementation + * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need + * to implement this. + */ +int crypto_hash_finish(struct crypto_hash *ctx, u8 *hash, size_t *len); + + +enum crypto_cipher_alg { + CRYPTO_CIPHER_NULL = 0, CRYPTO_CIPHER_ALG_AES, CRYPTO_CIPHER_ALG_3DES, + CRYPTO_CIPHER_ALG_DES, CRYPTO_CIPHER_ALG_RC2, CRYPTO_CIPHER_ALG_RC4 +}; + +struct crypto_cipher; + +/** + * crypto_cipher_init - Initialize block/stream cipher function + * @alg: Cipher algorithm + * @iv: Initialization vector for block ciphers or %NULL for stream ciphers + * @key: Cipher key + * @key_len: Length of key in bytes + * Returns: Pointer to cipher context to use with other cipher functions or + * %NULL on failure + * + * This function is only used with internal TLSv1 implementation + * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need + * to implement this. + */ +struct crypto_cipher * crypto_cipher_init(enum crypto_cipher_alg alg, + const u8 *iv, const u8 *key, + size_t key_len); + +/** + * crypto_cipher_encrypt - Cipher encrypt + * @ctx: Context pointer from crypto_cipher_init() + * @plain: Plaintext to cipher + * @crypt: Resulting ciphertext + * @len: Length of the plaintext + * Returns: 0 on success, -1 on failure + * + * This function is only used with internal TLSv1 implementation + * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need + * to implement this. + */ +int __must_check crypto_cipher_encrypt(struct crypto_cipher *ctx, + const u8 *plain, u8 *crypt, size_t len); + +/** + * crypto_cipher_decrypt - Cipher decrypt + * @ctx: Context pointer from crypto_cipher_init() + * @crypt: Ciphertext to decrypt + * @plain: Resulting plaintext + * @len: Length of the cipher text + * Returns: 0 on success, -1 on failure + * + * This function is only used with internal TLSv1 implementation + * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need + * to implement this. + */ +int __must_check crypto_cipher_decrypt(struct crypto_cipher *ctx, + const u8 *crypt, u8 *plain, size_t len); + +/** + * crypto_cipher_decrypt - Free cipher context + * @ctx: Context pointer from crypto_cipher_init() + * + * This function is only used with internal TLSv1 implementation + * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need + * to implement this. + */ +void crypto_cipher_deinit(struct crypto_cipher *ctx); + + +struct crypto_public_key; +struct crypto_private_key; + +/** + * crypto_public_key_import - Import an RSA public key + * @key: Key buffer (DER encoded RSA public key) + * @len: Key buffer length in bytes + * Returns: Pointer to the public key or %NULL on failure + * + * This function can just return %NULL if the crypto library supports X.509 + * parsing. In that case, crypto_public_key_from_cert() is used to import the + * public key from a certificate. + * + * This function is only used with internal TLSv1 implementation + * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need + * to implement this. + */ +struct crypto_public_key * crypto_public_key_import(const u8 *key, size_t len); + +struct crypto_public_key * +crypto_public_key_import_parts(const u8 *n, size_t n_len, + const u8 *e, size_t e_len); + +/** + * crypto_private_key_import - Import an RSA private key + * @key: Key buffer (DER encoded RSA private key) + * @len: Key buffer length in bytes + * @passwd: Key encryption password or %NULL if key is not encrypted + * Returns: Pointer to the private key or %NULL on failure + * + * This function is only used with internal TLSv1 implementation + * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need + * to implement this. + */ +struct crypto_private_key * crypto_private_key_import(const u8 *key, + size_t len, + const char *passwd); + +/** + * crypto_public_key_from_cert - Import an RSA public key from a certificate + * @buf: DER encoded X.509 certificate + * @len: Certificate buffer length in bytes + * Returns: Pointer to public key or %NULL on failure + * + * This function can just return %NULL if the crypto library does not support + * X.509 parsing. In that case, internal code will be used to parse the + * certificate and public key is imported using crypto_public_key_import(). + * + * This function is only used with internal TLSv1 implementation + * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need + * to implement this. + */ +struct crypto_public_key * crypto_public_key_from_cert(const u8 *buf, + size_t len); + +/** + * crypto_public_key_encrypt_pkcs1_v15 - Public key encryption (PKCS #1 v1.5) + * @key: Public key + * @in: Plaintext buffer + * @inlen: Length of plaintext buffer in bytes + * @out: Output buffer for encrypted data + * @outlen: Length of output buffer in bytes; set to used length on success + * Returns: 0 on success, -1 on failure + * + * This function is only used with internal TLSv1 implementation + * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need + * to implement this. + */ +int __must_check crypto_public_key_encrypt_pkcs1_v15( + struct crypto_public_key *key, const u8 *in, size_t inlen, + u8 *out, size_t *outlen); + +/** + * crypto_private_key_decrypt_pkcs1_v15 - Private key decryption (PKCS #1 v1.5) + * @key: Private key + * @in: Encrypted buffer + * @inlen: Length of encrypted buffer in bytes + * @out: Output buffer for encrypted data + * @outlen: Length of output buffer in bytes; set to used length on success + * Returns: 0 on success, -1 on failure + * + * This function is only used with internal TLSv1 implementation + * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need + * to implement this. + */ +int __must_check crypto_private_key_decrypt_pkcs1_v15( + struct crypto_private_key *key, const u8 *in, size_t inlen, + u8 *out, size_t *outlen); + +/** + * crypto_private_key_sign_pkcs1 - Sign with private key (PKCS #1) + * @key: Private key from crypto_private_key_import() + * @in: Plaintext buffer + * @inlen: Length of plaintext buffer in bytes + * @out: Output buffer for encrypted (signed) data + * @outlen: Length of output buffer in bytes; set to used length on success + * Returns: 0 on success, -1 on failure + * + * This function is only used with internal TLSv1 implementation + * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need + * to implement this. + */ +int __must_check crypto_private_key_sign_pkcs1(struct crypto_private_key *key, + const u8 *in, size_t inlen, + u8 *out, size_t *outlen); + +/** + * crypto_public_key_free - Free public key + * @key: Public key + * + * This function is only used with internal TLSv1 implementation + * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need + * to implement this. + */ +void crypto_public_key_free(struct crypto_public_key *key); + +/** + * crypto_private_key_free - Free private key + * @key: Private key from crypto_private_key_import() + * + * This function is only used with internal TLSv1 implementation + * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need + * to implement this. + */ +void crypto_private_key_free(struct crypto_private_key *key); + +/** + * crypto_public_key_decrypt_pkcs1 - Decrypt PKCS #1 signature + * @key: Public key + * @crypt: Encrypted signature data (using the private key) + * @crypt_len: Encrypted signature data length + * @plain: Buffer for plaintext (at least crypt_len bytes) + * @plain_len: Plaintext length (max buffer size on input, real len on output); + * Returns: 0 on success, -1 on failure + */ +int __must_check crypto_public_key_decrypt_pkcs1( + struct crypto_public_key *key, const u8 *crypt, size_t crypt_len, + u8 *plain, size_t *plain_len); + +/** + * crypto_global_init - Initialize crypto wrapper + * + * This function is only used with internal TLSv1 implementation + * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need + * to implement this. + */ +int __must_check crypto_global_init(void); + +/** + * crypto_global_deinit - Deinitialize crypto wrapper + * + * This function is only used with internal TLSv1 implementation + * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need + * to implement this. + */ +void crypto_global_deinit(void); + +/** + * crypto_mod_exp - Modular exponentiation of large integers + * @base: Base integer (big endian byte array) + * @base_len: Length of base integer in bytes + * @power: Power integer (big endian byte array) + * @power_len: Length of power integer in bytes + * @modulus: Modulus integer (big endian byte array) + * @modulus_len: Length of modulus integer in bytes + * @result: Buffer for the result + * @result_len: Result length (max buffer size on input, real len on output) + * Returns: 0 on success, -1 on failure + * + * This function calculates result = base ^ power mod modulus. modules_len is + * used as the maximum size of modulus buffer. It is set to the used size on + * success. + * + * This function is only used with internal TLSv1 implementation + * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need + * to implement this. + */ +int __must_check crypto_mod_exp(const u8 *base, size_t base_len, + const u8 *power, size_t power_len, + const u8 *modulus, size_t modulus_len, + u8 *result, size_t *result_len); + +/** + * rc4_skip - XOR RC4 stream to given data with skip-stream-start + * @key: RC4 key + * @keylen: RC4 key length + * @skip: number of bytes to skip from the beginning of the RC4 stream + * @data: data to be XOR'ed with RC4 stream + * @data_len: buf length + * Returns: 0 on success, -1 on failure + * + * Generate RC4 pseudo random stream for the given key, skip beginning of the + * stream, and XOR the end result with the data buffer to perform RC4 + * encryption/decryption. + */ +int rc4_skip(const u8 *key, size_t keylen, size_t skip, + u8 *data, size_t data_len); + +/** + * crypto_get_random - Generate cryptographically strong pseudy-random bytes + * @buf: Buffer for data + * @len: Number of bytes to generate + * Returns: 0 on success, -1 on failure + * + * If the PRNG does not have enough entropy to ensure unpredictable byte + * sequence, this functions must return -1. + */ +int crypto_get_random(void *buf, size_t len); + + +/** + * struct crypto_bignum - bignum + * + * Internal data structure for bignum implementation. The contents is specific + * to the used crypto library. + */ +struct crypto_bignum; + +/** + * crypto_bignum_init - Allocate memory for bignum + * Returns: Pointer to allocated bignum or %NULL on failure + */ +struct crypto_bignum * crypto_bignum_init(void); + +/** + * crypto_bignum_init_set - Allocate memory for bignum and set the value + * @buf: Buffer with unsigned binary value + * @len: Length of buf in octets + * Returns: Pointer to allocated bignum or %NULL on failure + */ +struct crypto_bignum * crypto_bignum_init_set(const u8 *buf, size_t len); + +/** + * crypto_bignum_deinit - Free bignum + * @n: Bignum from crypto_bignum_init() or crypto_bignum_init_set() + * @clear: Whether to clear the value from memory + */ +void crypto_bignum_deinit(struct crypto_bignum *n, int clear); + +/** + * crypto_bignum_to_bin - Set binary buffer to unsigned bignum + * @a: Bignum + * @buf: Buffer for the binary number + * @len: Length of @buf in octets + * @padlen: Length in octets to pad the result to or 0 to indicate no padding + * Returns: Number of octets written on success, -1 on failure + */ +int crypto_bignum_to_bin(const struct crypto_bignum *a, + u8 *buf, size_t buflen, size_t padlen); + +/** + * crypto_bignum_add - c = a + b + * @a: Bignum + * @b: Bignum + * @c: Bignum; used to store the result of a + b + * Returns: 0 on success, -1 on failure + */ +int crypto_bignum_add(const struct crypto_bignum *a, + const struct crypto_bignum *b, + struct crypto_bignum *c); + +/** + * crypto_bignum_mod - c = a % b + * @a: Bignum + * @b: Bignum + * @c: Bignum; used to store the result of a % b + * Returns: 0 on success, -1 on failure + */ +int crypto_bignum_mod(const struct crypto_bignum *a, + const struct crypto_bignum *b, + struct crypto_bignum *c); + +/** + * crypto_bignum_exptmod - Modular exponentiation: d = a^b (mod c) + * @a: Bignum; base + * @b: Bignum; exponent + * @c: Bignum; modulus + * @d: Bignum; used to store the result of a^b (mod c) + * Returns: 0 on success, -1 on failure + */ +int crypto_bignum_exptmod(const struct crypto_bignum *a, + const struct crypto_bignum *b, + const struct crypto_bignum *c, + struct crypto_bignum *d); + +/** + * crypto_bignum_inverse - Inverse a bignum so that a * c = 1 (mod b) + * @a: Bignum + * @b: Bignum + * @c: Bignum; used to store the result + * Returns: 0 on success, -1 on failure + */ +int crypto_bignum_inverse(const struct crypto_bignum *a, + const struct crypto_bignum *b, + struct crypto_bignum *c); + +/** + * crypto_bignum_sub - c = a - b + * @a: Bignum + * @b: Bignum + * @c: Bignum; used to store the result of a - b + * Returns: 0 on success, -1 on failure + */ +int crypto_bignum_sub(const struct crypto_bignum *a, + const struct crypto_bignum *b, + struct crypto_bignum *c); + +/** + * crypto_bignum_div - c = a / b + * @a: Bignum + * @b: Bignum + * @c: Bignum; used to store the result of a / b + * Returns: 0 on success, -1 on failure + */ +int crypto_bignum_div(const struct crypto_bignum *a, + const struct crypto_bignum *b, + struct crypto_bignum *c); + +/** + * crypto_bignum_mulmod - d = a * b (mod c) + * @a: Bignum + * @b: Bignum + * @c: Bignum + * @d: Bignum; used to store the result of (a * b) % c + * Returns: 0 on success, -1 on failure + */ +int crypto_bignum_mulmod(const struct crypto_bignum *a, + const struct crypto_bignum *b, + const struct crypto_bignum *c, + struct crypto_bignum *d); + +/** + * crypto_bignum_cmp - Compare two bignums + * @a: Bignum + * @b: Bignum + * Returns: -1 if a < b, 0 if a == b, or 1 if a > b + */ +int crypto_bignum_cmp(const struct crypto_bignum *a, + const struct crypto_bignum *b); + +/** + * crypto_bignum_bits - Get size of a bignum in bits + * @a: Bignum + * Returns: Number of bits in the bignum + */ +int crypto_bignum_bits(const struct crypto_bignum *a); + +/** + * crypto_bignum_is_zero - Is the given bignum zero + * @a: Bignum + * Returns: 1 if @a is zero or 0 if not + */ +int crypto_bignum_is_zero(const struct crypto_bignum *a); + +/** + * crypto_bignum_is_one - Is the given bignum one + * @a: Bignum + * Returns: 1 if @a is one or 0 if not + */ +int crypto_bignum_is_one(const struct crypto_bignum *a); + +/** + * struct crypto_ec - Elliptic curve context + * + * Internal data structure for EC implementation. The contents is specific + * to the used crypto library. + */ +struct crypto_ec; + +/** + * crypto_ec_init - Initialize elliptic curve context + * @group: Identifying number for the ECC group (IANA "Group Description" + * attribute registrty for RFC 2409) + * Returns: Pointer to EC context or %NULL on failure + */ +struct crypto_ec * crypto_ec_init(int group); + +/** + * crypto_ec_deinit - Deinitialize elliptic curve context + * @e: EC context from crypto_ec_init() + */ +void crypto_ec_deinit(struct crypto_ec *e); + +/** + * crypto_ec_prime_len - Get length of the prime in octets + * @e: EC context from crypto_ec_init() + * Returns: Length of the prime defining the group + */ +size_t crypto_ec_prime_len(struct crypto_ec *e); + +/** + * crypto_ec_prime_len_bits - Get length of the prime in bits + * @e: EC context from crypto_ec_init() + * Returns: Length of the prime defining the group in bits + */ +size_t crypto_ec_prime_len_bits(struct crypto_ec *e); + +/** + * crypto_ec_get_prime - Get prime defining an EC group + * @e: EC context from crypto_ec_init() + * Returns: Prime (bignum) defining the group + */ +const struct crypto_bignum * crypto_ec_get_prime(struct crypto_ec *e); + +/** + * crypto_ec_get_order - Get order of an EC group + * @e: EC context from crypto_ec_init() + * Returns: Order (bignum) of the group + */ +const struct crypto_bignum * crypto_ec_get_order(struct crypto_ec *e); + +/** + * struct crypto_ec_point - Elliptic curve point + * + * Internal data structure for EC implementation to represent a point. The + * contents is specific to the used crypto library. + */ +struct crypto_ec_point; + +/** + * crypto_ec_point_init - Initialize data for an EC point + * @e: EC context from crypto_ec_init() + * Returns: Pointer to EC point data or %NULL on failure + */ +struct crypto_ec_point * crypto_ec_point_init(struct crypto_ec *e); + +/** + * crypto_ec_point_deinit - Deinitialize EC point data + * @p: EC point data from crypto_ec_point_init() + * @clear: Whether to clear the EC point value from memory + */ +void crypto_ec_point_deinit(struct crypto_ec_point *p, int clear); + +/** + * crypto_ec_point_to_bin - Write EC point value as binary data + * @e: EC context from crypto_ec_init() + * @p: EC point data from crypto_ec_point_init() + * @x: Buffer for writing the binary data for x coordinate or %NULL if not used + * @y: Buffer for writing the binary data for y coordinate or %NULL if not used + * Returns: 0 on success, -1 on failure + * + * This function can be used to write an EC point as binary data in a format + * that has the x and y coordinates in big endian byte order fields padded to + * the length of the prime defining the group. + */ +int crypto_ec_point_to_bin(struct crypto_ec *e, + const struct crypto_ec_point *point, u8 *x, u8 *y); + +/** + * crypto_ec_point_from_bin - Create EC point from binary data + * @e: EC context from crypto_ec_init() + * @val: Binary data to read the EC point from + * Returns: Pointer to EC point data or %NULL on failure + * + * This function readers x and y coordinates of the EC point from the provided + * buffer assuming the values are in big endian byte order with fields padded to + * the length of the prime defining the group. + */ +struct crypto_ec_point * crypto_ec_point_from_bin(struct crypto_ec *e, + const u8 *val); + +/** + * crypto_bignum_add - c = a + b + * @e: EC context from crypto_ec_init() + * @a: Bignum + * @b: Bignum + * @c: Bignum; used to store the result of a + b + * Returns: 0 on success, -1 on failure + */ +int crypto_ec_point_add(struct crypto_ec *e, const struct crypto_ec_point *a, + const struct crypto_ec_point *b, + struct crypto_ec_point *c); + +/** + * crypto_bignum_mul - res = b * p + * @e: EC context from crypto_ec_init() + * @p: EC point + * @b: Bignum + * @res: EC point; used to store the result of b * p + * Returns: 0 on success, -1 on failure + */ +int crypto_ec_point_mul(struct crypto_ec *e, const struct crypto_ec_point *p, + const struct crypto_bignum *b, + struct crypto_ec_point *res); + +/** + * crypto_ec_point_invert - Compute inverse of an EC point + * @e: EC context from crypto_ec_init() + * @p: EC point to invert (and result of the operation) + * Returns: 0 on success, -1 on failure + */ +int crypto_ec_point_invert(struct crypto_ec *e, struct crypto_ec_point *p); + +/** + * crypto_ec_point_solve_y_coord - Solve y coordinate for an x coordinate + * @e: EC context from crypto_ec_init() + * @p: EC point to use for the returning the result + * @x: x coordinate + * @y_bit: y-bit (0 or 1) for selecting the y value to use + * Returns: 0 on success, -1 on failure + */ +int crypto_ec_point_solve_y_coord(struct crypto_ec *e, + struct crypto_ec_point *p, + const struct crypto_bignum *x, int y_bit); + +/** + * crypto_ec_point_is_at_infinity - Check whether EC point is neutral element + * @e: EC context from crypto_ec_init() + * @p: EC point + * Returns: 1 if the specified EC point is the neutral element of the group or + * 0 if not + */ +int crypto_ec_point_is_at_infinity(struct crypto_ec *e, + const struct crypto_ec_point *p); + +/** + * crypto_ec_point_is_on_curve - Check whether EC point is on curve + * @e: EC context from crypto_ec_init() + * @p: EC point + * Returns: 1 if the specified EC point is on the curve or 0 if not + */ +int crypto_ec_point_is_on_curve(struct crypto_ec *e, + const struct crypto_ec_point *p); + +#endif /* CRYPTO_H */ diff --git a/src/softsim/crypto/des-internal.c b/src/softsim/crypto/des-internal.c new file mode 100644 index 0000000..4ed6957 --- /dev/null +++ b/src/softsim/crypto/des-internal.c @@ -0,0 +1,494 @@ +/* + * DES and 3DES-EDE ciphers + * + * Modifications to LibTomCrypt implementation: + * Copyright (c) 2006-2009, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "crypto.h" +#include "des_i.h" + +/* + * This implementation is based on a DES implementation included in + * LibTomCrypt. The version here is modified to fit in wpa_supplicant/hostapd + * coding style. + */ + +/* LibTomCrypt, modular cryptographic library -- Tom St Denis + * + * LibTomCrypt is a library that provides various cryptographic + * algorithms in a highly modular and flexible manner. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tomstdenis@gmail.com, http://libtomcrypt.com + */ + +/** + DES code submitted by Dobes Vandermeer +*/ + +#define ROLc(x, y) \ + ((((unsigned long) (x) << (unsigned long) ((y) & 31)) | \ + (((unsigned long) (x) & 0xFFFFFFFFUL) >> \ + (unsigned long) (32 - ((y) & 31)))) & 0xFFFFFFFFUL) +#define RORc(x, y) \ + (((((unsigned long) (x) & 0xFFFFFFFFUL) >> \ + (unsigned long) ((y) & 31)) | \ + ((unsigned long) (x) << (unsigned long) (32 - ((y) & 31)))) & \ + 0xFFFFFFFFUL) + + +static const u32 bytebit[8] = +{ + 0200, 0100, 040, 020, 010, 04, 02, 01 +}; + +static const u32 bigbyte[24] = +{ + 0x800000UL, 0x400000UL, 0x200000UL, 0x100000UL, + 0x80000UL, 0x40000UL, 0x20000UL, 0x10000UL, + 0x8000UL, 0x4000UL, 0x2000UL, 0x1000UL, + 0x800UL, 0x400UL, 0x200UL, 0x100UL, + 0x80UL, 0x40UL, 0x20UL, 0x10UL, + 0x8UL, 0x4UL, 0x2UL, 0x1L +}; + +/* Use the key schedule specific in the standard (ANSI X3.92-1981) */ + +static const u8 pc1[56] = { + 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, + 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, + 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, + 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3 +}; + +static const u8 totrot[16] = { + 1, 2, 4, 6, + 8, 10, 12, 14, + 15, 17, 19, 21, + 23, 25, 27, 28 +}; + +static const u8 pc2[48] = { + 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, + 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, + 40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, + 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 +}; + + +static const u32 SP1[64] = +{ + 0x01010400UL, 0x00000000UL, 0x00010000UL, 0x01010404UL, + 0x01010004UL, 0x00010404UL, 0x00000004UL, 0x00010000UL, + 0x00000400UL, 0x01010400UL, 0x01010404UL, 0x00000400UL, + 0x01000404UL, 0x01010004UL, 0x01000000UL, 0x00000004UL, + 0x00000404UL, 0x01000400UL, 0x01000400UL, 0x00010400UL, + 0x00010400UL, 0x01010000UL, 0x01010000UL, 0x01000404UL, + 0x00010004UL, 0x01000004UL, 0x01000004UL, 0x00010004UL, + 0x00000000UL, 0x00000404UL, 0x00010404UL, 0x01000000UL, + 0x00010000UL, 0x01010404UL, 0x00000004UL, 0x01010000UL, + 0x01010400UL, 0x01000000UL, 0x01000000UL, 0x00000400UL, + 0x01010004UL, 0x00010000UL, 0x00010400UL, 0x01000004UL, + 0x00000400UL, 0x00000004UL, 0x01000404UL, 0x00010404UL, + 0x01010404UL, 0x00010004UL, 0x01010000UL, 0x01000404UL, + 0x01000004UL, 0x00000404UL, 0x00010404UL, 0x01010400UL, + 0x00000404UL, 0x01000400UL, 0x01000400UL, 0x00000000UL, + 0x00010004UL, 0x00010400UL, 0x00000000UL, 0x01010004UL +}; + +static const u32 SP2[64] = +{ + 0x80108020UL, 0x80008000UL, 0x00008000UL, 0x00108020UL, + 0x00100000UL, 0x00000020UL, 0x80100020UL, 0x80008020UL, + 0x80000020UL, 0x80108020UL, 0x80108000UL, 0x80000000UL, + 0x80008000UL, 0x00100000UL, 0x00000020UL, 0x80100020UL, + 0x00108000UL, 0x00100020UL, 0x80008020UL, 0x00000000UL, + 0x80000000UL, 0x00008000UL, 0x00108020UL, 0x80100000UL, + 0x00100020UL, 0x80000020UL, 0x00000000UL, 0x00108000UL, + 0x00008020UL, 0x80108000UL, 0x80100000UL, 0x00008020UL, + 0x00000000UL, 0x00108020UL, 0x80100020UL, 0x00100000UL, + 0x80008020UL, 0x80100000UL, 0x80108000UL, 0x00008000UL, + 0x80100000UL, 0x80008000UL, 0x00000020UL, 0x80108020UL, + 0x00108020UL, 0x00000020UL, 0x00008000UL, 0x80000000UL, + 0x00008020UL, 0x80108000UL, 0x00100000UL, 0x80000020UL, + 0x00100020UL, 0x80008020UL, 0x80000020UL, 0x00100020UL, + 0x00108000UL, 0x00000000UL, 0x80008000UL, 0x00008020UL, + 0x80000000UL, 0x80100020UL, 0x80108020UL, 0x00108000UL +}; + +static const u32 SP3[64] = +{ + 0x00000208UL, 0x08020200UL, 0x00000000UL, 0x08020008UL, + 0x08000200UL, 0x00000000UL, 0x00020208UL, 0x08000200UL, + 0x00020008UL, 0x08000008UL, 0x08000008UL, 0x00020000UL, + 0x08020208UL, 0x00020008UL, 0x08020000UL, 0x00000208UL, + 0x08000000UL, 0x00000008UL, 0x08020200UL, 0x00000200UL, + 0x00020200UL, 0x08020000UL, 0x08020008UL, 0x00020208UL, + 0x08000208UL, 0x00020200UL, 0x00020000UL, 0x08000208UL, + 0x00000008UL, 0x08020208UL, 0x00000200UL, 0x08000000UL, + 0x08020200UL, 0x08000000UL, 0x00020008UL, 0x00000208UL, + 0x00020000UL, 0x08020200UL, 0x08000200UL, 0x00000000UL, + 0x00000200UL, 0x00020008UL, 0x08020208UL, 0x08000200UL, + 0x08000008UL, 0x00000200UL, 0x00000000UL, 0x08020008UL, + 0x08000208UL, 0x00020000UL, 0x08000000UL, 0x08020208UL, + 0x00000008UL, 0x00020208UL, 0x00020200UL, 0x08000008UL, + 0x08020000UL, 0x08000208UL, 0x00000208UL, 0x08020000UL, + 0x00020208UL, 0x00000008UL, 0x08020008UL, 0x00020200UL +}; + +static const u32 SP4[64] = +{ + 0x00802001UL, 0x00002081UL, 0x00002081UL, 0x00000080UL, + 0x00802080UL, 0x00800081UL, 0x00800001UL, 0x00002001UL, + 0x00000000UL, 0x00802000UL, 0x00802000UL, 0x00802081UL, + 0x00000081UL, 0x00000000UL, 0x00800080UL, 0x00800001UL, + 0x00000001UL, 0x00002000UL, 0x00800000UL, 0x00802001UL, + 0x00000080UL, 0x00800000UL, 0x00002001UL, 0x00002080UL, + 0x00800081UL, 0x00000001UL, 0x00002080UL, 0x00800080UL, + 0x00002000UL, 0x00802080UL, 0x00802081UL, 0x00000081UL, + 0x00800080UL, 0x00800001UL, 0x00802000UL, 0x00802081UL, + 0x00000081UL, 0x00000000UL, 0x00000000UL, 0x00802000UL, + 0x00002080UL, 0x00800080UL, 0x00800081UL, 0x00000001UL, + 0x00802001UL, 0x00002081UL, 0x00002081UL, 0x00000080UL, + 0x00802081UL, 0x00000081UL, 0x00000001UL, 0x00002000UL, + 0x00800001UL, 0x00002001UL, 0x00802080UL, 0x00800081UL, + 0x00002001UL, 0x00002080UL, 0x00800000UL, 0x00802001UL, + 0x00000080UL, 0x00800000UL, 0x00002000UL, 0x00802080UL +}; + +static const u32 SP5[64] = +{ + 0x00000100UL, 0x02080100UL, 0x02080000UL, 0x42000100UL, + 0x00080000UL, 0x00000100UL, 0x40000000UL, 0x02080000UL, + 0x40080100UL, 0x00080000UL, 0x02000100UL, 0x40080100UL, + 0x42000100UL, 0x42080000UL, 0x00080100UL, 0x40000000UL, + 0x02000000UL, 0x40080000UL, 0x40080000UL, 0x00000000UL, + 0x40000100UL, 0x42080100UL, 0x42080100UL, 0x02000100UL, + 0x42080000UL, 0x40000100UL, 0x00000000UL, 0x42000000UL, + 0x02080100UL, 0x02000000UL, 0x42000000UL, 0x00080100UL, + 0x00080000UL, 0x42000100UL, 0x00000100UL, 0x02000000UL, + 0x40000000UL, 0x02080000UL, 0x42000100UL, 0x40080100UL, + 0x02000100UL, 0x40000000UL, 0x42080000UL, 0x02080100UL, + 0x40080100UL, 0x00000100UL, 0x02000000UL, 0x42080000UL, + 0x42080100UL, 0x00080100UL, 0x42000000UL, 0x42080100UL, + 0x02080000UL, 0x00000000UL, 0x40080000UL, 0x42000000UL, + 0x00080100UL, 0x02000100UL, 0x40000100UL, 0x00080000UL, + 0x00000000UL, 0x40080000UL, 0x02080100UL, 0x40000100UL +}; + +static const u32 SP6[64] = +{ + 0x20000010UL, 0x20400000UL, 0x00004000UL, 0x20404010UL, + 0x20400000UL, 0x00000010UL, 0x20404010UL, 0x00400000UL, + 0x20004000UL, 0x00404010UL, 0x00400000UL, 0x20000010UL, + 0x00400010UL, 0x20004000UL, 0x20000000UL, 0x00004010UL, + 0x00000000UL, 0x00400010UL, 0x20004010UL, 0x00004000UL, + 0x00404000UL, 0x20004010UL, 0x00000010UL, 0x20400010UL, + 0x20400010UL, 0x00000000UL, 0x00404010UL, 0x20404000UL, + 0x00004010UL, 0x00404000UL, 0x20404000UL, 0x20000000UL, + 0x20004000UL, 0x00000010UL, 0x20400010UL, 0x00404000UL, + 0x20404010UL, 0x00400000UL, 0x00004010UL, 0x20000010UL, + 0x00400000UL, 0x20004000UL, 0x20000000UL, 0x00004010UL, + 0x20000010UL, 0x20404010UL, 0x00404000UL, 0x20400000UL, + 0x00404010UL, 0x20404000UL, 0x00000000UL, 0x20400010UL, + 0x00000010UL, 0x00004000UL, 0x20400000UL, 0x00404010UL, + 0x00004000UL, 0x00400010UL, 0x20004010UL, 0x00000000UL, + 0x20404000UL, 0x20000000UL, 0x00400010UL, 0x20004010UL +}; + +static const u32 SP7[64] = +{ + 0x00200000UL, 0x04200002UL, 0x04000802UL, 0x00000000UL, + 0x00000800UL, 0x04000802UL, 0x00200802UL, 0x04200800UL, + 0x04200802UL, 0x00200000UL, 0x00000000UL, 0x04000002UL, + 0x00000002UL, 0x04000000UL, 0x04200002UL, 0x00000802UL, + 0x04000800UL, 0x00200802UL, 0x00200002UL, 0x04000800UL, + 0x04000002UL, 0x04200000UL, 0x04200800UL, 0x00200002UL, + 0x04200000UL, 0x00000800UL, 0x00000802UL, 0x04200802UL, + 0x00200800UL, 0x00000002UL, 0x04000000UL, 0x00200800UL, + 0x04000000UL, 0x00200800UL, 0x00200000UL, 0x04000802UL, + 0x04000802UL, 0x04200002UL, 0x04200002UL, 0x00000002UL, + 0x00200002UL, 0x04000000UL, 0x04000800UL, 0x00200000UL, + 0x04200800UL, 0x00000802UL, 0x00200802UL, 0x04200800UL, + 0x00000802UL, 0x04000002UL, 0x04200802UL, 0x04200000UL, + 0x00200800UL, 0x00000000UL, 0x00000002UL, 0x04200802UL, + 0x00000000UL, 0x00200802UL, 0x04200000UL, 0x00000800UL, + 0x04000002UL, 0x04000800UL, 0x00000800UL, 0x00200002UL +}; + +static const u32 SP8[64] = +{ + 0x10001040UL, 0x00001000UL, 0x00040000UL, 0x10041040UL, + 0x10000000UL, 0x10001040UL, 0x00000040UL, 0x10000000UL, + 0x00040040UL, 0x10040000UL, 0x10041040UL, 0x00041000UL, + 0x10041000UL, 0x00041040UL, 0x00001000UL, 0x00000040UL, + 0x10040000UL, 0x10000040UL, 0x10001000UL, 0x00001040UL, + 0x00041000UL, 0x00040040UL, 0x10040040UL, 0x10041000UL, + 0x00001040UL, 0x00000000UL, 0x00000000UL, 0x10040040UL, + 0x10000040UL, 0x10001000UL, 0x00041040UL, 0x00040000UL, + 0x00041040UL, 0x00040000UL, 0x10041000UL, 0x00001000UL, + 0x00000040UL, 0x10040040UL, 0x00001000UL, 0x00041040UL, + 0x10001000UL, 0x00000040UL, 0x10000040UL, 0x10040000UL, + 0x10040040UL, 0x10000000UL, 0x00040000UL, 0x10001040UL, + 0x00000000UL, 0x10041040UL, 0x00040040UL, 0x10000040UL, + 0x10040000UL, 0x10001000UL, 0x10001040UL, 0x00000000UL, + 0x10041040UL, 0x00041000UL, 0x00041000UL, 0x00001040UL, + 0x00001040UL, 0x00040040UL, 0x10000000UL, 0x10041000UL +}; + + +static void cookey(const u32 *raw1, u32 *keyout) +{ + u32 *cook; + const u32 *raw0; + u32 dough[32]; + int i; + + cook = dough; + for (i = 0; i < 16; i++, raw1++) { + raw0 = raw1++; + *cook = (*raw0 & 0x00fc0000L) << 6; + *cook |= (*raw0 & 0x00000fc0L) << 10; + *cook |= (*raw1 & 0x00fc0000L) >> 10; + *cook++ |= (*raw1 & 0x00000fc0L) >> 6; + *cook = (*raw0 & 0x0003f000L) << 12; + *cook |= (*raw0 & 0x0000003fL) << 16; + *cook |= (*raw1 & 0x0003f000L) >> 4; + *cook++ |= (*raw1 & 0x0000003fL); + } + + os_memcpy(keyout, dough, sizeof(dough)); +} + + +static void deskey(const u8 *key, int decrypt, u32 *keyout) +{ + u32 i, j, l, m, n, kn[32]; + u8 pc1m[56], pcr[56]; + + for (j = 0; j < 56; j++) { + l = (u32) pc1[j]; + m = l & 7; + pc1m[j] = (u8) + ((key[l >> 3U] & bytebit[m]) == bytebit[m] ? 1 : 0); + } + + for (i = 0; i < 16; i++) { + if (decrypt) + m = (15 - i) << 1; + else + m = i << 1; + n = m + 1; + kn[m] = kn[n] = 0L; + for (j = 0; j < 28; j++) { + l = j + (u32) totrot[i]; + if (l < 28) + pcr[j] = pc1m[l]; + else + pcr[j] = pc1m[l - 28]; + } + for (/* j = 28 */; j < 56; j++) { + l = j + (u32) totrot[i]; + if (l < 56) + pcr[j] = pc1m[l]; + else + pcr[j] = pc1m[l - 28]; + } + for (j = 0; j < 24; j++) { + if ((int) pcr[(int) pc2[j]] != 0) + kn[m] |= bigbyte[j]; + if ((int) pcr[(int) pc2[j + 24]] != 0) + kn[n] |= bigbyte[j]; + } + } + + cookey(kn, keyout); +} + + +static void desfunc(u32 *block, const u32 *keys) +{ + u32 work, right, leftt; + int cur_round; + + leftt = block[0]; + right = block[1]; + + work = ((leftt >> 4) ^ right) & 0x0f0f0f0fL; + right ^= work; + leftt ^= (work << 4); + + work = ((leftt >> 16) ^ right) & 0x0000ffffL; + right ^= work; + leftt ^= (work << 16); + + work = ((right >> 2) ^ leftt) & 0x33333333L; + leftt ^= work; + right ^= (work << 2); + + work = ((right >> 8) ^ leftt) & 0x00ff00ffL; + leftt ^= work; + right ^= (work << 8); + + right = ROLc(right, 1); + work = (leftt ^ right) & 0xaaaaaaaaL; + + leftt ^= work; + right ^= work; + leftt = ROLc(leftt, 1); + + for (cur_round = 0; cur_round < 8; cur_round++) { + work = RORc(right, 4) ^ *keys++; + leftt ^= SP7[work & 0x3fL] + ^ SP5[(work >> 8) & 0x3fL] + ^ SP3[(work >> 16) & 0x3fL] + ^ SP1[(work >> 24) & 0x3fL]; + work = right ^ *keys++; + leftt ^= SP8[ work & 0x3fL] + ^ SP6[(work >> 8) & 0x3fL] + ^ SP4[(work >> 16) & 0x3fL] + ^ SP2[(work >> 24) & 0x3fL]; + + work = RORc(leftt, 4) ^ *keys++; + right ^= SP7[ work & 0x3fL] + ^ SP5[(work >> 8) & 0x3fL] + ^ SP3[(work >> 16) & 0x3fL] + ^ SP1[(work >> 24) & 0x3fL]; + work = leftt ^ *keys++; + right ^= SP8[ work & 0x3fL] + ^ SP6[(work >> 8) & 0x3fL] + ^ SP4[(work >> 16) & 0x3fL] + ^ SP2[(work >> 24) & 0x3fL]; + } + + right = RORc(right, 1); + work = (leftt ^ right) & 0xaaaaaaaaL; + leftt ^= work; + right ^= work; + leftt = RORc(leftt, 1); + work = ((leftt >> 8) ^ right) & 0x00ff00ffL; + right ^= work; + leftt ^= (work << 8); + /* -- */ + work = ((leftt >> 2) ^ right) & 0x33333333L; + right ^= work; + leftt ^= (work << 2); + work = ((right >> 16) ^ leftt) & 0x0000ffffL; + leftt ^= work; + right ^= (work << 16); + work = ((right >> 4) ^ leftt) & 0x0f0f0f0fL; + leftt ^= work; + right ^= (work << 4); + + block[0] = right; + block[1] = leftt; +} + + +/* wpa_supplicant/hostapd specific wrapper */ + +int des_encrypt(const u8 *clear, const u8 *key, u8 *cypher) +{ + u8 pkey[8], next, tmp; + int i; + u32 ek[32], work[2]; + + /* Add parity bits to the key */ + next = 0; + for (i = 0; i < 7; i++) { + tmp = key[i]; + pkey[i] = (tmp >> i) | next | 1; + next = tmp << (7 - i); + } + pkey[i] = next | 1; + + deskey(pkey, 0, ek); + + work[0] = WPA_GET_BE32(clear); + work[1] = WPA_GET_BE32(clear + 4); + desfunc(work, ek); + WPA_PUT_BE32(cypher, work[0]); + WPA_PUT_BE32(cypher + 4, work[1]); + + os_memset(pkey, 0, sizeof(pkey)); + os_memset(ek, 0, sizeof(ek)); + return 0; +} + + +void des_key_setup(const u8 *key, u32 *ek, u32 *dk) +{ + deskey(key, 0, ek); + deskey(key, 1, dk); +} + + +void des_block_encrypt(const u8 *plain, const u32 *ek, u8 *crypt) +{ + u32 work[2]; + work[0] = WPA_GET_BE32(plain); + work[1] = WPA_GET_BE32(plain + 4); + desfunc(work, ek); + WPA_PUT_BE32(crypt, work[0]); + WPA_PUT_BE32(crypt + 4, work[1]); +} + + +void des_block_decrypt(const u8 *crypt, const u32 *dk, u8 *plain) +{ + u32 work[2]; + work[0] = WPA_GET_BE32(crypt); + work[1] = WPA_GET_BE32(crypt + 4); + desfunc(work, dk); + WPA_PUT_BE32(plain, work[0]); + WPA_PUT_BE32(plain + 4, work[1]); +} + + +void des3_key_setup(const u8 *key, struct des3_key_s *dkey) +{ + deskey(key, 0, dkey->ek[0]); + deskey(key + 8, 1, dkey->ek[1]); + deskey(key + 16, 0, dkey->ek[2]); + + deskey(key, 1, dkey->dk[2]); + deskey(key + 8, 0, dkey->dk[1]); + deskey(key + 16, 1, dkey->dk[0]); +} + + +void des3_encrypt(const u8 *plain, const struct des3_key_s *key, u8 *crypt) +{ + u32 work[2]; + + work[0] = WPA_GET_BE32(plain); + work[1] = WPA_GET_BE32(plain + 4); + desfunc(work, key->ek[0]); + desfunc(work, key->ek[1]); + desfunc(work, key->ek[2]); + WPA_PUT_BE32(crypt, work[0]); + WPA_PUT_BE32(crypt + 4, work[1]); +} + + +void des3_decrypt(const u8 *crypt, const struct des3_key_s *key, u8 *plain) +{ + u32 work[2]; + + work[0] = WPA_GET_BE32(crypt); + work[1] = WPA_GET_BE32(crypt + 4); + desfunc(work, key->dk[0]); + desfunc(work, key->dk[1]); + desfunc(work, key->dk[2]); + WPA_PUT_BE32(plain, work[0]); + WPA_PUT_BE32(plain + 4, work[1]); +} diff --git a/src/softsim/crypto/des_i.h b/src/softsim/crypto/des_i.h new file mode 100644 index 0000000..c766f02 --- /dev/null +++ b/src/softsim/crypto/des_i.h @@ -0,0 +1,27 @@ +/* + * DES and 3DES-EDE ciphers + * Copyright (c) 2006-2009, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef DES_I_H +#define DES_I_H + +#include "common.h" + +struct des3_key_s { + u32 ek[3][32]; + u32 dk[3][32]; +}; + +void des_key_setup(const u8 *key, u32 *ek, u32 *dk); +void des_block_encrypt(const u8 *plain, const u32 *ek, u8 *crypt); +void des_block_decrypt(const u8 *crypt, const u32 *dk, u8 *plain); + +void des3_key_setup(const u8 *key, struct des3_key_s *dkey); +void des3_encrypt(const u8 *plain, const struct des3_key_s *key, u8 *crypt); +void des3_decrypt(const u8 *crypt, const struct des3_key_s *key, u8 *plain); + +#endif /* DES_I_H */ diff --git a/src/softsim/crypto/includes.h b/src/softsim/crypto/includes.h new file mode 100644 index 0000000..e69de29 diff --git a/src/softsim/main.c b/src/softsim/main.c new file mode 100644 index 0000000..519069f --- /dev/null +++ b/src/softsim/main.c @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VPCD_PORT 0x8C7B +#define VPCD_HOST "127.0.0.1" + +#define VPCD_CTRL_OFF 0x00 +#define VPCD_CTRL_ON 0x01 +#define VPCD_CTRL_RESET 0x02 +#define VPCD_CTRL_ATR 0x04 + +bool running = true; +#define POLL_INTERVAL 5 /* sec */ + +static int vpcd_rx(int socket_fd, uint8_t *buf, size_t len) +{ + int rc; + uint16_t len_rx; + uint16_t len_bytes; + + /* Receive length */ + rc = read(socket_fd, &len_rx, sizeof(len_rx)); + if (rc != 2) { + SS_LOGP(SVPCD, running ? LERROR : LINFO, + "vpcd_rx: error reading length -- abort!\n"); + return -EINVAL; + } + len_bytes = ntohs(len_rx); + if (len_bytes > len) { + SS_LOGP(SVPCD, LERROR, + "vpcd_rx: buffer too small (%lu < %u) -- abort!\n", len, + len_bytes); + return -EINVAL; + } + + /* Receive data */ + rc = read(socket_fd, buf, len_bytes); + if (rc < 0) { + SS_LOGP(SVPCD, LERROR, "vpcd_rx: no data received -- abort!\n"); + return -EINVAL; + } + + SS_LOGP(SVPCD, LDEBUG, "vpcd_rx: received %u bytes: %04x:%s\n", rc, + len_rx, ss_hexdump(buf, rc)); + + return rc; +} + +static int vpcd_tx(int socket_fd, uint8_t *buf, size_t len) +{ + uint16_t len_tx; + int rc; + + assert(len <= 0xffff); + + len_tx = htons((uint16_t) len); + SS_LOGP(SVPCD, LDEBUG, "vpcd_tx: sending %lu bytes: %04x:%s\n", len, + len_tx, ss_hexdump(buf, len)); + rc = write(socket_fd, &len_tx, sizeof(len_tx)); + if (rc != 2) + return -EINVAL; + rc = write(socket_fd, buf, len); + if (rc != len) + return -EINVAL; + + return 0; +} + +static int handle_request(struct ss_context *ctx, int socket_fd) +{ + uint8_t vpcd_pdu[65536]; + int rc; + uint8_t card_response[256+2]; + size_t card_response_len; + + rc = vpcd_rx(socket_fd, vpcd_pdu, sizeof(vpcd_pdu)); + if (rc < 0) { + SS_LOGP(SVPCD, running ? LERROR : LINFO, "VPCD socket disconnected, terminating\n"); + exit(0); + } + if (rc == 1) { + /* Control byte */ + switch (vpcd_pdu[0]) { + case VPCD_CTRL_OFF: + SS_LOGP(SVPCD, LDEBUG, "POWER OFF request => reset\n"); + ss_reset(ctx); + break; + case VPCD_CTRL_ON: + SS_LOGP(SVPCD, LDEBUG, "POWER ON request => reset\n"); + ss_reset(ctx); + break; + case VPCD_CTRL_RESET: + SS_LOGP(SVPCD, LDEBUG, "RESET request\n"); + ss_reset(ctx); + break; + case VPCD_CTRL_ATR: + SS_LOGP(SVPCD, LDEBUG, "ATR request\n"); + card_response_len = + ss_atr(ctx,card_response, sizeof(card_response)); + vpcd_tx(socket_fd, card_response, card_response_len); + break; + default: + SS_LOGP(SVPCD, LDEBUG, "Invalid request => ignored\n"); + } + } else { + size_t request_length = rc; + /* Card APDU */ + card_response_len = + ss_transact(ctx, card_response, sizeof(card_response), + vpcd_pdu, &request_length); + if (request_length >= 0 && request_length < rc) + SS_LOGP(SVPCD, LERROR, "APDU contained trailing bytes that were ignored.\n"); + vpcd_tx(socket_fd, card_response, card_response_len); + } + return rc; +} + +/* Subtract timeval y from x, see also: + * https://www.gnu.org/software/libc/manual/html_node/Calculating-Elapsed-Time.html */ +int timeval_subtract(struct timeval *result, struct timeval *x, + struct timeval *y) +{ + if (x->tv_usec < y->tv_usec) { + int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1; + y->tv_usec -= 1000000 * nsec; + y->tv_sec += nsec; + } + if (x->tv_usec - y->tv_usec > 1000000) { + int nsec = (x->tv_usec - y->tv_usec) / 1000000; + y->tv_usec += 1000000 * nsec; + y->tv_sec -= nsec; + } + result->tv_sec = x->tv_sec - y->tv_sec; + result->tv_usec = x->tv_usec - y->tv_usec; + return x->tv_sec < y->tv_sec; +} + +/* Handle regular polling of the softsim (proactive SIM tasks) */ +static void handle_poll(struct ss_context *ctx) +{ + struct timeval time; + static struct timeval time_prev = { }; + struct timeval time_elapsed; + static struct timeval timer = { }; + int rc; + + gettimeofday(&time, NULL); + if (time_prev.tv_sec == 0 && time_prev.tv_usec == 0) + time_prev = time; + + rc = timeval_subtract(&time_elapsed, &time, &time_prev); + if (rc < 0) { + goto leave; + } + + if (timer.tv_usec + time_elapsed.tv_usec > 999999) { + timer.tv_usec = + (timer.tv_usec + time_elapsed.tv_usec) - 1000000; + timer.tv_sec = timer.tv_sec + time_elapsed.tv_sec + 1; + } else { + timer.tv_usec = timer.tv_usec + time_elapsed.tv_usec; + timer.tv_sec = timer.tv_sec + time_elapsed.tv_sec; + } + + if (timer.tv_sec >= POLL_INTERVAL) { + ss_poll(ctx); + timer.tv_sec = 0; + timer.tv_usec = 0; + } + +leave: + time_prev = time; +} + +/*! Terminate the program regularly. + * + * This allows a test environment to send SIGUSR1 to the process when it is + * done. As opposed to any default handler, this lets the process exit + * successfully in the regular case, whereas the address sanitizer (ASAN) has + * still a chance to run and set an unsuccessful exit state if any memory leaks + * were detected. + */ +static void sig_usr1(int signum) +{ + running = false; +} + +int main(void) +{ + int socket_fd; + int socket_flag; + struct sockaddr_in vpcd_server_addr; + struct ss_context *ctx; + fd_set fdset; + int rc; + struct timeval select_timer; + struct timeval select_timeout; + + signal(SIGUSR1, sig_usr1); + + SS_LOGP(SVPCD, LINFO, "softsim!\n"); + + ctx = ss_new_ctx(); + + socket_fd = socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd < 0) { + SS_LOGP(SVPCD, LERROR, + "cannot create socket for VPCD server -- abort!\n"); + exit(1); + } + + vpcd_server_addr.sin_family = AF_INET; + vpcd_server_addr.sin_addr.s_addr = inet_addr(VPCD_HOST); + vpcd_server_addr.sin_port = htons(VPCD_PORT); + + socket_flag = 1; + if (setsockopt + (socket_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&socket_flag, + sizeof(socket_fd))) { + SS_LOGP(SVPCD, LERROR, "cannot set socket options -- abort!\n"); + exit(1); + } + + if (connect + (socket_fd, (struct sockaddr *)&vpcd_server_addr, + sizeof(vpcd_server_addr)) != 0) { + SS_LOGP(SVPCD, LERROR, + "cannot connect to VPCD server -- abort!\n"); + exit(1); + } + + SS_LOGP(SVPCD, LINFO, "connected.\n"); + + FD_ZERO(&fdset); + FD_SET(socket_fd, &fdset); + + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 500000; + + while (running) { + + select_timer = select_timeout; + + rc = select(socket_fd+1, &fdset, NULL, NULL, &select_timer); + if (rc < 0) + SS_LOGP(SVPCD, LERROR, "error in select -- abort!\n"); + else if (rc) + handle_request(ctx, socket_fd); + else + SS_LOGP(SVPCD, LERROR, "select timeout -- pcscd silent?\n"); + + handle_poll(ctx); + } + + ss_free_ctx(ctx); + + return 0; +} diff --git a/src/softsim/milenage/CMakeLists.txt b/src/softsim/milenage/CMakeLists.txt new file mode 100644 index 0000000..1311eab --- /dev/null +++ b/src/softsim/milenage/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +add_library(milenage + STATIC + milenage.c + milenage_usim.c +) + +target_include_directories(milenage + PUBLIC + ${CMAKE_SOURCE_DIR}/include + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../ +) diff --git a/src/softsim/milenage/Makefile.am b/src/softsim/milenage/Makefile.am new file mode 100644 index 0000000..a43a558 --- /dev/null +++ b/src/softsim/milenage/Makefile.am @@ -0,0 +1,28 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir) \ + -I$(top_srcdir)/src/softsim \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(NULL) + +AM_LDFLAGS = \ + $(NULL) + +noinst_LIBRARIES = libmilenage.a + +libmilenage_a_SOURCES = \ + milenage.c \ + milenage_usim.c \ + $(NULL) + +noinst_HEADERS = \ + milenage.h \ + milenage_usim.h \ + $(NULL) diff --git a/src/softsim/milenage/milenage.c b/src/softsim/milenage/milenage.c new file mode 100644 index 0000000..c41e871 --- /dev/null +++ b/src/softsim/milenage/milenage.c @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2006-2007 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + * + * SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + * + * This file implements an example authentication algorithm defined for 3GPP + * AKA. This can be used to implement a simple HLR/AuC into hlr_auc_gw to allow + * EAP-AKA to be tested properly with real USIM cards. + * + * This implementations assumes that the r1..r5 and c1..c5 constants defined in + * TS 35.206 are used, i.e., r1=64, r2=0, r3=32, r4=64, r5=96, c1=00..00, + * c2=00..01, c3=00..02, c4=00..04, c5=00..08. The block cipher is assumed to + * be AES (Rijndael). + */ + +#include "crypto/includes.h" + +#include "crypto/common.h" +#include "crypto/aes_wrap.h" +#include "milenage.h" + + +/** + * milenage_f1 - Milenage f1 and f1* algorithms + * @opc: OPc = 128-bit value derived from OP and K + * @k: K = 128-bit subscriber key + * @_rand: RAND = 128-bit random challenge + * @sqn: SQN = 48-bit sequence number + * @amf: AMF = 16-bit authentication management field + * @mac_a: Buffer for MAC-A = 64-bit network authentication code, or %NULL + * @mac_s: Buffer for MAC-S = 64-bit resync authentication code, or %NULL + * Returns: 0 on success, -1 on failure + */ +int milenage_f1(const u8 *opc, const u8 *k, const u8 *_rand, + const u8 *sqn, const u8 *amf, u8 *mac_a, u8 *mac_s) +{ + u8 tmp1[16], tmp2[16], tmp3[16]; + int i; + + /* tmp1 = TEMP = E_K(RAND XOR OP_C) */ + for (i = 0; i < 16; i++) + tmp1[i] = _rand[i] ^ opc[i]; + if (aes_128_encrypt_block(k, tmp1, tmp1)) + return -1; + + /* tmp2 = IN1 = SQN || AMF || SQN || AMF */ + os_memcpy(tmp2, sqn, 6); + os_memcpy(tmp2 + 6, amf, 2); + os_memcpy(tmp2 + 8, tmp2, 8); + + /* OUT1 = E_K(TEMP XOR rot(IN1 XOR OP_C, r1) XOR c1) XOR OP_C */ + + /* rotate (tmp2 XOR OP_C) by r1 (= 0x40 = 8 bytes) */ + for (i = 0; i < 16; i++) + tmp3[(i + 8) % 16] = tmp2[i] ^ opc[i]; + /* XOR with TEMP = E_K(RAND XOR OP_C) */ + for (i = 0; i < 16; i++) + tmp3[i] ^= tmp1[i]; + /* XOR with c1 (= ..00, i.e., NOP) */ + + /* f1 || f1* = E_K(tmp3) XOR OP_c */ + if (aes_128_encrypt_block(k, tmp3, tmp1)) + return -1; + for (i = 0; i < 16; i++) + tmp1[i] ^= opc[i]; + if (mac_a) + os_memcpy(mac_a, tmp1, 8); /* f1 */ + if (mac_s) + os_memcpy(mac_s, tmp1 + 8, 8); /* f1* */ + return 0; +} + + +/** + * milenage_f2345 - Milenage f2, f3, f4, f5, f5* algorithms + * @opc: OPc = 128-bit value derived from OP and K + * @k: K = 128-bit subscriber key + * @_rand: RAND = 128-bit random challenge + * @res: Buffer for RES = 64-bit signed response (f2), or %NULL + * @ck: Buffer for CK = 128-bit confidentiality key (f3), or %NULL + * @ik: Buffer for IK = 128-bit integrity key (f4), or %NULL + * @ak: Buffer for AK = 48-bit anonymity key (f5), or %NULL + * @akstar: Buffer for AK = 48-bit anonymity key (f5*), or %NULL + * Returns: 0 on success, -1 on failure + */ +int milenage_f2345(const u8 *opc, const u8 *k, const u8 *_rand, + u8 *res, u8 *ck, u8 *ik, u8 *ak, u8 *akstar) +{ + u8 tmp1[16], tmp2[16], tmp3[16]; + int i; + + /* tmp2 = TEMP = E_K(RAND XOR OP_C) */ + for (i = 0; i < 16; i++) + tmp1[i] = _rand[i] ^ opc[i]; + if (aes_128_encrypt_block(k, tmp1, tmp2)) + return -1; + + /* OUT2 = E_K(rot(TEMP XOR OP_C, r2) XOR c2) XOR OP_C */ + /* OUT3 = E_K(rot(TEMP XOR OP_C, r3) XOR c3) XOR OP_C */ + /* OUT4 = E_K(rot(TEMP XOR OP_C, r4) XOR c4) XOR OP_C */ + /* OUT5 = E_K(rot(TEMP XOR OP_C, r5) XOR c5) XOR OP_C */ + + /* f2 and f5 */ + /* rotate by r2 (= 0, i.e., NOP) */ + for (i = 0; i < 16; i++) + tmp1[i] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 1; /* XOR c2 (= ..01) */ + /* f5 || f2 = E_K(tmp1) XOR OP_c */ + if (aes_128_encrypt_block(k, tmp1, tmp3)) + return -1; + for (i = 0; i < 16; i++) + tmp3[i] ^= opc[i]; + if (res) + os_memcpy(res, tmp3 + 8, 8); /* f2 */ + if (ak) + os_memcpy(ak, tmp3, 6); /* f5 */ + + /* f3 */ + if (ck) { + /* rotate by r3 = 0x20 = 4 bytes */ + for (i = 0; i < 16; i++) + tmp1[(i + 12) % 16] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 2; /* XOR c3 (= ..02) */ + if (aes_128_encrypt_block(k, tmp1, ck)) + return -1; + for (i = 0; i < 16; i++) + ck[i] ^= opc[i]; + } + + /* f4 */ + if (ik) { + /* rotate by r4 = 0x40 = 8 bytes */ + for (i = 0; i < 16; i++) + tmp1[(i + 8) % 16] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 4; /* XOR c4 (= ..04) */ + if (aes_128_encrypt_block(k, tmp1, ik)) + return -1; + for (i = 0; i < 16; i++) + ik[i] ^= opc[i]; + } + + /* f5* */ + if (akstar) { + /* rotate by r5 = 0x60 = 12 bytes */ + for (i = 0; i < 16; i++) + tmp1[(i + 4) % 16] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 8; /* XOR c5 (= ..08) */ + if (aes_128_encrypt_block(k, tmp1, tmp1)) + return -1; + for (i = 0; i < 6; i++) + akstar[i] = tmp1[i] ^ opc[i]; + } + + return 0; +} + + +/** + * milenage_generate - Generate AKA AUTN,IK,CK,RES + * @opc: OPc = 128-bit operator variant algorithm configuration field (encr.) + * @amf: AMF = 16-bit authentication management field + * @k: K = 128-bit subscriber key + * @sqn: SQN = 48-bit sequence number + * @_rand: RAND = 128-bit random challenge + * @autn: Buffer for AUTN = 128-bit authentication token + * @ik: Buffer for IK = 128-bit integrity key (f4), or %NULL + * @ck: Buffer for CK = 128-bit confidentiality key (f3), or %NULL + * @res: Buffer for RES = 64-bit signed response (f2), or %NULL + * @res_len: Max length for res; set to used length or 0 on failure + */ +void milenage_generate(const u8 *opc, const u8 *amf, const u8 *k, + const u8 *sqn, const u8 *_rand, u8 *autn, u8 *ik, + u8 *ck, u8 *res, size_t *res_len) +{ + int i; + u8 mac_a[8], ak[6]; + + if (*res_len < 8) { + *res_len = 0; + return; + } + if (milenage_f1(opc, k, _rand, sqn, amf, mac_a, NULL) || + milenage_f2345(opc, k, _rand, res, ck, ik, ak, NULL)) { + *res_len = 0; + return; + } + *res_len = 8; + + /* AUTN = (SQN ^ AK) || AMF || MAC */ + for (i = 0; i < 6; i++) + autn[i] = sqn[i] ^ ak[i]; + os_memcpy(autn + 6, amf, 2); + os_memcpy(autn + 8, mac_a, 8); +} + + +/** + * milenage_auts - Milenage AUTS validation + * @opc: OPc = 128-bit operator variant algorithm configuration field (encr.) + * @k: K = 128-bit subscriber key + * @_rand: RAND = 128-bit random challenge + * @auts: AUTS = 112-bit authentication token from client + * @sqn: Buffer for SQN = 48-bit sequence number + * Returns: 0 = success (sqn filled), -1 on failure + */ +int milenage_auts(const u8 *opc, const u8 *k, const u8 *_rand, const u8 *auts, + u8 *sqn) +{ + u8 amf[2] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */ + u8 ak[6], mac_s[8]; + int i; + + if (milenage_f2345(opc, k, _rand, NULL, NULL, NULL, NULL, ak)) + return -1; + for (i = 0; i < 6; i++) + sqn[i] = auts[i] ^ ak[i]; + if (milenage_f1(opc, k, _rand, sqn, amf, NULL, mac_s) || + os_memcmp_const(mac_s, auts + 6, 8) != 0) + return -1; + return 0; +} + + +/** + * gsm_milenage - Generate GSM-Milenage (3GPP TS 55.205) authentication triplet + * @opc: OPc = 128-bit operator variant algorithm configuration field (encr.) + * @k: K = 128-bit subscriber key + * @_rand: RAND = 128-bit random challenge + * @sres: Buffer for SRES = 32-bit SRES. May be NULL if that output is not needed. + * @kc: Buffer for Kc = 64-bit Kc + * Returns: 0 on success, -1 on failure + */ +int gsm_milenage(const u8 *opc, const u8 *k, const u8 *_rand, u8 *sres, u8 *kc) +{ + u8 res[8], ck[16], ik[16]; + int i; + + if (milenage_f2345(opc, k, _rand, res, ck, ik, NULL, NULL)) + return -1; + + for (i = 0; i < 8; i++) + kc[i] = ck[i] ^ ck[i + 8] ^ ik[i] ^ ik[i + 8]; + + if (sres != NULL) { +#ifdef GSM_MILENAGE_ALT_SRES + os_memcpy(sres, res, 4); +#else /* GSM_MILENAGE_ALT_SRES */ + for (i = 0; i < 4; i++) + sres[i] = res[i] ^ res[i + 4]; +#endif /* GSM_MILENAGE_ALT_SRES */ + } + return 0; +} + + +/** + * milenage_generate - Generate AKA AUTN,IK,CK,RES + * @opc: OPc = 128-bit operator variant algorithm configuration field (encr.) + * @k: K = 128-bit subscriber key + * @sqn: SQN = 48-bit sequence number + * @_rand: RAND = 128-bit random challenge + * @autn: AUTN = 128-bit authentication token + * @ik: Buffer for IK = 128-bit integrity key (f4), or %NULL + * @ck: Buffer for CK = 128-bit confidentiality key (f3), or %NULL + * @res: Buffer for RES = 64-bit signed response (f2), or %NULL + * @res_len: Variable that will be set to RES length + * @auts: 112-bit buffer for AUTS + * Returns: 0 on success, -1 on failure, or -2 on synchronization failure + */ +int milenage_check(const u8 *opc, const u8 *k, const u8 *sqn, const u8 *_rand, + const u8 *autn, u8 *ik, u8 *ck, u8 *res, size_t *res_len, + u8 *auts) +{ + int i; + u8 mac_a[8], ak[6], rx_sqn[6]; + const u8 *amf; + + wpa_hexdump(MSG_DEBUG, "Milenage: AUTN", autn, 16); + wpa_hexdump(MSG_DEBUG, "Milenage: RAND", _rand, 16); + + if (milenage_f2345(opc, k, _rand, res, ck, ik, ak, NULL)) + return -1; + + *res_len = 8; + wpa_hexdump_key(MSG_DEBUG, "Milenage: RES", res, *res_len); + wpa_hexdump_key(MSG_DEBUG, "Milenage: CK", ck, 16); + wpa_hexdump_key(MSG_DEBUG, "Milenage: IK", ik, 16); + wpa_hexdump_key(MSG_DEBUG, "Milenage: AK", ak, 6); + + /* AUTN = (SQN ^ AK) || AMF || MAC */ + for (i = 0; i < 6; i++) + rx_sqn[i] = autn[i] ^ ak[i]; + wpa_hexdump(MSG_DEBUG, "Milenage: SQN", rx_sqn, 6); + + if (os_memcmp(rx_sqn, sqn, 6) <= 0) { + u8 auts_amf[2] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */ + if (milenage_f2345(opc, k, _rand, NULL, NULL, NULL, NULL, ak)) + return -1; + wpa_hexdump_key(MSG_DEBUG, "Milenage: AK*", ak, 6); + for (i = 0; i < 6; i++) + auts[i] = sqn[i] ^ ak[i]; + if (milenage_f1(opc, k, _rand, sqn, auts_amf, NULL, auts + 6)) + return -1; + wpa_hexdump(MSG_DEBUG, "Milenage: AUTS", auts, 14); + return -2; + } + + amf = autn + 6; + wpa_hexdump(MSG_DEBUG, "Milenage: AMF", amf, 2); + if (milenage_f1(opc, k, _rand, rx_sqn, amf, mac_a, NULL)) + return -1; + + wpa_hexdump(MSG_DEBUG, "Milenage: MAC_A", mac_a, 8); + + if (os_memcmp_const(mac_a, autn + 8, 8) != 0) { + wpa_printf(MSG_DEBUG, "Milenage: MAC mismatch"); + wpa_hexdump(MSG_DEBUG, "Milenage: Received MAC_A", + autn + 8, 8); + return -1; + } + + return 0; +} + +int milenage_opc_gen(u8 *opc, const u8 *k, const u8 *op) +{ + int i; + + /* Encrypt OP using K */ + if (aes_128_encrypt_block(k, op, opc)) + return -1; + + /* XOR the resulting Ek(OP) with OP */ + for (i = 0; i < 16; i++) + opc[i] = opc[i] ^ op[i]; + + return 0; +} diff --git a/src/softsim/milenage/milenage.h b/src/softsim/milenage/milenage.h new file mode 100644 index 0000000..8384f98 --- /dev/null +++ b/src/softsim/milenage/milenage.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2006-2007 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * SPDX-License-Identifier: GPL-2.0 or BSD-3-Clause + * + * See README and COPYING for more details. + */ + +#ifndef MILENAGE_H +#define MILENAGE_H + +#include "crypto/common.h" + +void milenage_generate(const u8 *opc, const u8 *amf, const u8 *k, + const u8 *sqn, const u8 *_rand, u8 *autn, u8 *ik, + u8 *ck, u8 *res, size_t *res_len); +int milenage_auts(const u8 *opc, const u8 *k, const u8 *_rand, const u8 *auts, + u8 *sqn); +int gsm_milenage(const u8 *opc, const u8 *k, const u8 *_rand, u8 *sres, + u8 *kc); +int milenage_check(const u8 *opc, const u8 *k, const u8 *sqn, const u8 *_rand, + const u8 *autn, u8 *ik, u8 *ck, u8 *res, size_t *res_len, + u8 *auts); +int milenage_f1(const u8 *opc, const u8 *k, const u8 *_rand, + const u8 *sqn, const u8 *amf, u8 *mac_a, u8 *mac_s); +int milenage_f2345(const u8 *opc, const u8 *k, const u8 *_rand, + u8 *res, u8 *ck, u8 *ik, u8 *ak, u8 *akstar); + +int milenage_opc_gen(u8 *opc, const u8 *k, const u8 *op); + +#endif /* MILENAGE_H */ diff --git a/src/softsim/milenage/milenage_usim.c b/src/softsim/milenage/milenage_usim.c new file mode 100644 index 0000000..4793b2a --- /dev/null +++ b/src/softsim/milenage/milenage_usim.c @@ -0,0 +1,187 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * + * 3GPP AKA - SIM side validation as per TS 33.102 + * Author: Harald Welte + * + * The hostap milenage.c code doesn't really work for the "USIM side", + * as it doesn't implement Annex C of 3GPP TS 33.102. So we don't use + * milenage_check() from there, but the code from here. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "crypto/includes.h" +#include "crypto/common.h" +#include "uicc/utils.h" + +#include "milenage.h" +#include "milenage_usim.h" + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + +#define MILENAGE_IND_MASK ((1<seq); i++) { + if (sd->seq[i] > highest) + highest = sd->seq[i]; + } + return highest; +} + +static u64 load_u48be(const u8 *in) +{ + u64 ret; + + ret = ((u64)in[0]) << (5 * 8); + ret |= ((u64)in[1]) << (4 * 8); + ret |= ((u64)in[2]) << (3 * 8); + ret |= ((u64)in[3]) << (2 * 8); + ret |= ((u64)in[4]) << (1 * 8); + ret |= ((u64)in[5]) << (0 * 8); + + return ret; +} + +static void store_u48be(u8 *out, u64 in) +{ + out[0] = (in >> (5 * 8)); + out[1] = (in >> (4 * 8)); + out[2] = (in >> (3 * 8)); + out[3] = (in >> (2 * 8)); + out[4] = (in >> (1 * 8)); + out[5] = (in >> (0 * 8)); +} + + +/** + * milenage_usim_check - Check MILENAGE authentication + * @kd: caller-provided key data (k/op/opc/...) + * @sd: caller-provided sequence number data. may be updated! + * @mr: caller-allocated memory for output data + * @_rand: RAND = 128-bit random challenge + * @autn: AUTN = 128-bit authentication token + * Returns: 0 on success, -1 on failure, or -2 on synchronization failure + * + * See Annex C.2.2 of 3GPP TS 33.102 for the details on how the USIM checks for + * SQN freshness. + */ +int milenage_usim_check(const struct milenage_key_data *kd, + struct milenage_seq_data *sd, + struct milenage_result *mr, + const u8 *_rand, const u8 *autn) +{ + u8 opc[16]; + u8 mac_a[8], ak[6], rx_sqn[6]; + const u8 *amf; + u8 ind; + u64 rx_sqn64, rx_seq, seq_ms; + int ret = -1; + + u8 auts_amf[2] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */ + /* USIM shall generate a synchronisation failure message + * using the highest previously accepted sequence number + * anywhere in the array, i.e. SQN_MS. */ + u64 highest_seq_ms = get_highest_seq_ms(sd); + u8 highest_sqn_ms[6]; + + wpa_hexdump(MSG_DEBUG, "Milenage: AUTN", autn, 16); + wpa_hexdump(MSG_DEBUG, "Milenage: RAND", _rand, 16); + + if (!kd->opc_is_op) { + /* OPC is already the OPC */ + os_memcpy(opc, kd->opc, sizeof(opc)); + } else { + int rc = milenage_opc_gen(opc, kd->k, kd->opc); + if (rc < 0) + goto out; + } + + if (milenage_f2345(opc, kd->k, _rand, mr->res, mr->ck, mr->ik, ak, NULL)) + goto out; + + mr->res_len = 8; + wpa_hexdump_key(MSG_DEBUG, "Milenage: RES", mr->res, kd->res_len); + wpa_hexdump_key(MSG_DEBUG, "Milenage: CK", mr->ck, 16); + wpa_hexdump_key(MSG_DEBUG, "Milenage: IK", mr->ik, 16); + wpa_hexdump_key(MSG_DEBUG, "Milenage: AK", mr->ak, 6); + + /* AUTN = (SQN ^ AK) || AMF || MAC */ + for (unsigned int i = 0; i < 6; i++) + rx_sqn[i] = autn[i] ^ ak[i]; + wpa_hexdump(MSG_DEBUG, "Milenage: SQN", rx_sqn, 6); + + /* Determine IND and SEQ from SQN */ + rx_sqn64 = load_u48be(rx_sqn); + ind = rx_sqn64 & MILENAGE_IND_MASK; + rx_seq = rx_sqn64 >> MILENAGE_IND_LEN; + + /* FIXME #54: check whether this can be anything else than highest_seq_ms */ + seq_ms = get_highest_seq_ms(sd); + + /* the received sequence number SQN shall only be + accepted by the USIM if SEQ - SEQ_MS ≤ delta */ + if (rx_seq - seq_ms >= sd->delta) { + /* C.2.1 unsuccessful case: SEQ - SEQ_MS >= delta */ + goto out_auts; + } + + if (rx_seq <= sd->seq[ind]) { + /* C.2.2 unsuccessful case: SEQ <= SEQ_MS(i) */ + goto out_auts; + } + + /* C.2.2 successful case: SEQ > SEQ_MS(i) */ + sd->seq[ind] = rx_seq; + + amf = autn + 6; + wpa_hexdump(MSG_DEBUG, "Milenage: AMF", amf, 2); + if (milenage_f1(opc, kd->k, _rand, rx_sqn, amf, mac_a, NULL)) + goto out; + + wpa_hexdump(MSG_DEBUG, "Milenage: MAC_A", mac_a, 8); + + if (os_memcmp_const(mac_a, autn + 8, 8) != 0) { + wpa_printf(MSG_DEBUG, "Milenage: MAC mismatch"); + wpa_hexdump(MSG_DEBUG, "Milenage: Received MAC_A", + autn + 8, 8); + goto out; + } + + ret = 0; + +out: + /* clear cryptographic sensitive data from stack */ + ss_memzero(opc, sizeof(opc)); + ss_memzero(mac_a, sizeof(mac_a)); + ss_memzero(ak, sizeof(ak)); + ss_memzero(rx_sqn, sizeof(rx_sqn)); + + return ret; + + +out_auts: + + store_u48be(highest_sqn_ms, (highest_seq_ms << MILENAGE_IND_LEN) | ind); + + if (milenage_f2345(opc, kd->k, _rand, NULL, NULL, NULL, NULL, ak)) + goto out; + wpa_hexdump_key(MSG_DEBUG, "Milenage: AK*", ak, 6); + for (unsigned int i = 0; i < 6; i++) + mr->auts[i] = highest_sqn_ms[i] ^ ak[i]; + if (milenage_f1(opc, kd->k, _rand, highest_sqn_ms, auts_amf, NULL, mr->auts + 6)) + goto out; + wpa_hexdump(MSG_DEBUG, "Milenage: AUTS", mr->auts, 14); + + /* clear cryptographic sensitive data from stack */ + ret = -2; + goto out; + +} diff --git a/src/softsim/milenage/milenage_usim.h b/src/softsim/milenage/milenage_usim.h new file mode 100644 index 0000000..61eab0f --- /dev/null +++ b/src/softsim/milenage/milenage_usim.h @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * + * 3GPP AKA - SIM side validation as per TS 33.102 + * Author: Harald Welte + * + * The hostap milenage.c code doesn't really work for the "USIM side", + * as it doesn't implement Annex C of 3GPP TS 33.102. So we don't use + * milenage_check() from there, but the code from here. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#pragma once + +#include "crypto/common.h" + +/* Length of the IND bits (lower bits of SQN) + * + * The value of 5 is constant across all the profiles in TS 133 102 V16.0.0 + * Appendix C.3 + * */ +#define MILENAGE_IND_LEN 5 + +struct milenage_key_data { + u8 k[16]; /* Secret key K */ + u8 opc[16]; /* OPc or OP value */ + int opc_is_op; /* does opc store OPc or OP? */ +}; + +/* As described in TS 133 102 V16.0.0 Appendix C.2 */ +struct milenage_seq_data { + uint64_t seq[(1 << MILENAGE_IND_LEN)]; /* array of SEQ_MS indexed by IND */ + uint64_t delta; /* limit "delta" as per 33.102. Typically configured to 2**28 */ +}; + +struct milenage_result { + u8 ik[16]; + u8 ck[16]; + u8 res[8]; + u8 res_len; + u8 auts[14]; +}; + +int milenage_usim_check(const struct milenage_key_data *kd, + struct milenage_seq_data *sd, + struct milenage_result *mr, + const u8 *_rand, const u8 *autn); diff --git a/src/softsim/storage.c b/src/softsim/storage.c new file mode 100644 index 0000000..d2f23ce --- /dev/null +++ b/src/softsim/storage.c @@ -0,0 +1,471 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* TODO #66: Make configurable (commandline option) */ +static char storage_path[] = "./files"; + +/* Generate a host filesystem path for a given file path. */ +static int gen_abs_host_path(char *def_path, const struct ss_list *path, bool def, const char *division) +{ + char host_fs_path[1024]; + char abs_host_fs_path[PATH_MAX + 1]; + static char *host_fs_path_ptr; + struct ss_file *path_cursor; + struct ss_file *path_last = NULL; + int rc; + + if (ss_list_empty(path)) { + if (def) + SS_LOGP(SSTORAGE, LERROR, + "%s: unable to generate path to load file definition\n", division); + else + SS_LOGP(SSTORAGE, LERROR, + "%s: unable to generate path to load file content\n", division); + return -EINVAL; + } + + memset(host_fs_path, 0, sizeof(host_fs_path)); + host_fs_path_ptr = host_fs_path; + + SS_LIST_FOR_EACH(path, path_cursor, struct ss_file, list) { + rc = snprintf(host_fs_path_ptr, + sizeof(host_fs_path) - (host_fs_path_ptr - + host_fs_path), path_cursor->fid > 0xffff ? "/%08x" : "/%04x", + path_cursor->fid); + host_fs_path_ptr += rc; + path_last = path_cursor; + } + + if (def) { + snprintf(abs_host_fs_path, sizeof(abs_host_fs_path), "%s%s.def", + storage_path, host_fs_path); + SS_LOGP(SSTORAGE, LINFO, + "%s: requested file definition for %04x on host file system : %s\n", + division, path_last->fid, abs_host_fs_path); + } else { + snprintf(abs_host_fs_path, sizeof(abs_host_fs_path), "%s%s", + storage_path, host_fs_path); + SS_LOGP(SSTORAGE, LINFO, + "%s: requested file content for %04x on host file system: %s\n", + division, path_last->fid, abs_host_fs_path); + } + + strncpy(def_path, abs_host_fs_path, PATH_MAX); + return 0; +} + +/* Read file definition from host file system */ +static int read_file_def(char *host_path, struct ss_file *file) +{ + FILE *fd; + char line_buf[1024]; + char *rc; + + fd = fopen(host_path, "r"); + if (!fd) { + SS_LOGP(SSTORAGE, LERROR, + "unable to open definition file: %s\n", host_path); + return -EINVAL; + } + + rc = fgets(line_buf, sizeof(line_buf), fd); + fclose(fd); + if (!rc) { + SS_LOGP(SSTORAGE, LERROR, + "unable to read definition file: %s\n", host_path); + return -EINVAL; + } + + /* Note: Module fs.c must ensure freeing of the fci data */ + file->fci = ss_buf_from_hexstr(line_buf); + + return 0; +} + +/*! Get file definition for file (tip of the path). + * \param[inout] path to the file for which the definition should be read. + * \returns 0 on success, -EINVAL on failure */ +int ss_storage_get_file_def(struct ss_list *path) +{ + /*! Note: This function will allocate memory in file to store the file + * definition. The caller must take care of freeing. */ + + char host_path[PATH_MAX + 1]; + struct ss_file *file; + int rc; + + rc = gen_abs_host_path(host_path, path, true, "get-def"); + if (rc < 0) + return -EINVAL; + + file = ss_get_file_from_path(path); + if (!file) + return -EINVAL; + + return read_file_def(host_path, file); +} + +/*! Get content from file (tip of the path). + * \param[in] path path to the file to be read. + * \param[in] read_offset offset to start reading the file at. + * \param[in] read_len length of data to read. + * \returns buffer with content data on success, NULL on failure. */ +struct ss_buf *ss_storage_read_file(const struct ss_list *path, + size_t read_offset, size_t read_len) +{ + /*! Note: This function will allocate memory in fileto store the file + * contents. The caller must take care of freeing. */ + + /* TODO #65: check if the path really points to a file. If the path + * points to a directory print an error and return a size of 0. + * (Normally this shouldn't happen because the FCP is always checked + * before calling this function. */ + + char host_path[PATH_MAX + 1]; + int rc; + FILE *fd; + char *line_buf; + size_t fgets_rc; + struct ss_buf *result; + + rc = gen_abs_host_path(host_path, path, false, "read"); + if (rc < 0) + return NULL; + + line_buf = SS_ALLOC_N(read_len * 2 + 1); + memset(line_buf, 0, read_len * 2 + 1); + + fd = fopen(host_path, "r"); + if (!fd) { + SS_LOGP(SSTORAGE, LERROR, "unable to open content file: %s\n", + host_path); + SS_FREE(line_buf); + return NULL; + } + + rc = fseek(fd, read_offset * 2, SEEK_SET); + if (rc != 0) { + SS_LOGP(SSTORAGE, LERROR, + "unable to seek (read_offset=%lu) requested data in content file: %s\n", + read_offset, host_path); + SS_FREE(line_buf); + fclose(fd); + return NULL; + } + + fgets_rc = fread(line_buf, 2, read_len, fd); + if (fgets_rc != read_len) { + SS_LOGP(SSTORAGE, LERROR, + "unable to load content (read_offset=%lu, read_len=%lu) from file: %s\n", + read_offset, read_len, host_path); + SS_FREE(line_buf); + fclose(fd); + return NULL; + } + + fclose(fd); + + /* Note: Module fs.c must ensure freeing of the content */ + result = ss_buf_from_hexstr(line_buf); + SS_FREE(line_buf); + return result; +} + +/*! Write data to a file (tip of the path). + * \param[in] path path to the file to be written. + * \param[in] write_offset offset to start writing the file at. + * \param[in] write_len length of data to be written. + * \returns 0 on success, -EINVAL on failure. */ +int ss_storage_write_file(const struct ss_list *path, const uint8_t *data, + size_t write_offset, size_t write_len) +{ + char host_path[PATH_MAX + 1]; + int rc; + FILE *fd; + size_t i; + char hex[3]; + size_t fwrite_rc; + + rc = gen_abs_host_path(host_path, path, false, "write"); + if (rc < 0) + return -EINVAL; + + fd = fopen(host_path, "r+"); + if (!fd) { + SS_LOGP(SSTORAGE, LERROR, "unable to open content file: %s\n", + host_path); + return -EINVAL; + } + + rc = fseek(fd, write_offset * 2, SEEK_SET); + if (rc != 0) { + SS_LOGP(SSTORAGE, LERROR, + "unable to seek (write_offset=%lu) data to content file: %s\n", + write_offset, host_path); + fclose(fd); + return -EINVAL; + } + + for (i = 0; i < write_len; i++) { + snprintf(hex, sizeof(hex), "%02x", data[i]); + fwrite_rc = fwrite(hex, sizeof(hex) - 1, 1, fd); + if (fwrite_rc != 1) { + SS_LOGP(SSTORAGE, LERROR, + "unable to write (write_offset=%lu+%lu) data to content file: %s\n", + write_offset, i, host_path); + fclose(fd); + return -EINVAL; + } + } + fclose(fd); + + return 0; +} + +/*! Get the total size in bytes of a file. + * \param[in] path path to the file that gets selected. + * \returns size in bytes on success, 0 on failure. */ +size_t ss_storage_get_file_len(const struct ss_list *path) +{ + char host_path[PATH_MAX + 1]; + int rc; + FILE *fd; + long file_size; + + /* TODO #65: check if the path really points to a file. If the path + * points to a directory print an error and return a size of 0. + * (Normally this shouldn't happen because the FCP is always checked + * before calling this function. */ + + rc = gen_abs_host_path(host_path, path, false, "file-len"); + if (rc < 0) + return 0; + + fd = fopen(host_path, "r"); + if (!fd) { + SS_LOGP(SSTORAGE, LERROR, "unable to open content file: %s\n", + host_path); + return 0; + } + + rc = fseek(fd, 0, SEEK_END); + if (rc != 0) { + SS_LOGP(SSTORAGE, LERROR, + "unable to seek requested data in content file: %s\n", + host_path); + fclose(fd); + return 0; + } + + file_size = ftell(fd); + if (file_size < 0) { + SS_LOGP(SSTORAGE, LERROR, + "unable to tell the size of the file: %s\n", host_path); + fclose(fd); + return 0; + } + + fclose(fd); + + /* The files contain ASCII hex digits */ + file_size /= 2; + + return file_size; +} + +/*! Delete file or directory in the file system. + * \param[in] path path to the file or directory to delete. + * \returns 0 on success, -EINVAL on failure. */ +int ss_storage_delete(const struct ss_list *path) +{ + char host_path_def[PATH_MAX + 1]; + char host_path_content[PATH_MAX + 1]; + char rm_command[10 + PATH_MAX + 1]; + struct ss_file *file; + int rc; + + file = ss_get_file_from_path(path); + if (!file) + return -EINVAL; + + rc = gen_abs_host_path(host_path_def, path, true, "delete"); + if (rc < 0) + return -EINVAL; + rc = gen_abs_host_path(host_path_content, path, false, "delete"); + if (rc < 0) + return -EINVAL; + + rc = remove(host_path_def); + if (rc < 0) { + SS_LOGP(SSTORAGE, LERROR, + "unable to remove definition file: %s\n", + host_path_def); + return -EINVAL; + } + + snprintf(rm_command, sizeof(rm_command), "rm -rf %s", + host_path_content); + rc = system(rm_command); + if (rc < 0) { + SS_LOGP(SSTORAGE, LERROR, + "unable to remove content file: %s\n", + host_path_content); + return -EINVAL; + } + return 0; +} + +/*! Update definition file in the file system. + * \param[in] path path to the file to update. + * \returns 0 on success, -EINVAL on failure */ +int ss_storage_update_def(const struct ss_list *path) +{ + char host_path[PATH_MAX + 1]; + int rc; + FILE *fd; + struct ss_file *file; + size_t i; + char hex[3]; + size_t fwrite_rc; + + file = ss_get_file_from_path(path); + if (!file) + return -EINVAL; + + if (!file->fci) { + SS_LOGP(SSTORAGE, LERROR, + "file (%04x) has no definition (FCP) set -- abort\n", + file->fid); + return -EINVAL; + } + + /* Generate definition file */ + rc = gen_abs_host_path(host_path, path, true, "update-def"); + if (rc < 0) + return -EINVAL; + + fd = fopen(host_path, "w"); + if (!fd) { + SS_LOGP(SSTORAGE, LERROR, + "unable to create definition file: %s\n", host_path); + return -EINVAL; + } + for (i = 0; i < file->fci->len; i++) { + snprintf(hex, sizeof(hex), "%02x", file->fci->data[i]); + fwrite_rc = fwrite(hex, sizeof(hex) - 1, 1, fd); + if (fwrite_rc != 1) { + SS_LOGP(SSTORAGE, LERROR, + "unable to write file definition: %s\n", + host_path); + ss_storage_delete(path); + fclose(fd); + return -EINVAL; + } + } + fclose(fd); + return 0; +} + +/*! Create a file in the file system. + * \param[in] path path to the file that gets selected. + * \param[in] file_len length of the file to create (filled with 0xff). + * \returns 0 success, -EINVAL on failure */ +int ss_storage_create_file(const struct ss_list *path, size_t file_len) +{ + /*! Note: This function must not be called with pathes that point to + * a directory! */ + + char host_path[PATH_MAX + 1]; + int rc; + FILE *fd; + size_t i; + + /* Create definition file */ + rc = ss_storage_update_def(path); + if (rc < 0) + return -EINVAL; + + /* Generate (empty) content file */ + rc = gen_abs_host_path(host_path, path, false, "create-file"); + if (rc < 0) { + ss_storage_delete(path); + return -EINVAL; + } + fd = fopen(host_path, "w"); + if (!fd) { + SS_LOGP(SSTORAGE, LERROR, "unable to create content file: %s\n", + host_path); + ss_storage_delete(path); + return -EINVAL; + } + for (i = 0; i < file_len * 2; i++) { + if (fputc('f', fd) != 'f') { + SS_LOGP(SSTORAGE, LERROR, + "unable to prefill content file: %s\n", + host_path); + ss_storage_delete(path); + fclose(fd); + return -EINVAL; + } + } + fclose(fd); + + return 0; +} + +/*! Create a directory in the file system. + * \param[in] path path to the directory to create. + * \returns 0 on success, -EINVAL on failure */ +int ss_storage_create_dir(const struct ss_list *path) +{ + /*! Note: This function must not be called with pathes that point to + * a directory! */ + + char host_path[PATH_MAX + 1]; + int rc; + + /* Create definition file */ + rc = ss_storage_update_def(path); + if (rc < 0) + return -EINVAL; + + /* Create directory */ + rc = gen_abs_host_path(host_path, path, false, "create-dir"); + if (rc < 0) { + ss_storage_delete(path); + return -EINVAL; + } + + if (access(host_path, W_OK) == 0) + return 0; + rc = mkdir(host_path, 0700); + if (rc < 0) { + SS_LOGP(SSTORAGE, LERROR, "unable to create directory: %s\n", + host_path); + ss_storage_delete(path); + return -EINVAL; + } + + return 0; +} diff --git a/src/softsim/uicc/CMakeLists.txt b/src/softsim/uicc/CMakeLists.txt new file mode 100644 index 0000000..00cc8e4 --- /dev/null +++ b/src/softsim/uicc/CMakeLists.txt @@ -0,0 +1,54 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +set(uicc_collection + access.c + apdu.c + btlv_enc.c + btlv_dec.c + btlv_utils.c + ctlv.c + command.c + df_name.c + fcp.c + file.c + fs.c + fs_chg.c + fs_utils.c + uicc_lchan.c + sfi.c + sms.c + sw.c + softsim.c + tlv8.c + uicc_admin.c + uicc_auth.c + uicc_cat.c + uicc_sms_rx.c + uicc_sms_tx.c + uicc_remote_cmd.c + uicc_file_ops.c + uicc_pin.c + uicc_refresh.c + utils.c + utils_ota.c + utils_3des.c + utils_aes.c + proactive.c +) + +# Add log.c to get fcp_test working with CTest. It has otherwise been removed from the +# uicc_collection to allow users to implement log.c themselves by default. +if(BUILD_TESTING) + set(uicc_collection ${uicc_collection} log.c) +endif() + +# Combine UICC with or without testing required files +add_library(uicc STATIC ${uicc_collection}) + +target_include_directories(uicc + PUBLIC + ${CMAKE_SOURCE_DIR}/include + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../ +) diff --git a/src/softsim/uicc/Makefile.am b/src/softsim/uicc/Makefile.am new file mode 100644 index 0000000..18ffe1a --- /dev/null +++ b/src/softsim/uicc/Makefile.am @@ -0,0 +1,87 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir) \ + -I$(top_srcdir)/src/softsim \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(NULL) + +AM_LDFLAGS = \ + $(NULL) + +noinst_LIBRARIES = libuicc.a + +libuicc_a_SOURCES = \ + access.c \ + apdu.c \ + btlv_enc.c \ + btlv_dec.c \ + btlv_utils.c \ + ctlv.c \ + command.c \ + df_name.c \ + fcp.c \ + file.c \ + fs.c \ + fs_chg.c \ + fs_utils.c \ + uicc_lchan.c \ + log.c \ + sfi.c \ + sms.c \ + sw.c \ + softsim.c \ + tlv8.c \ + uicc_admin.c \ + uicc_auth.c \ + uicc_cat.c \ + uicc_sms_rx.c \ + uicc_sms_tx.c \ + uicc_remote_cmd.c \ + uicc_file_ops.c \ + uicc_pin.c \ + uicc_refresh.c \ + utils.c \ + utils_3des.c \ + utils_aes.c \ + utils_ota.c \ + proactive.c \ + $(NULL) + +noinst_HEADERS = \ + access.h \ + apdu.h \ + btlv.h \ + ctlv.h \ + command.h \ + df_name.h \ + context.h \ + fcp.h \ + fs_chg.h \ + fs_utils.h \ + uicc_lchan.h \ + sfi.h \ + sms.h \ + sw.h \ + tlv8.h \ + uicc_admin.h \ + uicc_auth.h \ + uicc_cat.h \ + uicc_sms_rx.h \ + uicc_sms_tx.h \ + uicc_remote_cmd.h \ + uicc_file_ops.h \ + uicc_ins.h \ + uicc_pin.h \ + uicc_refresh.h \ + utils_3des.h \ + utils_aes.h \ + utils_ota.h \ + proactive.h \ + $(NULL) diff --git a/src/softsim/uicc/access.c b/src/softsim/uicc/access.c new file mode 100644 index 0000000..6c7a26b --- /dev/null +++ b/src/softsim/uicc/access.c @@ -0,0 +1,448 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Christian Amsüss + */ + +#include +#include +#include + +#include "access.h" +#include "btlv.h" +#include "fs.h" +#include "fs_utils.h" +#include "fcp.h" +#include "uicc_pin.h" +#include "apdu.h" +#include +#include + +#define ACCESSBYTE_FLAG_PROPRIETARY_HIGHBITS 0x80 +#define ACCESSBYTE_MASK_HIGHBITS 0x78 + +enum arr_ref_type { + ARR_REF_NONE, /**< The FCP contained no access rule reference */ + ARR_REF_IDENTIFIED, /**< A file ID and record number have been identified */ + ARR_REF_UNMATCHING, /**< The file ID was identified, but no data was */ + /* available for the given SE ID. (Currently, + * this is the case for all SE ID references, as + * the SE ID is not passed into \ref + * arr_from_fcp). */ +}; + +struct arr_ref { + enum arr_ref_type type; + uint16_t file_id; + uint8_t record_number; /**< Record number inside the file; only valid */ + /* for type ARR_REF_IDENTIFIED */ +}; + +const uint8_t FCP_TAG_REFERENCED_FORMAT = 0x8b; +const uint8_t FCP_TAG_SECURITY_ATTRIB_COMPACT = 0x8c; +const uint8_t FCP_TAG_SECURITY_ATTRIB_EXTENDED = 0xab; + +/** Extract the access rule reference from the FCP as described in TS 102 221 v15.0.0 Section 9.2.7 */ +struct arr_ref arr_from_fcp(struct ss_list *fcp_decoded_envelope) +{ + struct ber_tlv_ie *fcp_decoded_arr; + + struct arr_ref result = { .type = ARR_REF_NONE }; + + fcp_decoded_arr = ss_btlv_get_ie(fcp_decoded_envelope, FCP_TAG_REFERENCED_FORMAT); + if (!fcp_decoded_arr) + return result; + + if (fcp_decoded_arr->value->len == 3) { + /* File ID, record number */ + result.type = ARR_REF_IDENTIFIED; + result.file_id = (fcp_decoded_arr->value->data[0] << 8) | + fcp_decoded_arr->value->data[1]; + result.record_number = fcp_decoded_arr->value->data[2]; + } else { + /* File ID, pairs of SE ID and record numbers */ + + /* When support for SE IDs is needed, extend API to pass in an + * SE ID from the caller, and set it as IDENTIFIED after + * iterating through the SE ID / record number pairs. */ + result.type = ARR_REF_UNMATCHING; + } + + return result; +} + +/** Populate an lchan's selected file's access list + * + * \pre The lchan has a selected file. + * \pre The lchan's current file has its access member unset. + * + * \post The lchan's current file has its access member set to the rules that + * apply to the lchan (through its SE, but that is currently unimplemented). + * These are NULL on a load error, which is treated as 'never allow'. + * + * This currently only evaluates access rules through + * refernces through TS 102 221 V15 section 9.2.7 access rule + * referencing (EF.ARR). The section 9.2.6 expanded format should be + * straightforward to add (by leaving out the indirection), and it might be + * possible to also implement the section 9.2.5 format by expanding it. + */ +void ss_access_populate(struct ss_lchan *lchan) +{ + struct ss_file *selected_file = ss_get_file_from_path(&lchan->fs_path); + + assert(selected_file != NULL); + + /* Access list already populated, so we may return early. */ + if (selected_file->access) + return; + + struct ss_buf *record; + + /* Check for other security attributes than the supported EF.ARR based ones + * -- as we can't evaluate them, they'll lead to rejection. + * + * These are purely for debugging purposes (since without an EF.ARR + * reference, access is denied unconditionally anyway). */ + struct ber_tlv_ie *undecoded_element; + undecoded_element = ss_btlv_get_ie(selected_file->fcp_decoded, FCP_TAG_SECURITY_ATTRIB_COMPACT); + if (undecoded_element) { + SS_LOGP(SACCESS, LERROR, "Compact security attribute present but not implemented.\n"); + } + + undecoded_element = ss_btlv_get_ie(selected_file->fcp_decoded, FCP_TAG_SECURITY_ATTRIB_EXTENDED); + if (undecoded_element) { + SS_LOGP(SACCESS, LERROR, "Extended security attribute present but not implemented.\n"); + } + + struct arr_ref arr = arr_from_fcp(selected_file->fcp_decoded); + switch (arr.type) { + case ARR_REF_NONE: + SS_LOGP(SACCESS, LDEBUG, "No access list referenced, denying all access.\n"); + selected_file->access = NULL; + break; + case ARR_REF_UNMATCHING: + SS_LOGP(SACCESS, LERROR, "SE based access list not decoded.\n"); + selected_file->access = NULL; + break; + case ARR_REF_IDENTIFIED: + record = ss_fs_read_relative_file_record(&lchan->fs_path, arr.file_id, arr.record_number); + if (record != NULL) { + selected_file->access = ss_btlv_decode(record->data, record->len, NULL); + ss_buf_free(record); + } + SS_LOGP(SACCESS, LDEBUG, "Access referenced into file %04x record %02x, loaded:\n", arr.file_id, arr.record_number); + if (selected_file->access == NULL) { + SS_LOGP(SACCESS, LDEBUG, "(No valid access condition loaded)\n"); + } else { + ss_btlv_dump(selected_file->access, 0, SACCESS, LDEBUG); + } + break; + } +} + +static bool apdu_matches_am_byte(enum ss_access_intention intention, uint8_t am_byte) +{ + if (intention == SS_ACCESS_INTENTION_OTHER) { + /* Not covered by this type of rule */ + return false; + } + + if ((intention & ACCESSBYTE_MASK_HIGHBITS) && + (am_byte & ACCESSBYTE_FLAG_PROPRIETARY_HIGHBITS)) { + /* Intention has standardized high bits set, but the required am_byte does + * not even express them */ + return false; + } + + return intention & am_byte; +} + +static bool apdu_matches_sc_byte(struct ss_apdu *apdu, uint8_t sc_byte) +{ + /* FIXME #55: Evaluate precise condition */ + return false; +} + +/** Extract the access condition according to TS 102 221 V15.0.0 table 9.3. + * + * For example, ADM1 is 0x0A. + * + * This extracts only the key ref value, and checks that the access condition + * (which is a constructed value) is otherwise valid, ie. has both a 1 long key + * reference (83) IE and a usager qualifer (95) IE with value 01. + * */ +static uint8_t access_condition_extract(struct ss_list *access_condition) +{ + uint8_t result = 0; + bool usage_qualifier_ok = false; + bool unexpected_ie = false; + + struct ber_tlv_ie *item; + SS_LIST_FOR_EACH(access_condition, item, struct ber_tlv_ie, list) { + if (item->tag_encoded == 0x83 && item->value->len == 1) { + result = item->value->data[0]; + } else if (item->tag_encoded == 0x95 && item->value->len == 1) { + usage_qualifier_ok = true; + } else { + unexpected_ie = true; + } + } + + if (!usage_qualifier_ok || unexpected_ie) { + result = 0; + } + return result; +} + +/** Decide whether a command is allowed based on the selected file's access rules. + * + * \param[in] apdu The command being executed. + * The file accessed through it must be selected in its `lchan`, + * which especially means that its `.access` needs to be populated. + * \param[in] intention The operation the caller is about to perform. While + * this is encoded in the APDU already, this reduces duplication across the + * code (as the caller alreay knows that it is about to perform a read). This + * parameter must be aligned with the \p apdu; depending on how the access + * rules are phrased, this function will either use the intention (for + * evaluating access mode bytes), or it will use the APDU's CLA, INS and Pn + * (for evaluating access mode data objects; currently not implemented). + * The intention must also match the \p file's type. + * + * If the card is in creation / initialization state (judged by the MF's life + * cycle, and assumed in absence of an MF), access controls are dispensed, and + * all access is allowed. + * + * \return true if select access is allowed according to the file's access rules. + * + * eg. per TS 102 221 V15 9.2.0, SS_SW_ERR_CMD_NOT_ALLOWED_SECURITY_STATUS + * should indicate that the condition could not be determined, equivalent + * to ->access=NULL + */ +bool ss_access_check_command(struct ss_apdu *apdu, enum ss_access_intention intention) +{ + struct ss_file *selected_file = ss_get_file_from_path(&apdu->lchan->fs_path); + + /* Before we go to regualr access control, maybe we're in creation / + * initialization state -- which is indicated in the master file. */ + + if (selected_file == NULL) { + /* The only currently known situation in which an empty path can ever be + * around is when there is no MF yet, and thus the card is in creation / + * initialization (personalization) state. + * + * As an empty path also might result from programming errors, whose + * consequences here would be severe (lifting all access restrictions), + * this double check is performed to verify that there is indeed no MF + * loadable. + * */ + struct ss_list check_mf_path = {NULL, NULL}; + ss_fs_init(&check_mf_path); + assert(ss_get_file_from_path(&apdu->lchan->fs_path) == NULL); + + SS_LOGP(SACCESS, LINFO, "MF in creation / initialization state (MF not even present), bypassing authorizations.\n"); + return true; + } + + /* Go over the end twice to find the DLL's first element */ + struct ss_file *mf = ss_get_file_from_path(apdu->lchan->fs_path.next->next); + /* If a situation ever comes up where using a path that does not start at the + * MF is valid, we can still consider loading the MF as it is done above -- + * but until there is a legitimate use case, it is most likely a bug. */ + assert(mf->fid == 0x3f00); + + struct ber_tlv_ie *lcsi_do = ss_btlv_get_ie_minlen(mf->fcp_decoded, TS_102_221_IEI_FCP_LIFE_CYCLE_ST, 1); + if (lcsi_do == NULL || lcsi_do->value->data[0] == 0) { + /* As above. */ + SS_LOGP(SACCESS, LERROR, "MF's envelope contained no lifecycle information, rejecting all access.\n"); + return false; + } + SS_LOGP(SACCESS, LDEBUG, "MF lifecycle is %02x\n", lcsi_do->value->data[0]); + if (lcsi_do->value->data[0] < 4) { + SS_LOGP(SACCESS, LINFO, "MF in creation / initialization state (as indicated in the MF), bypassing authorizations.\n"); + return true; + } + /* FIXME #56: Do we want to check for MF deactivation / termination as well, while we're at it? */ + + /* Card is in operational state or later -- performing regular access control. */ + + SS_LOGP(SACCESS, LDEBUG, "Checking access to path=%s with intention=%02x\n", + ss_fs_utils_dump_path(&apdu->lchan->fs_path), + intention); + + if (selected_file->access == NULL) { + SS_LOGP(SACCESS, LERROR, "No valid access rules available, rejecting.\n"); + return false; + } + + /* Next steps: + * + * * check at file creation whether referenced file is present. + */ + + /* Iterate over the rules until a matching AM (Access Mode) is found, then go + * through the following SCs to reject if any doesn't match. There is no need + * to go back to looking into AMs once at the SC stage: their content "shall + * be unique" within the rulle according to TS 102 221 V15.0.0 section 9.2.4. + * */ + + bool sc_phase = false; + bool sc_leastone = false; /* Set to verify that at least one SC was + * evaluated. Having one in there is a requirement + * that a rule might violate, and invalid rules + * should lead to rejection (while without this + * check they'd lead to acceptance based on ALL({}) + * being true for every question) */ + struct ber_tlv_ie *item; + SS_LIST_FOR_EACH(selected_file->access, item, struct ber_tlv_ie, list) { + /* This could also be two loops, one looking for the AM (sc_phase == 0) and + * one looking for a matching SC (sc_phase == 1), but the way SS_LIST + * iteration works would not have better readability */ + + if (!sc_phase) { + switch (item->tag_encoded) { + /* The SCs according to ISO/IEC 7816-4:2005(e) table 23 */ + case 0x90: + case 0x97: + case 0x9E: + case 0xA4: + case 0xB4: + case 0xB6: + case 0xB8: + case 0xA0: + case 0xA7: + case 0xAF: + if (&item->list == selected_file->access) { + SS_LOGP(SACCESS, LERROR, "Invalid access rule: Starts with an SC\n"); + return false; + } + /* Ignore: We didn't match on the preceding AM */ + continue; + case 0x80: + if (item->value->len != 1) { + SS_LOGP(SACCESS, LERROR, "Invalid access rule: AM byte with len != 1\n"); + return false; + } + if (apdu_matches_am_byte(intention, item->value->data[0])) { + SS_LOGP(SACCESS, LDEBUG, "Matched on AM %02x, evaluating SCs.\n", + item->value->data[0]); + sc_phase = true; + } + continue; + /* Command headers currently not supported. */ + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + /* We don't have a proprietary state machine */ + case 0x9C: + SS_LOGP(SACCESS, LERROR, "Access rule with unknown AM %02x. Continuing to look for satisfiable rules.\n", + item->tag_encoded); + /* There is no harm in doing this: the rules do not overlap, so if we + * find something later, nothing in here could have overlapped with it, + * and if we don't find anything, we reject by default anyway. */ + continue; + default: + SS_LOGP(SACCESS, LERROR, "Access rule with entry neither AM nor SC tag %02x, aborting.\n", + item->tag_encoded); + return false; + } + } else { + switch (item->tag_encoded) { + /* The SCs according to ISO/IEC 7816-4:2005(e) table 23 */ + case 0x90: + SS_LOGP(SACCESS, LDEBUG, "Continuing on 'always' SC.\n"); + sc_leastone = true; + continue; + case 0x97: + SS_LOGP(SACCESS, LDEBUG, "Encountered on 'never' SC, rejecting.\n"); + return false; + case 0x9E: + if (item->value->len != 1) { + SS_LOGP(SACCESS, LERROR, "Invalid access rule: SC byte with len != 1\n"); + return false; + } + if (apdu_matches_sc_byte(apdu, item->value->data[0])) { + sc_leastone = true; + continue; + } else { + SS_LOGP(SACCESS, LDEBUG, "SC %02x not matched, rejecting.\n", item->value->data[0]); + return false; + } + case 0xA4: + { + uint8_t access_condition = access_condition_extract(item->nested); + if (access_condition == 0) { + SS_LOGP(SACCESS, LERROR, "Acccess condition not understood, rejecting.\n"); + return false; + } + if (ss_uicc_pin_verified(access_condition, apdu->lchan)) { + sc_leastone = true; + continue; + } + SS_LOGP(SACCESS, LDEBUG, "Access condition %02x required but pin not verified.\n", + access_condition); + return false; + } + case 0xB4: + case 0xB6: + case 0xB8: + case 0xA0: /* OR template, not supported */ + case 0xA7: /* NOT template, not supported */ + case 0xAF: /* AND template, not supported, and useless without OR and NOT */ + SS_LOGP(SACCESS, LDEBUG, "Encountered unknown SC %02x, rejecting.\n", + item->tag_encoded); + return false; + /* All known AMs */ + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + case 0x9C: + /* break from switch and from loop */ + goto end_sc; + default: + SS_LOGP(SACCESS, LERROR, "Access rule with entry neither AM nor SC tag %02x, aborting.\n", + item->tag_encoded); + return false; + } + } + } + if (!sc_phase) { + SS_LOGP(SACCESS, LDEBUG, "No AM matched\n"); + return false; + } + +end_sc: + if (!sc_leastone) { + SS_LOGP(SACCESS, LERROR, "Not even one SC present after AM, rejecting\n"); + return false; + } + + SS_LOGP(SACCESS, LDEBUG, "No SC after the AM caused early rejection, accepting\n"); + return true; +} diff --git a/src/softsim/uicc/access.h b/src/softsim/uicc/access.h new file mode 100644 index 0000000..cc6d4cd --- /dev/null +++ b/src/softsim/uicc/access.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +/** Access rule evaluation */ + +#include +#include "uicc_lchan.h" + +/** Classification of an APDU for access control purposes + * + * Organization and values match ISO/IEC 7816-4:2005(E) tables 16-19; callers + * of \ref ss_access_check_command need to find their access in the relevant tables. + * + * The value SS_ACCESS_INTENTION_OTHER must be selected if the access does not + * fall in any of these categories. + * + * Regular intentions are always 1-bit values; thus, they can be easily + * compared to the OR expressed in the AM byte by &-ing them. + * + * The current values allow for 1:1 comparison with the AM bytes; if this + * implementation starts giving meaning to the proprietary bits, they could be + * placed in the enum value's higher byte. + */ +enum ss_access_intention { + SS_ACCESS_INTENTION_EF_READ = 0x01, /*< EF: READ BINARY, READ RECORD(s), SEARCH BINARY, SEARCH RECORDS */ + SS_ACCESS_INTENTION_EF_UPDATE_ERASE = 0x02, /*< EF: UPDATE BINARY, UPDATE RECORD, ERASE BINARY, ERASE RECORD(S) */ + SS_ACCESS_INTENTION_EF_WRITE = 0x04, /*< EF: WRITE BINARY, WRITE RECORD(S), APPEND RECORD */ + + SS_ACCESS_INTENTION_DF_DELETE_FILE = 0x01, /*< DF: DELETE FILE (child) */ + SS_ACCESS_INTENTION_DF_CREATE_EF = 0x02, /*< DF: CREATE FILE (EF creation) */ + SS_ACCESS_INTENTION_DF_CREATE_DF = 0x04, /*< DF: CREATE FILE (DF creation) */ + + SS_ACCESS_INTENTION_EFDF_DEACTIVATE_FILE = 0x08, /*< EF or DF: DEACTIVATE FILE */ + SS_ACCESS_INTENTION_EFDF_ACTIVATE_FILE = 0x10, /*< EF or DF: ACTIVATE FILE */ + SS_ACCESS_INTENTION_EFDF_TERMINATE_CARD_OR_DF = 0x20, /*< EF or DF: TERMINATE EF / TERMINATE CARD USAGE (MF), TERMINATE DF */ + SS_ACCESS_INTENTION_EFDF_DELETE_FILE_SELF = 0x40, /*< EF or DF: DELETE FILE / DELTE FILE (self) */ + + SS_ACCESS_INTENTION_OTHER = 0, +}; + +void ss_access_populate(struct ss_lchan *lchan); + +bool ss_access_check_command(struct ss_apdu *apdu, enum ss_access_intention intention); diff --git a/src/softsim/uicc/apdu.c b/src/softsim/uicc/apdu.c new file mode 100644 index 0000000..3000363 --- /dev/null +++ b/src/softsim/uicc/apdu.c @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include "uicc_lchan.h" +#include "apdu.h" +#include "context.h" + +/*! Create a new APDU struct for use with the softsim API. + * \returns pointer to allocated APDU struct, NULL on allocation failure. */ +struct ss_apdu *ss_apdu_new(struct ss_context *ctx) +{ + struct ss_apdu *apdu; + apdu = SS_ALLOC(struct ss_apdu); + memset(apdu, 0, sizeof(*apdu)); + apdu->ctx = ctx; + SS_LOGP(SLCHAN, LDEBUG, "allocating APDU %p\n", apdu); + return apdu; +} + +/*! Toss APDU that is no longer needed by the API user. + * \param[in] apdu to toss. */ +void ss_apdu_toss(struct ss_apdu *apdu) +{ + /*! NOTE: By calling this function the APDU struct is not freed + * immediately. It is kept for another cycle for internal + * references. */ + + /* NOTE: An APDU without lchan may occur when it was impossible to + * resolve a valid lchan. This may happen when a non existing lchan + * is addressed. */ + if (!apdu->lchan) { + SS_LOGP(SLCHAN, LERROR, "tossing APDU without lchan\n"); + return; + } + + /* free any previous last_apdu we might still be keeping. If we decide + * to keep the last apdu, then we free the current APDU instead. + * + * Note that if there is ever anything more to freeing the old APDU, this + * also needs to be extended in ss_lchan_reset, which is the other place + * where an APDU is freed. */ + + if (apdu->lchan->last_apdu_keep) { + SS_LOGP(SLCHAN, LDEBUG, "freeing APDU %p (current), keeping APDU %p (last)\n", apdu, apdu->lchan->last_apdu); + SS_FREE(apdu); + } else { + if (apdu->lchan->last_apdu) + SS_FREE(apdu->lchan->last_apdu); + SS_LOGP(SLCHAN, LDEBUG, "freeing APDU %p (last), keeping APDU %p (current)\n", apdu->lchan->last_apdu, apdu); + apdu->lchan->last_apdu = apdu; + } +} diff --git a/src/softsim/uicc/apdu.h b/src/softsim/uicc/apdu.h new file mode 100644 index 0000000..8b0159f --- /dev/null +++ b/src/softsim/uicc/apdu.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include +struct ss_context; + +/*! APDU header */ +struct ss_apdu_hdr { + uint8_t cla; + uint8_t ins; + uint8_t p1; + uint8_t p2; + uint8_t p3; +} __attribute__((packed)); + +/*! Internal representation of APDU */ +struct ss_apdu { + /*! logical channel through which APDU is transceived */ + struct ss_lchan *lchan; + + /*! backpointer to softsim context */ + struct ss_context *ctx; + + /* header + command and response payload */ + struct ss_apdu_hdr hdr; + uint8_t lc; /*< length (command) */ + uint8_t le; /*< length (expected response) */ + uint8_t cmd[256]; /*< command body */ + uint8_t rsp[256]; /*< response body */ + size_t rsp_len; /*< actual length of of rsp */ + uint16_t sw; /*< status word */ +}; + +struct ss_apdu *ss_apdu_new(struct ss_context *ctx); +void ss_apdu_toss(struct ss_apdu *apdu); diff --git a/src/softsim/uicc/btlv.h b/src/softsim/uicc/btlv.h new file mode 100644 index 0000000..9fdb0a3 --- /dev/null +++ b/src/softsim/uicc/btlv.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include +#include +#include + +#define SS_BERTLV_MAX_LEN_BYTES 4 + +enum ber_tlv_cls { + BER_TLV_UNIV_CLS, + BER_TLV_APPL_CLS, + BER_TLV_CONT_CLS, + BER_TLV_PRIV_CLS, +}; + +struct ber_tlv_ie { + struct ss_list list; + + /* Meta information */ + char *title; + + /* Tag information */ + enum ber_tlv_cls cls; + bool constr; + uint16_t tag; + uint32_t tag_encoded; + + /* Value */ + struct ss_buf *value; + struct ss_list *nested; +}; + +struct ber_tlv_desc { + /* ID number of this IE description item + * (id = 0 marks table ending and must not be used.) */ + uint32_t id; + + /* ID number of the parent IE description item */ + uint32_t id_parent; + + /* Meta information */ + char *title; + uint32_t tag_encoded; +}; + +struct ss_list *ss_btlv_decode(const uint8_t *enc, size_t len, + const struct ber_tlv_desc *descr); +void ss_btlv_dump(const struct ss_list *list, uint8_t indent, + enum log_subsys log_subsys, enum log_level log_level); +void ss_btlv_free(struct ss_list *list); + +struct ber_tlv_ie *ss_btlv_new_ie(struct ss_list *parent, const char *title, + uint32_t tag, size_t len, + const uint8_t *value); +struct ber_tlv_ie *ss_btlv_new_ie_constr(struct ss_list *parent, + const char *title, uint32_t tag); +struct ber_tlv_ie *ss_btlv_get_ie(const struct ss_list *list, uint32_t tag); +struct ber_tlv_ie *ss_btlv_get_ie_minlen(const struct ss_list *list, + uint16_t tag, size_t min_len); +size_t ss_btlv_encode(uint8_t *enc, size_t len, struct ss_list *list); +struct ss_buf *ss_btlv_encode_to_ss_buf(struct ss_list *list); +void ss_btlv_attach_to_constr(struct ber_tlv_ie *ie, struct ss_list *list); +struct ss_list *ss_btlv_split_off_from_constr(struct ber_tlv_ie *ie); diff --git a/src/softsim/uicc/btlv_dec.c b/src/softsim/uicc/btlv_dec.c new file mode 100644 index 0000000..ad20858 --- /dev/null +++ b/src/softsim/uicc/btlv_dec.c @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "btlv.h" + +/* Advance the bufer and check if we are still in bounds. The parameter "inc" + * sets how many bytes the buffer pointer (enc) should be be advanced. The + * parameter "bytes_ahead" sets the minimum valid bytes that the caller expects + * to be available after the buffer pointer (enc) has been advanced. */ +#define CHECK_AND_ADVANCE(inc, bytes_ahead) \ + if (len < bytes_used + inc + bytes_ahead) { \ + SS_LOGP(SBTLV, LDEBUG, "exceeding buffer bounds: len=%zu, inc=%zu, bytes_ahead=%zu, cannot decode IE\n", \ + len, (size_t) inc, (size_t) bytes_ahead); \ + return NULL; \ + } \ + bytes_used+=inc; \ + enc+=inc + +/* Return one decoded information element */ +static struct ber_tlv_ie *decode_ie(size_t *used_len, const uint8_t *enc, size_t len) +{ + struct ber_tlv_ie ie; + struct ber_tlv_ie *ie_ret; + uint8_t len_bytes; + uint8_t i; + size_t bytes_used = 0; + const uint8_t *value; + size_t ie_len; + + *used_len = 0; + + if (len == 0) + return NULL; + + memset(&ie, 0, sizeof(ie)); + + /* Header bits */ + ie.cls = (*enc >> 6) & 3; + ie.constr = (*enc >> 5) & 1; + + /* Decode tag field */ + ie.tag_encoded = *enc; + if ((*enc & 0x1f) == 0x1f) { + CHECK_AND_ADVANCE(1, 1); + ie.tag = *enc & 0x7f; + ie.tag_encoded <<= 8; + ie.tag_encoded |= *enc; + if (*enc & 0x80) { + ie.tag = ie.tag << 8; + CHECK_AND_ADVANCE(1, 1); + ie.tag |= *enc; + ie.tag_encoded <<= 8; + ie.tag_encoded |= *enc; + } + } else + ie.tag = *enc & 0x1f; + CHECK_AND_ADVANCE(1, 1); + + /* Decode length field */ + if (*enc < 0x7f) { + ie_len = *enc; + } else { + len_bytes = *enc & 0x7f; + + /* Make sure the decoded length field length makes sense */ + if (len_bytes == 0 || len_bytes > SS_BERTLV_MAX_LEN_BYTES) { + SS_LOGP(SBTLV, LDEBUG, + "invalid ber-tlv length field length (tag = 0x%02x)\n", + ie.tag); + return NULL; + } + + ie_len = 0; + for (i = 0; i < len_bytes; i++) { + ie_len <<= 8; + CHECK_AND_ADVANCE(1, 1); + ie_len |= *enc; + } + } + CHECK_AND_ADVANCE(1, ie_len); + value = enc; + CHECK_AND_ADVANCE(ie_len, 0); + *used_len = bytes_used; + + /* Create output struct */ + ie_ret = SS_ALLOC(struct ber_tlv_ie); + if (!ie_ret) + return NULL; + memcpy(ie_ret, &ie, sizeof(ie)); + + /* Copy data part to the newly allocated item */ + ie_ret->value = ss_buf_alloc(ie_len); + memcpy(ie_ret->value->data, value, ie_len); + + return ie_ret; +} + +/* Get a description for an IE with a specified tag that is assigned to a specified parent (id) */ +static const struct ber_tlv_desc *get_ie_descr(const struct ber_tlv_desc *descr, + uint32_t tag_encoded, + uint32_t id_parent) +{ + uint32_t i = 0; + + if (descr == NULL) + return NULL; + + do { + if (descr[i].id_parent == id_parent + && descr[i].tag_encoded == tag_encoded) { + return &descr[i]; + } + i++; + } while (descr[i].id != 0); + + return NULL; +} + +/* Decode BER TLV encoded string recursively + * + * On decoding errors, this returns NULL. If any constructed elements could not + * be decoded, that is tolerated; in that case, that element's .nested element + * is NULL. + * + * Consecutive 0xFF bytes at the trailing end are tolerated. + * */ +static struct ss_list *btlv_decode(const uint8_t *enc, size_t len, + const struct ber_tlv_desc *descr, + uint32_t id_parent) +{ + struct ber_tlv_ie *ie; + const struct ber_tlv_desc *ie_descr; + size_t used_len; + size_t remaining_len = len; + + struct ss_list *list; + + list = SS_ALLOC(struct ss_list); + ss_list_init(list); + + do { + /* Decode IE and store it in the given list */ + ie = decode_ie(&used_len, enc, remaining_len); + if (ie) { + ss_list_put(list, &ie->list); + ie_descr = get_ie_descr(descr, ie->tag_encoded, + id_parent); + if (ie_descr && ie_descr->title) { + ie->title = + SS_ALLOC_N(strlen(ie_descr->title) + 1); + strcpy(ie->title, ie_descr->title); + } + } else if (remaining_len > 0) { + for (int i = used_len; i < used_len + remaining_len; ++i) { + if (enc[i] != 0xff) { + SS_LOGP(SBTLV, LERROR, "Error decoding BTLV (%s).\n", + ss_hexdump(&enc[used_len], remaining_len)); + ss_btlv_free(list); + return NULL; + } + } + } + + /* If we detect that we deal with a constructed IE, we call + * this function again recursively, but we use the nested + * list of the IE. */ + if (ie && ie->constr) { + if (ie_descr) { + ie->nested = + btlv_decode(ie->value->data, ie->value->len, + descr, ie_descr->id); + } else { + ie->nested = + btlv_decode(ie->value->data, ie->value->len, + NULL, 0); + } + } + + /* Go to the next IE */ + enc += used_len; + remaining_len -= used_len; + } while (ie != NULL); + + return list; +} + +/*! Decode binary BER-TLV encoded data. + * \param[in] enc pointer to buffer with encoded BER-TLV data. + * \param[in] len length of the buffer that contains the BER-TLV encoded data. + * \param[in] descr decoded BER-TLV data that serves a description (titles). + * \returns pointer to allocated linked list with BER-TLV data (can be empty). */ +struct ss_list *ss_btlv_decode(const uint8_t *enc, size_t len, + const struct ber_tlv_desc *descr) +{ + return btlv_decode(enc, len, descr, 0); +} diff --git a/src/softsim/uicc/btlv_enc.c b/src/softsim/uicc/btlv_enc.c new file mode 100644 index 0000000..90710ab --- /dev/null +++ b/src/softsim/uicc/btlv_enc.c @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "btlv.h" + +/* Compute how much length in bytes a specified length value will need when + * it is encoded according BER-TLV rules */ +static size_t len_len(size_t len) +{ + size_t len_sum = 1; + size_t compare_val = 0; + unsigned int i; + + /* A single byte is sufficient */ + if (len <= 127) + return 1; + + /* Multiple bytes are needed */ + for (i = 0; i < SS_BERTLV_MAX_LEN_BYTES; i++) { + compare_val = compare_val << 8; + compare_val |= 0xff; + len_sum++; + + if (compare_val >= len) + break; + } + + return len_sum; +} + +/* Compute the length of the encoded header for a given IE */ +static size_t ie_hdr_len(struct ber_tlv_ie *ie) +{ + size_t len_sum = 1; + size_t ie_len = 0; + + /* Tag size */ + if (ie->tag > 0x1f) + len_sum++; + if (ie->tag > 0x7f) + len_sum++; + + /* Len size */ + if (ie->value) + ie_len = ie->value->len; + len_sum += len_len(ie_len); + + return len_sum; +} + +/* Go through the BER-TLV tree and compute the length for all constructed IEs */ +static size_t compute_nested_len(struct ss_list *list) +{ + struct ber_tlv_ie *ie; + size_t len_sum = 0; + size_t len; + + if (!list) + return 0; + + SS_LIST_FOR_EACH(list, ie, struct ber_tlv_ie, list) { + if (ie->constr) { + len = compute_nested_len(ie->nested); + + /* NOTE: we only have to store the len. Unfortunatlly + * the length field is inside an ss_buf struct. This + * means we have to carefully check if such a stuct + * is already present. This is normally the case when + * the input data originates from the btlv decoder + * (re-encoding). We will only take action when the + * field length changed or when no (encoded) value + * part is present at all. In those cases we will also + * make sure the data part of the ss_buf is set to + * zeroed out. Under no circumstances we may do + * ss_buf_alloc() and set ie->value->len to the length + * value we have computed above. This will render the + * btlv tree inconsistent! */ + if (!ie->value) { + ie->value = ss_buf_alloc(len); + memset(ie->value->data, 0, ie->value->len); + } else if (ie->value && ie->value->len != len) { + ss_buf_free(ie->value); + ie->value = ss_buf_alloc(len); + memset(ie->value->data, 0, ie->value->len); + } + } + len_sum += ie->value->len; + len_sum += ie_hdr_len(ie); + } + + return len_sum; +} + +/* Assemble BER-TLV encoded string */ +static size_t gen_ber_tlv_header(uint8_t *enc, struct ber_tlv_ie *ie) +{ + size_t bytes_used = 1; + size_t len_field_len; + unsigned int i; + + /* Encode header bits */ + *enc = (ie->cls & 0x03) << 6; + *enc |= (ie->constr & 0x01) << 5; + + /* Encode tag field */ + if (ie->tag < 0x1f) { + *enc |= ie->tag; + } else if (ie->tag <= 0x7f) { + *enc |= 0x1f; + bytes_used++; + enc++; + *enc = ie->tag; + } else { + *enc |= 0x1f; + bytes_used++; + enc++; + *enc = 0x80; + *enc |= ie->tag >> 8; + *enc |= 0x80; + bytes_used++; + enc++; + *enc |= ie->tag & 0xff; + } + + /* Len size */ + len_field_len = len_len(ie->value->len); + if (len_field_len == 1) { + bytes_used++; + enc++; + *enc = ie->value->len; + } else { + bytes_used++; + enc++; + *enc = (len_field_len - 1) | 0x80; + for (i = len_field_len - 1; i > 0; i--) { + bytes_used++; + enc++; + *enc = ie->value->len >> ((i - 1) * 8); + } + } + + return bytes_used; +} + +static size_t gen_ber_tlv_string(uint8_t **enc, const struct ss_list *list) +{ + struct ber_tlv_ie *ie; + size_t bytes_used = 0; + size_t bytes_used_ie; + + if (!list) + return 0; + + SS_LIST_FOR_EACH(list, ie, struct ber_tlv_ie, list) { + bytes_used_ie = gen_ber_tlv_header(*enc, ie); + *enc += bytes_used_ie; + + if (ie->constr) + bytes_used_ie += gen_ber_tlv_string(enc, ie->nested); + else { + memcpy(*enc, ie->value->data, ie->value->len); + bytes_used_ie += ie->value->len; + *enc += ie->value->len; + } + + bytes_used += bytes_used_ie; + } + + return bytes_used; +} + +/*! Encode linked list with BER-TLV data to its binary encoded representation. + * \param[out] enc pointer to buffer to output the encoded BER-TLV data. + * \param[in] len length of the buffer that will store the output. + * \param[inout] list linked list with BER-TLV data to encode (will regenerate length of constructed IEs). + * \returns number of encoed bytes. */ +size_t ss_btlv_encode(uint8_t *enc, size_t len, struct ss_list *list) +{ + size_t bytes_needed; + + /* clear output buffer (just to be sure) */ + memset(enc, 0, len); + + /* (re-)compute the length required for each of the nested fields. */ + bytes_needed = compute_nested_len(list); + + /* don't even start encoding pass when we detect that we do not have + * enough memory for the result */ + if (len < bytes_needed) { + SS_LOGP(SBTLV, LDEBUG, + "cannot encode BER-TLV string, buffer to small (bytes needed: %lu, bytes available: %lu\n", + bytes_needed, len); + return 0; + } + + /* encode BER-TLV data */ + gen_ber_tlv_string(&enc, list); + + return bytes_needed; +} + +/*! Encode linked list with BER-TLV data to its binary encoded representation (to ss_buf). + * \param[inout] list linked list with BER-TLV data to encode (will regenerate length of constructed IEs). + * \returns ss_buf with encoded result. */ +struct ss_buf *ss_btlv_encode_to_ss_buf(struct ss_list *list) +{ + size_t bytes_needed; + struct ss_buf *buf; + uint8_t *data_ptr; + + /* (re-)compute the length required for each of the nested fields. */ + bytes_needed = compute_nested_len(list); + + buf = ss_buf_alloc(bytes_needed); + + /* encode BER-TLV data */ + data_ptr = buf->data; + gen_ber_tlv_string(&data_ptr, list); + + return buf; +} diff --git a/src/softsim/uicc/btlv_utils.c b/src/softsim/uicc/btlv_utils.c new file mode 100644 index 0000000..88ffb12 --- /dev/null +++ b/src/softsim/uicc/btlv_utils.c @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "btlv.h" + +#define SS_BERTLV_MAX_LEN_BYTES 4 + +/* Split an encoded tag into tag, constructed bit and class */ +static int decode_tag(uint16_t *tag, enum ber_tlv_cls *cls, bool *constr, uint32_t tag_encoded) +{ + uint8_t enc[3] = { 0 }; + uint16_t tag_result; + uint8_t cls_result; + bool constr_result; + + if (tag_encoded <= 0xff) + enc[0] = tag_encoded & 0xff; + else if (tag_encoded <= 0xffff) { + enc[0] = (tag_encoded >> 8) & 0xff; + enc[1] = tag_encoded & 0xff; + } else { + enc[0] = (tag_encoded >> 16) & 0xff; + enc[1] = (tag_encoded >> 8) & 0xff; + enc[2] = tag_encoded & 0xff; + } + + cls_result = (enc[0] >> 6) & 3; + constr_result = (enc[0] >> 5) & 1; + + if ((enc[0] & 0x1f) == 0x1f) { + tag_result = enc[1] & 0x7f; + if (enc[1] & 0x80) { + tag_result = tag_result << 8; + tag_result |= enc[2]; + } else { + if (enc[2] != 0x00) + return -EINVAL; + } + } else { + if (enc[1] != 0x00 && enc[2] != 0x00) + return -EINVAL; + tag_result = enc[0] & 0x1f; + } + + if (tag) + *tag = tag_result; + if (cls) + *cls = cls_result; + if (constr) + *constr = constr_result; + return 0; +} + +static void dump_ie(const struct ber_tlv_ie *ie, uint8_t indent, + enum log_subsys subsys, enum log_level level) +{ + char indent_str[256]; + char *title_str, *value_str; + size_t value_len; + char delimiter; + + memset(indent_str, ' ', indent); + indent_str[indent] = '\0'; + + if (ie == NULL) { + SS_LOGP(subsys, level, "%s(NULL)\n", indent_str); + return; + } + + if (ie->title) + title_str = ie->title; + else + title_str = "unknown IE"; + + if (ie->value) { + value_str = ss_hexdump(ie->value->data, ie->value->len); + value_len = ie->value->len; + delimiter = ':'; + } else { + value_str = ""; + value_len = 0; + delimiter = ' '; + } + + SS_LOGP(subsys, level, "%s%s(tag=0x%02x(0x%02x), cls=%x, constr=%s, len=%zu)%c %s\n", + indent_str, title_str, ie->tag_encoded, ie->tag, ie->cls, ie->constr ? "true" : "false", + value_len, delimiter, value_str); +} + +/*! Dump decoded BER-TLV data. + * \param[in] list linked list begin of the BER-TLV tree. + * \param[in] indent indentation level of the generated output. + * \param[in] log_subsys log subsystem to generate the output for. + * \param[in] log_level log level to generate the output for. */ +void ss_btlv_dump(const struct ss_list *list, uint8_t indent, + enum log_subsys log_subsys, enum log_level log_level) +{ + struct ber_tlv_ie *ie; + + SS_LIST_FOR_EACH(list, ie, struct ber_tlv_ie, list) { + dump_ie(ie, indent, log_subsys, log_level); + if (ie->constr && ie->nested) { + ss_btlv_dump(ie->nested, indent + 2, log_subsys, + log_level); + } + } +} + +static void free_ie(struct ber_tlv_ie *ie) +{ + if (ie == NULL) + return; + + if (ie->title) + SS_FREE(ie->title); + if (ie->nested) + SS_FREE(ie->nested); + if (ie->value) + ss_buf_free(ie->value); + + /* Make sure all data vanishes from memory */ + memset(ie, 0, sizeof(*ie)); + + SS_FREE(ie); +} + +static void btlv_free(struct ss_list *list) +{ + struct ber_tlv_ie *ie; + struct ber_tlv_ie *ie_pre; + + if (!list) + return; + + if (ss_list_empty(list)) + return; + + SS_LIST_FOR_EACH_SAVE(list, ie, ie_pre, struct ber_tlv_ie, list) { + /* If it is a constructed element, we have to take care of the + * nested elements first */ + if (ie->constr && ie->nested) + btlv_free(ie->nested); + + /* Unlink the element from the list and free it. */ + ss_list_remove(&ie->list); + free_ie(ie); + } +} + +/*! Free BER-TLV data (including list begin). + * \param[in] list linked list begin of the BER-TLV tree. */ +void ss_btlv_free(struct ss_list *list) +{ + if (!list) + return; + + /* Free everything that is in the supplied list */ + btlv_free(list); + + /* Get rid of the list isself */ + SS_FREE(list); +} + +/* Create a new BER-TLV IE with binary data */ + +/*! Allocate a new BER-TLV IE. + * \param[out] parent linked list parent of the BER-TLV tree. + * \param[in] title human readable title that serves as a description. + * \param[in] tag BER-TLV tag (encoded format). + * \param[in] len BER-TLV value length. + * \param[in] value pointer to BER-TLV value (data is copied). + * \returns pointer to allocated IE struct. */ +struct ber_tlv_ie *ss_btlv_new_ie(struct ss_list *parent, const char *title, + uint32_t tag, size_t len, + const uint8_t *value) +{ + int rc; + struct ber_tlv_ie *ie = SS_ALLOC(struct ber_tlv_ie); + bool constr; + + memset(ie, 0, sizeof(*ie)); + + /* Make sure the supplied tag is correctly formatted and consistent */ + ie->tag_encoded = tag; + rc = decode_tag(&ie->tag, &ie->cls, &constr, tag); + if (rc < 0) { + SS_LOGP(SBTLV, LERROR, + "incorrect tag format (%02x), cannot create IE!\n", tag); + SS_FREE(ie); + return NULL; + } + if (constr != false) { + SS_LOGP(SBTLV, LERROR, + "tag does not describe a primitive ie (%02x), cannot create IE!\n", tag); + SS_FREE(ie); + return NULL; + } + + if (title) { + ie->title = SS_ALLOC_N(strlen(title) + 1); + strcpy(ie->title, title); + } + ie->nested = NULL; + if (value) { + ie->value = ss_buf_alloc(len); + memcpy(ie->value->data, value, len); + } + + if (parent) + ss_list_put(parent, &ie->list); + return ie; +} + +/*! Allocate a new constructed BER-TLV IE. + * \param[out] parent linked list parent of the BER-TLV tree. + * \param[in] title human readable title that serves as a description. + * \param[in] tag BER-TLV tag (encoded format). + * \returns pointer to allocated IE struct. */ +struct ber_tlv_ie *ss_btlv_new_ie_constr(struct ss_list *parent, + const char *title, uint32_t tag) +{ + int rc; + struct ber_tlv_ie *ie = SS_ALLOC(struct ber_tlv_ie); + bool constr; + + memset(ie, 0, sizeof(*ie)); + + /* Make sure the supplied tag is correctly formatted and consistent */ + ie->tag_encoded = tag; + rc = decode_tag(&ie->tag, &ie->cls, &constr, tag); + if (rc < 0) { + SS_LOGP(SBTLV, LERROR, + "incorrect tag format (%02x), cannot create IE!\n", tag); + SS_FREE(ie); + return NULL; + } + if (constr != true) { + SS_LOGP(SBTLV, LERROR, + "tag does not describe a constructed ie (%02x), cannot create IE!\n", tag); + SS_FREE(ie); + return NULL; + } + + if (title) { + ie->title = SS_ALLOC_N(strlen(title) + 1); + strcpy(ie->title, title); + } + ie->value = NULL; + ie->constr = true; + ie->nested = SS_ALLOC(struct ss_list); + ss_list_init(ie->nested); + + if (parent) + ss_list_put(parent, &ie->list); + return ie; +} + +/*! Get an IE from the list by its tag (on the current level). + * \param[in] list linked list begin of the BER-TLV tree. + * \param[in] tag BER-TLV (encoded format) tag to look for. + * \returns pointer to IE struct on success, NULL if IE is not found. */ +struct ber_tlv_ie *ss_btlv_get_ie(const struct ss_list *list, uint32_t tag) +{ + struct ber_tlv_ie *ie; + int rc; + + if (!list) + return NULL; + + /* Make sure we search only for correctly formatted tags */ + rc = decode_tag(NULL, NULL, NULL, tag); + if (rc < 0) { + SS_LOGP(SBTLV, LERROR, + "incorrect tag format (%02x), cannot search for IE!\n", tag); + return NULL; + } + + SS_LIST_FOR_EACH(list, ie, struct ber_tlv_ie, list) { + if (ie->tag_encoded == tag) + return ie; + } + + return NULL; +} + +/*! Get an IE from the list by its tag, ensure minimum length (on the current level). + * \param[in] list linked list begin of the BER-TLV tree. + * \param[in] tag BER-TLV tag to look for. + * \param[in] min_len minimum required length. + * \returns pointer to IE struct on success, NULL if IE is not found. */ +struct ber_tlv_ie *ss_btlv_get_ie_minlen(const struct ss_list *list, + uint16_t tag, size_t min_len) +{ + struct ber_tlv_ie *ie = ss_btlv_get_ie(list, tag); + if (!ie) + return NULL; + if (ie->value->len < min_len) + return NULL; + return ie; +} + +/*! Attach a BER-TLV tree to an existing IE. + * \param[inout] ie BER-TLV IE struct to which the BER-TLV tree shall be attached. + * \param[in] list linked list begin of the BER-TLV tree. */ +void ss_btlv_attach_to_constr(struct ber_tlv_ie *ie, struct ss_list *list) +{ + /* Get rid of all current nested elements. Normally there should only + * be an empty list and nothing else. */ + ss_btlv_free(ie->nested); + + /* May exist when the btlv tree (list) is a parsed result. There is + * no replacement for this. The encoder will not use this field. */ + ss_buf_free(ie->value); + ie->value = NULL; + + /* Attach given list as the new nested BTLV elements. */ + ie->nested = list; +} + +/*! Split off a BER-TLV tree from an existing IE. + * \param[inout] ie BER-TLV IE struct to which the BER-TLV tree shall be splitted off. + * \returns linked list begin of the BER-TLV tree that is splitted off. */ +struct ss_list *ss_btlv_split_off_from_constr(struct ber_tlv_ie *ie) +{ + struct ss_list *result; + + result = ie->nested; + ie->nested = SS_ALLOC(struct ss_list); + ss_list_init(ie->nested); + return result; +} diff --git a/src/softsim/uicc/command.c b/src/softsim/uicc/command.c new file mode 100644 index 0000000..a6bc1fc --- /dev/null +++ b/src/softsim/uicc/command.c @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include +#include "command.h" +#include "context.h" +#include "uicc_ins.h" +#include "uicc_pin.h" +#include "uicc_file_ops.h" +#include "uicc_admin.h" +#include "uicc_auth.h" +#include "uicc_cat.h" +#include "apdu.h" +#include "sw.h" + +const struct ss_command commands[] = { + + /* pin handling */ + { + .name = "VERIFY PIN", + .cla = 0x00, + .cla_mask = 0x70, + .ins = TS_102_221_INS_VERIFY_PIN, + .handler = ss_uicc_pin_cmd_verify_pin, + .case_ = SS_COMMAND_CASE_3, + }, + { + .name = "CHANGE PIN", + .cla = 0x00, + .cla_mask = 0x70, + .ins = TS_102_221_INS_CHANGE_PIN, + .handler = ss_uicc_pin_cmd_change_pin, + .case_ = SS_COMMAND_CASE_3, + }, + { + .name = "DISABLE PIN", + .cla = 0x00, + .cla_mask = 0x70, + .ins = TS_102_221_INS_DISABLE_PIN, + .handler = ss_uicc_pin_cmd_disable_pin, + .case_ = SS_COMMAND_CASE_3, + }, + { + .name = "ENABLE PIN", + .cla = 0x00, + .cla_mask = 0x70, + .ins = TS_102_221_INS_ENABLE_PIN, + .handler = ss_uicc_pin_cmd_enable_pin, + .case_ = SS_COMMAND_CASE_3, + }, + { + .name = "UNBLOCK PIN", + .cla = 0x00, + .cla_mask = 0x70, + .ins = TS_102_221_INS_UNBLOCK_PIN, + .handler = ss_uicc_pin_cmd_unblock_pin, + .case_ = SS_COMMAND_CASE_3, + }, + + /* file operations */ + { + .name = "STATUS", + .cla = 0x00, + .cla_mask = 0x70, + .ins = TS_102_221_INS_STATUS, + .handler = ss_uicc_file_ops_cmd_status, + .case_ = SS_COMMAND_CASE_2, + }, + { + .name = "READ BINARY", + .cla = 0x00, + .cla_mask = 0x70, + .ins = TS_102_221_INS_READ_BINARY, + .handler = ss_uicc_file_ops_cmd_read_binary, + .case_ = SS_COMMAND_CASE_2, + }, + { + .name = "UPDATE BINARY", + .cla = 0x00, + .cla_mask = 0x70, + .ins = TS_102_221_INS_UPDATE_BINARY, + .handler = ss_uicc_file_ops_cmd_update_binary, + .case_ = SS_COMMAND_CASE_3, + }, + { + .name = "READ RECORD", + .cla = 0x00, + .cla_mask = 0x70, + .ins = TS_102_221_INS_READ_RECORD, + .handler = ss_uicc_file_ops_cmd_read_record, + .case_ = SS_COMMAND_CASE_2, + }, + { + .name = "UPDATE RECORD", + .cla = 0x00, + .cla_mask = 0x70, + .ins = TS_102_221_INS_UPDATE_RECORD, + .handler = ss_uicc_file_ops_cmd_update_record, + .case_ = SS_COMMAND_CASE_3, + }, + { + .name = "SEARCH RECORD", + .cla = 0x00, + .cla_mask = 0x70, + .ins = TS_102_221_INS_SEARCH_RECORD, + .handler = ss_uicc_file_ops_cmd_search_record, + .case_ = SS_COMMAND_CASE_4, + }, + { + .name = "SELECT FILE", + .cla = 0x00, + .cla_mask = 0x70, + .ins = TS_102_221_INS_SELECT_FILE, + .handler = ss_uicc_file_ops_cmd_select, + .case_ = SS_COMMAND_CASE_3, /* It does have response data, but that's not being asked for by an LE */ + }, + + /* administrative commands */ + { + .name = "CREATE FILE", + .cla = 0x00, + .cla_mask = 0xB0, /* 0X or 4X */ + .ins = TS_102_222_INS_CREATE_FILE, + .handler = ss_uicc_admin_cmd_create_file, + .case_ = SS_COMMAND_CASE_3, + }, + { + .name = "DELETE FILE", + .cla = 0x00, + .cla_mask = 0xB0, /* 0X or 4X */ + .ins = TS_102_222_INS_DELETE_FILE, + .handler = ss_uicc_admin_cmd_delete_file, + .case_ = SS_COMMAND_CASE_3, + }, + { + .name = "ACTIVATE", + .cla = 0x00, + .cla_mask = 0xB0, /* 0X or 4X */ + .ins = TS_102_221_INS_ACTIVATE_FILE, + .handler = ss_uicc_admin_cmd_activate_file, + .case_ = SS_COMMAND_CASE_3, + }, + + /* CAT commands */ + { + .name = "TERMINAL PROFILE", + .cla = 0x80, + .cla_mask = 0xB0, /* 0X or 4X */ + .ins = TS_102_221_INS_TERMINAL_PROFILE, + .handler = ss_uicc_cat_cmd_term_profile, + .case_ = SS_COMMAND_CASE_3, + }, + { + .name = "ENVELOPE", + .cla = 0x80, + .cla_mask = 0xB0, /* 0X or 4X */ + .ins = TS_102_221_INS_ENVELOPE, + .handler = ss_uicc_cat_cmd_envelope, + .case_ = SS_COMMAND_CASE_3, /* It does have response data, but that's not being asked for by an LE */ + }, + { + .name = "FETCH", + .cla = 0x80, + .cla_mask = 0xB0, /* 0X or 4X */ + .ins = TS_102_221_INS_FETCH, + .handler = ss_uicc_cat_cmd_fetch, + .case_ = SS_COMMAND_CASE_2, + }, + { + .name = "TERMINAL RESPONSE", + .cla = 0x80, + .cla_mask = 0xB0, /* 0X or 4X */ + .ins = TS_102_221_INS_TERMINAL_RESPONSE, + .handler = ss_uicc_cat_cmd_term_resp, + .case_ = SS_COMMAND_CASE_3, + }, + + /* logical channels */ + { + .name = "MANAGE CHANNEL", + .cla = 0x00, + .cla_mask = 0xB0, /* 0X or 4X */ + .ins = TS_102_221_INS_MANAGE_CHANNEL, + .handler = ss_uicc_lchan_cmd_manage_channel, + .case_ = SS_COMMAND_CASE_2, + }, + + /* authentication */ + { + .name = "AUTHENTICATE EVEN", + .cla = 0x00, + .cla_mask = 0x70, + .ins = TS_102_221_INS_AUTHENTICATE_EVEN, + .handler = ss_uicc_auth_cmd_authenticate_even_fn, + .case_ = SS_COMMAND_CASE_4, + }, + +}; + +/*! Find the command handler for given APDU CLA+INS. + * \param[inout] apdu apdu with incoming command information. + * \returns command struct, NULL when command not found. */ +const struct ss_command *ss_command_match(struct ss_apdu *apdu) +{ + unsigned int i; + bool cla_seen = false; + + for(i = 0; i < SS_ARRAY_SIZE(commands); i++) { + if ((apdu->hdr.cla & commands[i].cla_mask) == commands[i].cla) + cla_seen = true; + + if ((apdu->hdr.cla & commands[i].cla_mask) == commands[i].cla + && apdu->hdr.ins == commands[i].ins) { + SS_LOGP(SCMD, LINFO, "command found (cla=%02X, cla_mask=%02X, ins=%02X, name=\"%s\")\n", + commands[i].cla, commands[i].cla_mask, commands[i].ins, commands[i].name); + return &commands[i]; + } + } + + SS_LOGP(SCMD, LERROR, "command not found (cla=%02X, ins=%02X)\n", + apdu->hdr.cla, apdu->hdr.ins); + + if (!cla_seen) + apdu->sw = SS_SW_ERR_CHECKING_CLA_INVALID; + else + apdu->sw = SS_SW_ERR_CHECKING_INS_INVALID; + + return NULL; +} diff --git a/src/softsim/uicc/command.h b/src/softsim/uicc/command.h new file mode 100644 index 0000000..892d67d --- /dev/null +++ b/src/softsim/uicc/command.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include +struct ss_context; +struct ss_apdu; + +/*! Classification of command body according to ISO/IEC 7816-3 Secton 12.1 */ +enum ss_command_case { + SS_COMMAND_CASE_UNDEF = 0, /* TBD remove this when all is properly initialized */ + SS_COMMAND_CASE_1, /**< just header */ + SS_COMMAND_CASE_2, /**< header, Le */ + SS_COMMAND_CASE_3, /**< header, Lc, data */ + SS_COMMAND_CASE_4, /**< header, Lc, data, Le */ + /* It may turn out that some commands occur in multiple cases; these would + * need a special case, and would require an "indeterminate" variant, in + * which the handler will be required to indicate the consumed length. */ +}; + +/*! command handler for APDU commands */ +struct ss_command { + /*! Human readable name that describes the command. */ + const char *name; + /*! CLA and MASK against which to compare CLA from APDU header */ + uint8_t cla; + uint8_t cla_mask; + /*! INS against which to compare the INS from APDU header */ + uint8_t ins; + /*! call-back function to be called when a matching command was received. + * Return value + * 0: OK (SW 9000 is sent by command dispatcher) + * > 0: use return value as SW + * < 0: some unexpected internal error, generic error SW is used */ + int (*handler)(struct ss_apdu *apdu); + /*! Command case: description of which data follows the APDU header + * + * Having this in metadata (and not just impicitly using the knowledge in the + * handler implementation) allows processing commands from multiple buffers, + * as they are presented in compact remote commands. + */ + enum ss_command_case case_; +}; + +const struct ss_command *ss_command_match(struct ss_apdu *apdu); diff --git a/src/softsim/uicc/context.h b/src/softsim/uicc/context.h new file mode 100644 index 0000000..30d40e2 --- /dev/null +++ b/src/softsim/uicc/context.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include "uicc_lchan.h" +#include "proactive.h" +#include "fs_chg.h" + +/* Context for one softsim instance. */ +struct ss_context { + + /* context holding the state for the (one and only) logical channel */ + struct ss_lchan lchan; + + /* context holding the state for proactive SIM commands */ + struct ss_proactive_ctx proactive; + + /* pointer to an array of size SS_FS_CHG_BUF_SIZE */ + uint8_t *fs_chg_filelist; + + /* File changes through this context are recorded in the associated + * file list */ + bool fs_chg_record; + + /* If true, then fs_chg_filelist is owned by a different context */ + bool fs_chg_is_borrowed; +}; + +struct ss_context *ss_new_reporting_ctx(uint8_t *fs_chg_filelist); diff --git a/src/softsim/uicc/ctlv.c b/src/softsim/uicc/ctlv.c new file mode 100644 index 0000000..15e68fe --- /dev/null +++ b/src/softsim/uicc/ctlv.c @@ -0,0 +1,459 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ctlv.h" + +/* Advance the bufer and check if we are still in bounds. The parameter "inc" + * sets how many bytes the buffer pointer (enc) should be be advanced. The + * parameter "bytes_ahead" sets the minimum valid bytes that the caller expects + * to be available after the buffer pointer (enc) has been advanced. */ +#define CHECK_AND_ADVANCE(inc, bytes_ahead) \ + if (len < bytes_used + inc + bytes_ahead) { \ + SS_LOGP(SCTLV, LDEBUG, "exceeding buffer bounds: len=%zu, inc=%zu, bytes_ahead=%zu, cannot decode IE\n", \ + len, (size_t) inc, (size_t) bytes_ahead); \ + return NULL; \ + } \ + bytes_used+=inc; \ + enc+=inc \ + +static struct cmp_tlv_ie *decode_ie(size_t *used_len, const uint8_t *enc, size_t len) +{ + struct cmp_tlv_ie ie; + struct cmp_tlv_ie *ie_ret; + size_t bytes_used = 0; + uint32_t ie_len = 0; + const uint8_t *value; + + *used_len = 0; + memset(&ie, 0, sizeof(ie)); + + /* We expect at least 1 byte tag + 1 byte len */ + if (len < 2) + return NULL; + + /* Not used */ + if (*enc == 0xFF || *enc == 0x00) + return NULL; + + /* Reserved for future use */ + if (*enc == 0x80) + return NULL; + + /* Decode tag */ + if (*enc == 0x7F) { + CHECK_AND_ADVANCE(1, 1); + if (*enc & 0x80) + ie.cr = true; + ie.tag = *enc << 8; + CHECK_AND_ADVANCE(1, 1); + ie.tag |= *enc; + ie.tag &= 0x7FFF; + } else { + if (*enc & 0x80) + ie.cr = true; + ie.tag = *enc; + ie.tag &= 0x007F; + } + CHECK_AND_ADVANCE(1, 1); + + /* Decode length */ + if (*enc <= 0x7F) { + ie_len = *enc; + } else if (*enc == 0x81) { + CHECK_AND_ADVANCE(1, 1); + ie_len = *enc; + } else if (*enc == 0x82) { + CHECK_AND_ADVANCE(1, 1); + ie_len = *enc << 8; + CHECK_AND_ADVANCE(1, 1); + ie_len |= *enc; + } else if (*enc == 0x83) { + CHECK_AND_ADVANCE(1, 1); + ie_len = *enc << 16; + CHECK_AND_ADVANCE(1, 1); + ie_len |= *enc << 8; + CHECK_AND_ADVANCE(1, 1); + ie_len |= *enc; + } + CHECK_AND_ADVANCE(1, ie_len); + + value = enc; + CHECK_AND_ADVANCE(ie_len, 0); + *used_len = bytes_used; + + /* Create output struct */ + ie_ret = SS_ALLOC(struct cmp_tlv_ie); + if (!ie_ret) + return NULL; + memcpy(ie_ret, &ie, sizeof(ie)); + + /* Copy data part to the newly allocated item */ + ie_ret->value = ss_buf_alloc(ie_len); + memcpy(ie_ret->value->data, value, ie_len); + + return ie_ret; +} + +/*! Decode binary COMPREHENSION-TLV encoded data. + * \param[in] enc pointer to buffer with encoded COMPREHENSION-TLV data. + * \param[in] len length of the buffer that contains the COMPREHENSION-TLV encoded data. + * \returns pointer to allocated linked list with COMPREHENSION-TLV data (can be empty). */ +struct ss_list *ss_ctlv_decode(const uint8_t *enc, size_t len) +{ + size_t i; + struct cmp_tlv_ie *ie; + size_t used_len; + size_t remaining_len = len; + + struct ss_list *list; + + list = SS_ALLOC(struct ss_list); + ss_list_init(list); + do { + /* Decode IE and store it in the given list */ + ie = decode_ie(&used_len, enc, remaining_len); + if (ie) { + ss_list_put(list, &ie->list); + } else if (remaining_len > 0) { + for (i = used_len; i < used_len + remaining_len; ++i) { + if (enc[i] != 0xff) { + SS_LOGP(SCTLV, LERROR, + "Error decoding COMPREHENSION-BTLV (%s).\n", + ss_hexdump(&enc[used_len], + remaining_len)); + ss_ctlv_free(list); + return NULL; + } + } + } + + /* Go to the next IE */ + enc += used_len; + remaining_len -= used_len; + } while (ie != NULL); + + return list; +} + +static void dump_ie(const struct cmp_tlv_ie *ie, uint8_t indent, + enum log_subsys subsys, enum log_level level) +{ + char indent_str[256]; + char *value_str; + size_t value_len; + char delimiter; + uint8_t tag_cr; + + memset(indent_str, ' ', indent); + indent_str[indent] = '\0'; + + if (ie == NULL) { + SS_LOGP(subsys, level, "%s(NULL)\n", indent_str); + return; + } + + if (ie->value) { + value_str = ss_hexdump(ie->value->data, ie->value->len); + value_len = ie->value->len; + delimiter = ':'; + } else { + value_str = ""; + value_len = 0; + delimiter = ' '; + } + + tag_cr = ie->tag; + if (ie->cr) + tag_cr |= 0x80; + + SS_LOGP(subsys, level, "%s(tag=0x%02x(0x%02x), cr=%s, len=%zu)%c %s\n", + indent_str, tag_cr, ie->tag, ie->cr ? "true" : "false", + value_len, delimiter, value_str); +} + +/*! Dump decoded COMPREHENSION-TLV data. + * \param[in] list linked list begin of the COMPREHENSION-TLV list. + * \param[in] indent indentation level of the generated output. + * \param[in] log_subsys log subsystem to generate the output for. + * \param[in] log_level log level to generate the output for. */ +void ss_ctlv_dump(const struct ss_list *list, uint8_t indent, + enum log_subsys log_subsys, enum log_level log_level) +{ + struct cmp_tlv_ie *ie; + + SS_LIST_FOR_EACH(list, ie, struct cmp_tlv_ie, list) { + dump_ie(ie, indent, log_subsys, log_level); + } +} + +static void free_ie(struct cmp_tlv_ie *ie) +{ + if (ie == NULL) + return; + + if (ie->value) + ss_buf_free(ie->value); + + /* Make sure all data vanishes from memory */ + memset(ie, 0, sizeof(*ie)); + + SS_FREE(ie); +} + +/*! Free COMPREHENSION-TLV data (including list begin). + * \param[in] list linked list begin of the COMPREHENSION-TLV list. */ +void ss_ctlv_free(struct ss_list *list) +{ + struct cmp_tlv_ie *ie; + struct cmp_tlv_ie *ie_pre; + + if (!list) + return; + + if (ss_list_empty(list)) + return; + + SS_LIST_FOR_EACH_SAVE(list, ie, ie_pre, struct cmp_tlv_ie, list) { + /* Unlink the element from the list and free it. */ + ss_list_remove(&ie->list); + free_ie(ie); + } + + /* Get rid of the list isself */ + SS_FREE(list); +} + +/*! Allocate a new COMPREHENSION-TLV IE. + * \param[out] list linked list parent of the COMPREHENSION-TLV list. + * \param[in] tag COMPREHENSION-TLV tag (encoded format). + * \param[in] cr COMPREHENSION-TLV comprehension flag. + * \param[in] len COMPREHENSION-TLV value length. + * \param[in] value pointer to COMPREHENSION-TLV value (data is copied). + * \returns pointer to allocated IE struct. */ +struct cmp_tlv_ie *ss_ctlv_new_ie(struct ss_list *list, uint16_t tag, bool cr, + size_t len, const uint8_t *value) +{ + struct cmp_tlv_ie *ie = SS_ALLOC(struct cmp_tlv_ie); + + memset(ie, 0, sizeof(*ie)); + + ie->tag = tag; + ie->cr = cr; + + if (value) { + ie->value = ss_buf_alloc(len); + memcpy(ie->value->data, value, len); + } else { + ie->value = ss_buf_alloc(0); + } + + if (list) + ss_list_put(list, &ie->list); + return ie; +} + +/*! Get an IE from the list by its tag (on the current level). + * \param[in] list linked list begin of the COMPREHENSION-TLV tree. + * \param[in] tag COMPREHENSION-TLV (encoded format) tag to look for. + * \returns pointer to IE struct on success, NULL if IE is not found. */ +struct cmp_tlv_ie *ss_ctlv_get_ie(const struct ss_list *list, uint16_t tag) +{ + struct cmp_tlv_ie *ie; + + if (!list) + return NULL; + + SS_LIST_FOR_EACH(list, ie, struct cmp_tlv_ie, list) { + if (ie->tag == tag) + return ie; + } + + return NULL; +} + +/*! Get an IE from the list by its tag, ensure minimum length (on the current level). + * \param[in] list linked list begin of the COMPREHENSION-TLV tree. + * \param[in] tag COMPREHENSION-TLV tag to look for. + * \param[in] min_len minimum required length. + * \returns pointer to IE struct on success, NULL if IE is not found. */ +struct cmp_tlv_ie *ss_ctlv_get_ie_minlen(const struct ss_list *list, + uint16_t tag, size_t min_len) +{ + struct cmp_tlv_ie *ie = ss_ctlv_get_ie(list, tag); + if (!ie) + return NULL; + if (ie->value->len < min_len) + return NULL; + return ie; +} + +static size_t calc_ie_len(const struct cmp_tlv_ie *ie) +{ + size_t len = 0; + + if (ie->tag == 0x7F || ie->tag > 0xFE) + len += 3; + else + len++; + + if (ie->value->len <= 0x7F) { + len++; + } else if (ie->value->len <= 0xFF) { + len += 2; + } else if (ie->value->len <= 0xFFFF) { + len += 3; + } else if (ie->value->len <= 0xFFFFFF) { + len += 4; + } else { + return 0; + } + + len += ie->value->len; + + return len; +} + +static size_t encode_ie(uint8_t *enc, size_t len, const struct cmp_tlv_ie *ie) +{ + size_t ie_len; + uint16_t tag; + + ie_len = calc_ie_len(ie); + + /* Do not encode anything when we are unable to determine the length + * or when the predicted length exceeds the buffer. */ + if (ie_len == 0 || ie_len > len) { + SS_LOGP(SCTLV, LERROR, + "not enough buffer space to encode TLV string, aborting at IE %02x.\n", + ie->tag); + return 0; + } + + /* Do not allow tag values that are not allowed. */ + if (ie->tag == 0x00 || ie->tag == 0xff || ie->tag == 0x80) { + SS_LOGP(SCTLV, LERROR, + "tag %02x is not allowed in COMPRENSION-TLV, aborting at IE\n", + ie->tag); + return 0; + } + + /* Encode tag */ + tag = ie->tag; + if (ie->tag == 0x7F || ie->tag > 0xFE) { + if (ie->cr) + tag |= 0x8000; + *enc = 0x7F; + enc++; + *enc = (tag >> 8) & 0xFF; + enc++; + *enc = tag & 0xFF; + } else { + if (ie->cr) + tag |= 0x80; + *enc = tag; + } + enc++; + + /* Encode len */ + if (ie->value->len <= 0x7F) { + *enc = ie->value->len; + } else if (ie->value->len <= 0xFF) { + *enc = 0x81; + enc++; + *enc = ie->value->len; + } else if (ie->value->len <= 0xFFFF) { + *enc = 0x82; + enc++; + *enc = (ie->value->len >> 8) & 0xFF; + enc++; + *enc = ie->value->len & 0xFF; + } else if (ie->value->len <= 0xFFFFFF) { + *enc = 0x83; + enc++; + *enc = (ie->value->len >> 16) & 0xFF; + enc++; + *enc = (ie->value->len >> 8) & 0xFF; + enc++; + *enc = ie->value->len & 0xFF; + } else { + SS_LOGP(SCTLV, LERROR, + "Error encoding IE, length field too large (%zu), aborting at IE %02x\n", + ie->value->len, ie->tag); + return 0; + } + enc++; + + /* Encode data */ + memcpy(enc, ie->value->data, ie->value->len); + + return ie_len; +} + +/*! Encode linked list with COMPREHENSION-TLV data to its binary encoded representation. + * \param[out] enc pointer to buffer to output the encoded COMPREHENSION-TLV data. + * \param[in] len length of the buffer that will store the output. + * \param[in] list linked list with COMPREHENSION-TLV data to encode. + * \returns number of encoed bytes. */ +size_t ss_ctlv_encode(uint8_t *enc, size_t len, const struct ss_list *list) +{ + size_t bytes_remain = len; + struct cmp_tlv_ie *ie; + size_t rc; + + /* clear output buffer (just to be sure) */ + memset(enc, 0, len); + + SS_LIST_FOR_EACH(list, ie, struct cmp_tlv_ie, list) { + rc = encode_ie(enc, bytes_remain, ie); + if (rc == 0) + return 0; + bytes_remain -= rc; + enc += rc; + } + + return len - bytes_remain; +} + +static size_t calc_ctlv_len(const struct ss_list *list) +{ + size_t bytes_needed = 0; + struct cmp_tlv_ie *ie; + size_t rc; + + SS_LIST_FOR_EACH(list, ie, struct cmp_tlv_ie, list) { + rc = calc_ie_len(ie); + if (rc == 0) + return 0; + bytes_needed += rc; + } + + return bytes_needed; +} + +/*! Encode linked list with COMPREHENSION-TLV data to its binary encoded representation (to ss_buf). + * \param[inout] list linked list with COMPREHENSION-TLV data to encode. + * \returns ss_buf with encoded result. */ +struct ss_buf *ss_ctlv_encode_to_ss_buf(const struct ss_list *list) +{ + size_t bytes_needed; + struct ss_buf *buf; + + bytes_needed = calc_ctlv_len(list); + buf = ss_buf_alloc(bytes_needed); + ss_ctlv_encode(buf->data, buf->len, list); + + return buf; +} diff --git a/src/softsim/uicc/ctlv.h b/src/softsim/uicc/ctlv.h new file mode 100644 index 0000000..f115f8e --- /dev/null +++ b/src/softsim/uicc/ctlv.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include +#include +#include + +struct cmp_tlv_ie { + struct ss_list list; + + bool cr; + uint16_t tag; + struct ss_buf *value; +}; + +struct ss_list *ss_ctlv_decode(const uint8_t *enc, size_t len); +void ss_ctlv_dump(const struct ss_list *list, uint8_t indent, + enum log_subsys log_subsys, enum log_level log_level); +void ss_ctlv_free(struct ss_list *list); + +struct cmp_tlv_ie *ss_ctlv_new_ie(struct ss_list *list, uint16_t tag, bool cr, + size_t len, const uint8_t *value); +struct cmp_tlv_ie *ss_ctlv_get_ie(const struct ss_list *list, uint16_t tag); +struct cmp_tlv_ie *ss_ctlv_get_ie_minlen(const struct ss_list *list, + uint16_t tag, size_t min_len); + +size_t ss_ctlv_encode(uint8_t *enc, size_t len, const struct ss_list *list); +struct ss_buf *ss_ctlv_encode_to_ss_buf(const struct ss_list *list); diff --git a/src/softsim/uicc/df_name.c b/src/softsim/uicc/df_name.c new file mode 100644 index 0000000..83f1cd6 --- /dev/null +++ b/src/softsim/uicc/df_name.c @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include +#include "fs.h" +#include "fs_utils.h" +#include "fcp.h" +#include "btlv.h" + +#define DF_NAME_FID 0xA1DF1D01 + +/*! Register a FID in the internal SFI to FID translation file. + * \param[inout] path path to the file that shall be registered. + * \returns 0 success, -EINVAL on failure. */ +int ss_df_name_update(struct ss_list *path) +{ + struct ss_file *file; + struct ber_tlv_ie *fcp_df_name_ie; + struct ber_tlv_ie *fcp_fid_ie; + int rc; + uint8_t record[16 + 2]; + size_t free_record; + struct ss_list path_copy; + + /* ensure we won't work on an uninitialized list */ + ss_list_init(&path_copy); + + /*! NOTE: This function will always search in the currently selected DF + * or ADF for the DF_NAME to FID lookup file */ + + file = ss_get_file_from_path(path); + + /* get FID */ + fcp_fid_ie = + ss_btlv_get_ie_minlen(file->fcp_decoded, + TS_102_221_IEI_FCP_FILE_ID, 2); + if (!fcp_fid_ie) + return -EINVAL; + + /* Extract DF Name (if present) */ + fcp_df_name_ie = ss_btlv_get_ie_minlen(file->fcp_decoded, + TS_102_221_IEI_FCP_DF_NAME, 1); + if (!fcp_df_name_ie) { + /* Nothing to do, This file just has no DF_NAME assigned */ + return 0; + } + + if (fcp_df_name_ie->value->len > 16) { + SS_LOGP(SDFNAME, LERROR, + "cannot register too long DF_NAME %s, len=%lu > 16\n", + ss_hexdump(fcp_df_name_ie->value->data, + fcp_df_name_ie->value->len), + fcp_df_name_ie->value->len); + return -EINVAL; + } + memset(record, 0, sizeof(record)); + memcpy(record, fcp_df_name_ie->value->data, fcp_df_name_ie->value->len); + assert(fcp_fid_ie->value->len == 2); + memcpy(record + 16, fcp_fid_ie->value->data, fcp_fid_ie->value->len); + + /* select lookup file and add new DF_NAME/FID_RECORD. If the file does + * not exist, create it. */ + rc = ss_fs_utils_path_clone(&path_copy, path); + if (rc < 0) + return -EINVAL; + rc = ss_fs_select_parent(&path_copy); + if (rc < 0) { + /* Under normal conditions this shouldn't happen. If it does, + * then an inconsistent file system or a bug/misuse of this + * function might be the cause. */ + SS_LOGP(SDFNAME, LERROR, + "failed to select parent directory - this is where we would expect the lookup file to be\n"); + rc = -EINVAL; + goto leave; + } + + /* Select lookup file. If it does not exist, create a new one. */ + rc = ss_fs_select(&path_copy, DF_NAME_FID); + if (rc < 0) { + SS_LOGP(SDFNAME, LERROR, + "lookup file %s does not exist, creating a new one.\n", + ss_fs_utils_dump_path(&path_copy)); + rc = ss_fs_utils_create_record_file(&path_copy, DF_NAME_FID, + 16 + 2, 16); + rc += ss_fs_select(&path_copy, DF_NAME_FID); + if (rc < 0) { + SS_LOGP(SDFNAME, LERROR, + "failed to create lookup file %s\n", + ss_fs_utils_dump_path(&path_copy)); + rc = -EINVAL; + goto leave; + } + } + + /* Find a free record */ + free_record = ss_fs_utils_find_free_record(&path_copy); + if (!free_record) { + SS_LOGP(SDFNAME, LERROR, + "failed to register DF_NAME=%s in lookup file %s - no free record found\n", + ss_hexdump(fcp_df_name_ie->value->data, + fcp_df_name_ie->value->len), + ss_fs_utils_dump_path(&path_copy)); + rc = -EINVAL; + goto leave; + } + + rc = ss_fs_write_file_record(&path_copy, free_record, record, + sizeof(record)); + if (rc < 0) { + SS_LOGP(SDFNAME, LERROR, + "failed to register DF_NAME=%s in lookup file %s - could not write record\n", + ss_hexdump(fcp_df_name_ie->value->data, + fcp_df_name_ie->value->len), + ss_fs_utils_dump_path(&path_copy)); + rc = -EINVAL; + goto leave; + } + + SS_LOGP(SDFNAME, LDEBUG, + "registered DF_NAME=%s for FID=%02x%02x in file %s on record number %lu\n", + ss_hexdump(fcp_df_name_ie->value->data, + fcp_df_name_ie->value->len), + fcp_fid_ie->value->data[0], fcp_fid_ie->value->data[1], + ss_fs_utils_dump_path(&path_copy), free_record); + rc = 0; +leave: + ss_path_reset(&path_copy); + return rc; +} + +/*! Resolve an DF NAME (AID) to FID by querying the DF NAME to FID translation file. + * \param[inout] path path to the current directory. + * \param[in] df_name DF NAME to look for. + * \param[in] df_name_len length of the DF NAME. + * \returns 0 success, -EINVAL on failure */ +int ss_df_name_resolve(struct ss_list *path, const uint8_t *df_name, + size_t df_name_len) +{ + struct ss_list path_copy; + int rc; + uint32_t fid; + struct ss_buf *record; + struct ss_file *file; + size_t record_number; + uint8_t template[16 + 2]; + uint8_t mask[16 + 2]; + + /*! NOTE: This function will always search in the currently selected DF + * or ADF for the SFI to FID translation file */ + + /* A DF_name can only be max. 16 bytes long */ + if (df_name_len > 16) + return -EINVAL; + + /* ensure we won't work on an uninitialized list */ + ss_list_init(&path_copy); + + /* select lookup file */ + rc = ss_fs_utils_path_clone(&path_copy, path); + if (rc < 0) + return -EINVAL; + rc = ss_fs_select(&path_copy, DF_NAME_FID); + if (rc < 0) { + SS_LOGP(SDFNAME, LERROR, + "cannot resolve DF_NAME=%s, unable to select lookup file in %s\n", + ss_hexdump(df_name, df_name_len), + ss_fs_utils_dump_path(&path_copy)); + rc = -EINVAL; + goto leave; + } + + /* search for DF_NAME in file to find related FID */ + file = ss_get_file_from_path(&path_copy); + if (!file) { + rc = -EINVAL; + goto leave; + } + + memcpy(template, df_name, df_name_len); + memset(mask, 0x00, sizeof(mask)); + memset(mask, 0xff, df_name_len); + + record_number = + ss_fs_utils_find_record(&path_copy, template, mask, + sizeof(template)); + if (!record_number) { + SS_LOGP(SDFNAME, LERROR, + "unable to resolve DF_NAME=%s to FID - lookup file %s has no matching record\n", + ss_hexdump(df_name, df_name_len), + ss_fs_utils_dump_path(&path_copy)); + rc = -EINVAL; + goto leave; + } + + record = ss_fs_read_file_record(&path_copy, record_number); + if (!record) { + SS_LOGP(SDFNAME, LERROR, + "unable to resolve DF_NAME=%s to FID - lookup file %s is not readable at record number %lu\n", + ss_hexdump(df_name, df_name_len), + ss_fs_utils_dump_path(&path_copy), record_number); + rc = -EINVAL; + goto leave; + } + + fid = ss_uint32_from_array(record->data + 16, 2); + SS_LOGP(SDFNAME, LDEBUG, + "resolved DF_NAME=%s to FID=%04x using lookup file %s\n", + ss_hexdump(df_name, df_name_len), fid, + ss_fs_utils_dump_path(&path_copy)); + ss_buf_free(record); + rc = fid; + +leave: + ss_path_reset(&path_copy); + return rc; +} diff --git a/src/softsim/uicc/df_name.h b/src/softsim/uicc/df_name.h new file mode 100644 index 0000000..ad189c7 --- /dev/null +++ b/src/softsim/uicc/df_name.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +struct ss_list; + +int ss_df_name_update(struct ss_list *path); +int ss_df_name_resolve(struct ss_list *path, const uint8_t *df_name, + size_t df_name_len); diff --git a/src/softsim/uicc/fcp.c b/src/softsim/uicc/fcp.c new file mode 100644 index 0000000..bb2adfb --- /dev/null +++ b/src/softsim/uicc/fcp.c @@ -0,0 +1,452 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include +#include +#include "btlv.h" +#include "fcp.h" + +const struct ber_tlv_desc bertlv_tree_descr[] = { + { + .id = 1, + .id_parent = 0, + .title = "fcp_template", + .tag_encoded = 0x62, + }, + { + .id = 2, + .id_parent = 1, + .title = "file_descriptor", + .tag_encoded = 0x82, + }, + { + .id = 2, + .id_parent = 1, + .title = "DF_name", + .tag_encoded = 0x84, + }, + { + .id = 3, + .id_parent = 1, + .title = "file_identifier", + .tag_encoded = 0x83, + }, + { + .id = 4, + .id_parent = 1, + .title = "proprietary_info", + .tag_encoded = 0xA5, + }, + { + .id = 4, + .id_parent = 4, + .title = "uicc_characteristics", + .tag_encoded = 0x80, + }, + { + .id = 4, + .id_parent = 4, + .title = "application_power_consumption", + .tag_encoded = 0x81, + }, + { + .id = 4, + .id_parent = 4, + .title = "minimum_app_clock_freq", + .tag_encoded = 0x82, + }, + { + .id = 4, + .id_parent = 4, + .title = "available_memory", + .tag_encoded = 0x83, + }, + { + .id = 4, + .id_parent = 4, + .title = "file_details", + .tag_encoded = 0x84, + }, + { + .id = 4, + .id_parent = 4, + .title = "reserved_file_size", + .tag_encoded = 0x85, + }, + { + .id = 4, + .id_parent = 4, + .title = "maximum_file_size", + .tag_encoded = 0x86, + }, + { + .id = 4, + .id_parent = 4, + .title = "suported_system_commands", + .tag_encoded = 0x87, + }, + { + .id = 4, + .id_parent = 4, + .title = "specific_uicc_env_cond", + .tag_encoded = 0x88, + }, + { + .id = 4, + .id_parent = 4, + .title = "p2p_cat_secured_apdu", + .tag_encoded = 0x89, + }, + { + .id = 4, + .id_parent = 1, + .title = "life_cycle_status_int", + .tag_encoded = 0x8A, + }, + { + .id = 4, + .id_parent = 1, + .title = "security_attrib_ref_expanded", + .tag_encoded = 0xAB, + }, + { + .id = 4, + .id_parent = 1, + .title = "security_attrib_compact", + .tag_encoded = 0x8C, + }, + { + .id = 4, + .id_parent = 1, + .title = "security_attrib_expanded", + .tag_encoded = 0x8B, + }, + { + .id = 4, + .id_parent = 1, + .title = "pin_status_template_do", + .tag_encoded = 0xC6, + }, + { + .id = 4, + .id_parent = 1, + .title = "file_size", + .tag_encoded = 0x80, + }, + { + .id = 4, + .id_parent = 1, + .title = "total_file_size", + .tag_encoded = 0x81, + }, + { + .id = 4, + .id_parent = 1, + .title = "short_file_id", + .tag_encoded = 0x88, + }, + { + .id = 0, + } +}; + +/*! Get a btlv description with the most important FCP information elements. + * \returns description for use with ss_btlv_decode(). */ +const struct ber_tlv_desc *ss_fcp_get_descr(void) +{ + return bertlv_tree_descr; +} + +/*! Decode file FCP (file control parameter). + * \param[in] pointer to ss_buf object containing the encoded FCP. + * \returns decoded FCP on success, NULL on failure. */ +struct ss_list *ss_fcp_decode(const struct ss_buf *fcp) +{ + struct ss_list *decoded_fcp; + decoded_fcp = ss_btlv_decode(fcp->data, fcp->len, ss_fcp_get_descr()); + return decoded_fcp; +} + +/*! Decode file descriptor. + * \param[out] user provided memory to store parsed file descriptor. + * \param[in] encoded representation of the file descriptor. + * \returns 0 on success -EINVAL on failure. */ +int ss_fcp_dec_file_descr(struct ss_fcp_file_descr *fd, + const struct ss_buf *fd_encoded) +{ + uint8_t fd_byte; + + memset(fd, 0, sizeof(*fd)); + + /* A file descriptor is at least 2 bytes long. It consists of the file + * descriptor byte and a data coding byte. Both are mandatory, even + * though the data coding byte is always 0x21 and ignored by the + * terminal */ + if (fd_encoded->len < 2) + return -EINVAL; + + /* See also: ETSI TS 102 221, Table 11.5 */ + fd_byte = fd_encoded->data[0]; + + /* Sharable file ? */ + fd->shareable = (fd_byte >> 7) & 1; + + /* File type */ + fd->type = (fd_byte >> 3) & 0x07; + + /* Structure */ + if ((fd_byte & 0xbf) == 0x39) + fd->structure = SS_FCP_BTLV; + else { + fd->structure = fd_byte & 0x07; + } + + /* See also: ETSI TS 102 221, Section 11.1.1.4.3 */ + if (fd->structure == SS_FCP_LINEAR_FIXED || + fd->structure == SS_FCP_CYCLIC) { + if (fd_encoded->len < 5) + return -EINVAL; + fd->record_len = fd_encoded->data[2] << 8; + fd->record_len |= fd_encoded->data[3]; + fd->number_of_records = fd_encoded->data[4]; + } + + return 0; +} + +/*! Generate file descriptor. + * \param[in] fd user provided memory with file descriptor struct. + * \returns buffer with generated file descriptor on success NULL on failure. */ +struct ss_buf *ss_fcp_gen_file_descr(const struct ss_fcp_file_descr *fd) +{ + struct ss_buf *result; + + if (fd->structure == SS_FCP_LINEAR_FIXED || + fd->structure == SS_FCP_CYCLIC) { + result = ss_buf_alloc(5); + memset(result->data, 0, 5); + } else { + result = ss_buf_alloc(2); + memset(result->data, 0, 2); + } + + if (fd->shareable) + result->data[0] |= 0x40; + + /* See also: ETSI TS 102 221, Table 11.5 */ + result->data[0] |= fd->type << 3; + result->data[0] |= fd->structure; + + /* Data coding byte */ + result->data[1] = 0x21; + + /* Record oriented file */ + if (fd->structure == SS_FCP_LINEAR_FIXED || + fd->structure == SS_FCP_CYCLIC) { + result->data[2] = fd->record_len >> 8; + result->data[3] = fd->record_len & 0xff; + result->data[4] = fd->number_of_records; + } + + return result; +} + +/*! Generate an FCP template to be used with internal files. + * \param[in] fd user provided memory with file descriptor struct. + * \param[in] fid file ID. + * \param[in] file_size size of the file, if not already specified in fd. + * \returns buffer with generated file fcp on success NULL on failure. */ +struct ss_buf *ss_fcp_gen(const struct ss_fcp_file_descr *fd, uint32_t fid, + size_t file_size) +{ + /* NOTE: This file generates a file control parameter template (FCP) + * that fullfills minimal requirements. The function is intended to + * be used when creating internal (hidden) files. (When creating files + * using the CREATE FILE command, the FCP template is provided from + * outside.) */ + + struct ss_list *fcp; + struct ss_buf *fd_encoded; + struct ber_tlv_ie *fcp_template; + uint8_t fid_array[4]; + size_t fid_len; + uint8_t file_size_array[4]; + size_t file_size_len; + struct ss_buf *result; + + fcp = SS_ALLOC(struct ss_list); + ss_list_init(fcp); + + fcp_template = ss_btlv_new_ie_constr(fcp, "fcp", 0x62); + + /* File descriptor */ + fd_encoded = ss_fcp_gen_file_descr(fd); + if (!fd_encoded) + return NULL; + ss_btlv_new_ie(fcp_template->nested, "file_descriptor", 0x82, + fd_encoded->len, fd_encoded->data); + ss_buf_free(fd_encoded); + + /* FID */ + fid_len = 2; + if (fid > 0xffff) + fid_len = 4; + ss_array_from_uint32(fid_array, fid_len, fid); + ss_btlv_new_ie(fcp_template->nested, "file_identifier", 0x83, fid_len, + fid_array); + + /* File size */ + if (fd->structure == SS_FCP_LINEAR_FIXED || + fd->structure == SS_FCP_CYCLIC) { + file_size = fd->record_len * fd->number_of_records; + } + file_size_len = ss_optimal_len_for_uint32(file_size); + ss_array_from_uint32(file_size_array, file_size_len, file_size); + ss_btlv_new_ie(fcp_template->nested, "file_size", 0x80, file_size_len, + file_size_array); + + result = ss_btlv_encode_to_ss_buf(fcp); + ss_btlv_free(fcp); + + return result; +} + +/*! (Re)-encode the encoded FCP string of a file. + * \param[inout] path path to the file. + * \returns 0 on success -EINVAL on failure. */ +int ss_fcp_reencode(struct ss_file *file) +{ + struct ss_list *fcp; + struct ber_tlv_ie *fcp_template; + struct ss_buf *fci; + int rc = 0; + + /* Generate an FCP template envelope. We have to do that because the + * FCP/FCI is stored without the envelope for easier access. */ + fcp = SS_ALLOC(struct ss_list); + ss_list_init(fcp); + fcp_template = ss_btlv_new_ie_constr(fcp, "fcp-template", 0x62); + + /* Temporarly attach the FCP to the envelope and re-encode it. */ + ss_btlv_attach_to_constr(fcp_template, file->fcp_decoded); + + fci = ss_btlv_encode_to_ss_buf(fcp); + if (!fci) { + rc = -EINVAL; + goto leave; + } + + /* Exchange old fci data with re-encoded fci data */ + if (file->fci) + ss_buf_free(file->fci); + file->fci = fci; + +leave: + /* Split the FCI away from the envelope and free the envelope part we + * have allocated above. */ + ss_btlv_split_off_from_constr(fcp_template); + ss_btlv_free(fcp); + return rc; +} + +/*! Get DF name from a decoded FCP. + * \param[in] fcp_decoded_envelope representation of the file descriptor. + * \returns buffer with DF name on success NULL on failure. */ +struct ss_buf *ss_fcp_get_df_name(const struct ss_list *fcp_decoded_envelope) +{ + struct ber_tlv_ie *fcp_df_name_ie; + + /*! The returned buffer is the value part of the IE in the TLV tree. + * the caller must not take ownership of the buffer (free it) */ + + /* Extract DF Name (if present) */ + fcp_df_name_ie = ss_btlv_get_ie_minlen(fcp_decoded_envelope, + TS_102_221_IEI_FCP_DF_NAME, 1); + if (!fcp_df_name_ie) + return NULL; + + return fcp_df_name_ie->value; +} + +/*! Dump decoded file descriptor. + * \param[in] fd user provided memory with file descriptor struct. + * \param[in] indent indentation level of the generated output. + * \param[in] log_subsys log subsystem to generate the output for. + * \param[in] log_level log level to generate the output for. */ +void ss_fcp_dump_file_descr(const struct ss_fcp_file_descr *fd, uint8_t indent, + enum log_subsys log_subsys, + enum log_level log_level) +{ + char indent_str[256]; + + memset(indent_str, ' ', indent); + indent_str[indent] = '\0'; + + SS_LOGP(log_subsys, log_level, "%sshareable = %s\n", indent_str, + fd->shareable ? "true" : "false"); + + switch (fd->type) { + case SS_FCP_WORKING_EF: + SS_LOGP(log_subsys, log_level, "%stype = \"working EF\"\n", + indent_str); + break; + case SS_FCP_INTERNAL_EF: + SS_LOGP(log_subsys, log_level, "%stype = \"internal EF\"\n", + indent_str); + break; + case SS_FCP_DF_OR_ADF: + SS_LOGP(log_subsys, log_level, "%stype = \"DF or ADF\"\n", + indent_str); + break; + default: + SS_LOGP(log_subsys, log_level, "%stype = %x\n", indent_str, + fd->type); + break; + } + + switch (fd->structure) { + case SS_FCP_UNKNOWN: + SS_LOGP(log_subsys, log_level, "%sstructure = \"unknown\"\n", + indent_str); + break; + case SS_FCP_TRANSPARENT: + SS_LOGP(log_subsys, log_level, + "%sstructure = \"transparent\"\n", indent_str); + break; + case SS_FCP_LINEAR_FIXED: + SS_LOGP(log_subsys, log_level, + "%sstructure = \"linear fixed\"\n", indent_str); + break; + case SS_FCP_CYCLIC: + SS_LOGP(log_subsys, log_level, "%sstructure = \"cyclic\"\n", + indent_str); + break; + case SS_FCP_BTLV: + SS_LOGP(log_subsys, log_level, "%sstructure = \"BTLV\"\n", + indent_str); + break; + default: + SS_LOGP(log_subsys, log_level, "%sstructure = %x\n", indent_str, + fd->structure); + break; + } + + if (fd->structure == SS_FCP_LINEAR_FIXED + || fd->structure == SS_FCP_CYCLIC) { + SS_LOGP(log_subsys, log_level, "%srecord_len = %u\n", + indent_str, fd->record_len); + SS_LOGP(log_subsys, log_level, "%snumber_of_records = %u\n", + indent_str, fd->number_of_records); + } +} + diff --git a/src/softsim/uicc/fcp.h b/src/softsim/uicc/fcp.h new file mode 100644 index 0000000..fcb9514 --- /dev/null +++ b/src/softsim/uicc/fcp.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include +#include + +struct ss_buf; +struct ber_tlv_desc; +struct ss_file; + +enum ss_fcp_file_type { + SS_FCP_WORKING_EF = 0, + SS_FCP_INTERNAL_EF = 1, + SS_FCP_DF_OR_ADF = 7, +}; + +enum ss_fcp_file_struct { + SS_FCP_UNKNOWN = 0, + SS_FCP_TRANSPARENT = 1, + SS_FCP_LINEAR_FIXED = 2, + SS_FCP_CYCLIC = 6, + SS_FCP_BTLV = 57, +}; + +struct ss_fcp_file_descr { + bool shareable; + enum ss_fcp_file_type type; + enum ss_fcp_file_struct structure; + + /* Only valid for linear fixed and cyclic files */ + uint16_t record_len; + uint8_t number_of_records; +}; + +/* See also 11.1.1.3 ETSI TS 102 221 */ +#define TS_102_221_IEI_FCP_TMPL 0x62 +enum ss_fcp_iei { + TS_102_221_IEI_FCP_FILE_SIZE = 0x80, + TS_102_221_IEI_FCP_TOTAL_FILE_SIZE = 0x81, + TS_102_221_IEI_FCP_FILE_DESCR = 0x82, + TS_102_221_IEI_FCP_FILE_ID = 0x83, + TS_102_221_IEI_FCP_DF_NAME = 0x84, + TS_102_221_IEI_FCP_SHORT_FILE_ID = 0x88, + TS_102_221_IEI_FCP_LIFE_CYCLE_ST = 0x8A, + TS_102_221_IEI_FCP_SEC_ATTR_8B = 0x8B, + TS_102_221_IEI_FCP_SEC_ATTR_8C = 0x8C, + TS_102_221_IEI_FCP_SEC_ATTR_AB = 0xAB, + TS_102_221_IEI_FCP_PIN_STAT_TMPL = 0xC6, +}; + +const struct ber_tlv_desc *ss_fcp_get_descr(void); +struct ss_list *ss_fcp_decode(const struct ss_buf *fcp); +int ss_fcp_dec_file_descr(struct ss_fcp_file_descr *fd, + const struct ss_buf *fd_encoded); +struct ss_buf *ss_fcp_gen_file_descr(const struct ss_fcp_file_descr *fd); +struct ss_buf *ss_fcp_gen(const struct ss_fcp_file_descr *fd, uint32_t fid, + size_t file_size); +struct ss_buf *ss_fcp_get_df_name(const struct ss_list *fcp_decoded_envelope); +void ss_fcp_dump_file_descr(const struct ss_fcp_file_descr *fd, uint8_t indent, + enum log_subsys log_subsys, + enum log_level log_level); +int ss_fcp_reencode(struct ss_file *file); diff --git a/src/softsim/uicc/file.c b/src/softsim/uicc/file.c new file mode 100644 index 0000000..15e763e --- /dev/null +++ b/src/softsim/uicc/file.c @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include + +/*! Get file struct from a path (tip of the path). + * \param[in] path path to the file. + * \returns file on success, NULL on failure. */ +struct ss_file *ss_get_file_from_path(const struct ss_list *path) +{ + if (ss_list_empty(path)) + return NULL; + return SS_LIST_GET(path->previous, struct ss_file, list); +} + +/*! Get parent file struct from a path (tip of the path minus one file). + * \param[in] path path to the file. + * \returns file on success, NULL on failure. */ +struct ss_file *ss_get_parent_file_from_path(const struct ss_list *path) +{ + if (ss_list_empty(path)) + return NULL; + + /* When the path is only one file long, we end up at the beginning of + * the list again, which means there is no parent we could return. */ + if (path->previous->previous == path) + return NULL; + + return SS_LIST_GET(path->previous->previous, struct ss_file, list); +} diff --git a/src/softsim/uicc/fs.c b/src/softsim/uicc/fs.c new file mode 100644 index 0000000..956fba5 --- /dev/null +++ b/src/softsim/uicc/fs.c @@ -0,0 +1,451 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "btlv.h" +#include "fs.h" +#include "fs_utils.h" +#include "fcp.h" +#include "access.h" +#define FILE_MF 0x3f00 + +/* Allocate a new file struct and extend the current file path */ +static struct ss_file *path_add(struct ss_list *path, uint32_t fid) +{ + struct ss_file *file; + + file = SS_ALLOC(struct ss_file); + memset(file, 0, sizeof(*file)); + file->fid = fid; + ss_list_put(path, &file->list); + + return file; +} + +/* Free a file struct and its contents. */ +void file_free(struct ss_file *file) +{ + if (!file) + return; + ss_buf_free(file->fci); + ss_btlv_free(file->fcp_decoded); + SS_FREE(file->fcp_file_descr); + ss_btlv_free(file->access); + SS_FREE(file); +} + +/* Remove last entry from the current path (when another file in the same DF is + * selected) + * + * Note that while being a "select" call, this does not populate the file's + * indirect details -- but if the file was previously accessed through \ref + * ss_fs_select, they will also be present after calling this function. + * */ +int ss_fs_select_parent(const struct ss_list *path) +{ + struct ss_file *file; + + if (ss_list_empty(path)) + return -EINVAL; + + file = SS_LIST_GET(path->previous, struct ss_file, list); + ss_list_remove(&file->list); + file_free(file); + + /* There must be still a file (parent) in the path after we have + * selected the parent! */ + if (ss_list_empty(path)) + return -EINVAL; + + return 0; +} + +/*! Clear path completely (when MF is selected). + * \param[inout] path path to reset. */ +void ss_path_reset(struct ss_list *path) +{ + /*! NOTE: This is essentially freeing all items from the path list. + * this function can be used to free copies made with ss_fs_utils_path_clone() */ + + int rc; + do { + rc = ss_fs_select_parent(path); + } while (rc == 0); +} + +static struct ss_buf *file_descr_from_fcp(const struct ss_list *fcp_decoded_envelope) +{ + struct ber_tlv_ie *fcp_decoded_file_descr; + + if (!fcp_decoded_envelope) + return NULL; + fcp_decoded_file_descr = + ss_btlv_get_ie_minlen(fcp_decoded_envelope, 0x82, 2); + if (!fcp_decoded_file_descr) + return NULL; + + /* NOTE: The caller must not take ownership of the value */ + return fcp_decoded_file_descr->value; +} + +/** + * Find a file relative to a given one, and read a record + * + * This implements the rules for location of the access rules of TS 102 221 V15 + * section 9.2.7, but is phrased in the general terms of file system access. + * + * \pre file_path contains some file. + * + * \pre record_number != 0 + * + * \return a buffer containing the full record (or NULL if no file was found / + * the indicated record was not present) + */ +struct ss_buf *ss_fs_read_relative_file_record(const struct ss_list *path, + uint16_t file_id, + uint8_t record_number) +{ + struct ss_list efarr_path; + if (ss_fs_utils_path_clone(&efarr_path, path) != 0) { + SS_LOGP(SFS, LERROR, "Insufficient memory to discover file.\n"); + return NULL; + } + + /* ADFs are specified to look up their EF.ARR in the master file */ + if (ss_get_file_from_path(path)->aid != NULL) { + while (ss_get_file_from_path(path)->fid != FILE_MF) + ss_fs_select_parent(&efarr_path); + } + + /* selecting the indicated file_id in a DF would produce an EF.ARR in + * the selected directory -- but TS 102 221 V15.0.0 Section 9.2.7, + * second list, second bullet tells us to look above. + * + * The MF is excluded from that rule for obvious reasons. + * */ + if (ss_get_file_from_path(path)->fcp_file_descr->type == SS_FCP_DF_OR_ADF && + ss_get_file_from_path(path)->fid != FILE_MF + ) + ss_fs_select_parent(&efarr_path); + + int select_rc = -1; + while (!ss_list_empty(&efarr_path)) { + select_rc = ss_fs_select(&efarr_path, file_id); + if (select_rc == 0) + break; + ss_fs_select_parent(&efarr_path); + } + if (select_rc != 0) { + SS_LOGP(SFS, LERROR, "Reference pointed to nonexistent file\n"); + return NULL; + } + + struct ss_buf *result = ss_fs_read_file_record(&efarr_path, record_number); + + ss_path_reset(&efarr_path); + return result; +} + +/*! Select a file from the file system + * + * \param[inout] path File path to manipulate. + * \param[in] fid file ID to select. + * + * \returns zero on success, or an error number + * + * \post On success, there is a file selected in @p path. + * + */ +int ss_fs_select(struct ss_list *path, uint32_t fid) +{ + int rc; + struct ss_file *selected_file; + struct ss_buf *file_descr; + + /* Selection of the MF always resets the path */ + if (fid == FILE_MF) + ss_path_reset(path); + + /* When the currently selected file is an EF, then we must remove this + * file first. */ + selected_file = ss_get_file_from_path(path); + if (selected_file) { + if (selected_file->fcp_file_descr->type != SS_FCP_DF_OR_ADF) + ss_fs_select_parent(path); + } + + /* Add a new file to the path */ + selected_file = path_add(path, fid); + + /* Read the file defintion from storage */ + rc = ss_storage_get_file_def(path); + if (rc < 0) { + ss_fs_select_parent(path); + SS_LOGP(SFS, LINFO, "select fid=%04x failed, path=%s\n", fid, + ss_fs_utils_dump_path(path)); + return -EINVAL; + } + + /* Store parsed representation of the FCP */ + selected_file->fcp_decoded = NULL; + struct ss_list *fci_decoded = ss_fcp_decode(selected_file->fci); + struct ber_tlv_ie *fcp_decoded_envelope = NULL; + + if (fci_decoded) + fcp_decoded_envelope = ss_btlv_get_ie(fci_decoded, TS_102_221_IEI_FCP_TMPL); + if (fcp_decoded_envelope) { + selected_file->fcp_decoded = fcp_decoded_envelope->nested; + /* We're moved responsibility for freeing the nested elements to the + * file; breaking the link allows easy cleanup. */ + fcp_decoded_envelope->nested = NULL; + ss_btlv_free(fci_decoded); + } + + if (!selected_file->fcp_decoded) { + ss_fs_select_parent(path); + SS_LOGP(SBTLV, LERROR, "select fid=%04x failed, path=%s, unable to decode FCP\n", + fid, ss_fs_utils_dump_path(path)); + return -EINVAL; + } + + file_descr = file_descr_from_fcp(selected_file->fcp_decoded); + if (!file_descr) { + ss_fs_select_parent(path); + SS_LOGP(SBTLV, LERROR, "select fid=%04x failed, path=%s, unable to decode FD\n", + fid, ss_fs_utils_dump_path(path)); + } + + selected_file->fcp_file_descr = SS_ALLOC(struct ss_fcp_file_descr); + ss_fcp_dec_file_descr(selected_file->fcp_file_descr, + file_descr); + return 0; +} + +/*! Read record from file (tip of the path). + * \param[in] path path to the file to be read. + * \param[in] record_no number of the record to be read. + * \returns buffer with record data on success, NULL on failure */ +struct ss_buf *ss_fs_read_file_record(const struct ss_list *path, + size_t record_no) +{ + struct ss_file *file; + + file = ss_get_file_from_path(path); + if (!file) + return NULL; + + /* Reacord number 0 always points to the current record. The caller + * has to maintain this state and then call this function with the + * absolue record number. */ + if (record_no == 0) { + SS_LOGP(SFS, LINFO, + "non existing record (%lu) referenced in file (%04x)\n", + record_no, file->fid); + return NULL; + } + + if (record_no > file->fcp_file_descr->number_of_records + 1) { + SS_LOGP(SFS, LINFO, + "non existing record (%lu) referenced in file (%04x)\n", + record_no, file->fid); + return NULL; + } + + if (file->fcp_file_descr->structure != SS_FCP_LINEAR_FIXED + && file->fcp_file_descr->structure != SS_FCP_CYCLIC) { + SS_LOGP(SFS, LINFO, + "cannot read record from non record oriented file (%04x)\n", + file->fid); + return NULL; + } + + return ss_storage_read_file(path, (record_no - 1) * + file->fcp_file_descr->record_len, + file->fcp_file_descr->record_len); +} + +/*! Write record to file (tip of the path). + * \param[in] path path to the file to write. + * \param[in] record_no number of the record to write. + * \param[in] data user provided memory with record data. + * \param[in] data record data length (checked against FCP). + * \returns 0 on success, -EINVAL on failure */ +int ss_fs_write_file_record(const struct ss_list *path, size_t record_no, + const uint8_t *data, size_t len) +{ + struct ss_file *file; + + file = ss_get_file_from_path(path); + if (!file) + return -EINVAL; + + /* See also note in ss_fs_get_file_record() */ + if (record_no == 0) { + SS_LOGP(SFS, LINFO, + "non existing record (%lu) referenced in file (%04x)\n", + record_no, file->fid); + return -EINVAL; + } + + if (record_no > file->fcp_file_descr->number_of_records + 1) { + SS_LOGP(SFS, LINFO, + "non existing record (%lu) referenced in file (%04x), file has %u records\n", + record_no, file->fid, file->fcp_file_descr->number_of_records); + return -EINVAL; + } + + if (file->fcp_file_descr->structure != SS_FCP_LINEAR_FIXED + && file->fcp_file_descr->structure != SS_FCP_CYCLIC) { + SS_LOGP(SFS, LINFO, + "cannot write record on non record oriented file (%04x)\n", + file->fid); + return -EINVAL; + } + + if (file->fcp_file_descr->record_len != len) { + SS_LOGP(SFS, LINFO, + "cannot write record with improper length (%u != %lu) to file (%04x)\n", + file->fcp_file_descr->record_len, len, file->fid); + return -EINVAL; + } + + return ss_storage_write_file(path, data, (record_no - 1) * + file->fcp_file_descr->record_len, + file->fcp_file_descr->record_len); +} + +/*! Initialize filesystem. + * \param[inout] path to initialize. */ +void ss_fs_init(struct ss_list *path) +{ + ss_list_init(path); + + /* Make sure the MF is selected on startup */ + ss_fs_select(path, FILE_MF); +} + +/*! Create a file or directory on the file system. + * \param[inout] path directory where the file or directory shall be created. + * \param[in] fci file control information for the file or directory to + * create, in particular containing FCP (file control parameters). + * \returns 0 success, -EINVAL on failure */ +int ss_fs_create(struct ss_list *path, const uint8_t *fci_data, size_t fci_len) +{ + /*! NOTE: This function will only create the file in the file system + * along with a vaild definition file. It will not write ARR or SFID + * entries or select the file. Those tasks have to be done in seperate + * steps by the caller. */ + + int rc = 0; + struct ss_file *file; + struct ss_list *fci_decoded; + struct ber_tlv_ie *fcp_tmpl_ie; + struct ber_tlv_ie *fcp_fid_ie; + struct ber_tlv_ie *fcp_file_size_ie; + struct ber_tlv_ie *fcp_file_descr_ie; + + uint32_t fid; + uint32_t size; + struct ss_fcp_file_descr file_descr; + struct ss_buf *fci; + + /* Decode FCP template (envelope) */ + fci_decoded = ss_btlv_decode(fci_data, fci_len, NULL); + if (!fci_decoded) { + SS_LOGP(SFS, LERROR, "Failed to decode BTLV in file creation\n"); + return -EINVAL; + } + fcp_tmpl_ie = ss_btlv_get_ie(fci_decoded, TS_102_221_IEI_FCP_TMPL); + if (!fcp_tmpl_ie) { + SS_LOGP(SFS, LERROR, "Missing FCP TMPL in file creation\n"); + rc = -EINVAL; + goto leave; + } + + /* Decode FID */ + fcp_fid_ie = + ss_btlv_get_ie_minlen(fcp_tmpl_ie->nested, + TS_102_221_IEI_FCP_FILE_ID, 2); + if (!fcp_fid_ie) { + SS_LOGP(SFS, LERROR, "Missing FID IE in file creation\n"); + rc = -EINVAL; + goto leave; + } + fid = + ss_uint32_from_array(fcp_fid_ie->value->data, + fcp_fid_ie->value->len); + + /* Decode File descriptor */ + fcp_file_descr_ie = + ss_btlv_get_ie_minlen(fcp_tmpl_ie->nested, + TS_102_221_IEI_FCP_FILE_DESCR, 2); + if (!fcp_file_descr_ie) { + SS_LOGP(SFS, LERROR, "Missing file descriptor in file creation\n"); + rc = -EINVAL; + goto leave; + } + if (ss_fcp_dec_file_descr(&file_descr, fcp_file_descr_ie->value) < 0) { + SS_LOGP(SFS, LERROR, "Error decoding file descriptor file creation\n"); + rc = -EINVAL; + goto leave; + } + + /* Make sure that the supplied path points to a DF or ADF. */ + file = ss_get_file_from_path(path); + if (file && file->fcp_file_descr->type != SS_FCP_DF_OR_ADF) { + ss_fs_select_parent(path); + } + + /* Add a file with the supplied FCP to the path. This must not be + * confused with a proper select. We just fullfill the bare minimum + * requirements that the storage layer will accept */ + file = path_add(path, fid); + + /* Note: When the file is removed from the path again (see below), + * the fci we allocate here is freed. */ + fci = ss_buf_alloc_and_cpy(fci_data, fci_len); + file->fci = fci; + + if (file_descr.type == SS_FCP_DF_OR_ADF) + rc = ss_storage_create_dir(path); + else { + /* Decode file size */ + fcp_file_size_ie = + ss_btlv_get_ie_minlen(fcp_tmpl_ie->nested, + TS_102_221_IEI_FCP_FILE_SIZE, 1); + if (!fcp_file_size_ie) { + SS_LOGP(SFS, LERROR, "Missing file size file creation\n"); + rc = -EINVAL; + /* Remove file from the path and leave */ + ss_fs_select_parent(path); + goto leave; + } + size = + ss_uint32_from_array(fcp_file_size_ie->value->data, + fcp_file_size_ie->value->len); + rc = ss_storage_create_file(path, size); + } + + /* Remove the file from the path again */ + ss_fs_select_parent(path); + +leave: + ss_btlv_free(fci_decoded); + return rc; +} diff --git a/src/softsim/uicc/fs.h b/src/softsim/uicc/fs.h new file mode 100644 index 0000000..116d160 --- /dev/null +++ b/src/softsim/uicc/fs.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +struct ss_file; +struct ss_list; + +int ss_fs_select(struct ss_list *path, uint32_t fid); +void ss_fs_init(struct ss_list *path); +struct ss_buf *ss_fs_read_file_record(const struct ss_list *path, + size_t record_no); +int ss_fs_write_file_record(const struct ss_list *path, size_t record_no, + const uint8_t *data, size_t len); +int ss_fs_create(struct ss_list *path, const uint8_t *fcp_data, size_t fcp_len); +int ss_fs_select_parent(const struct ss_list *fs_path); +void ss_path_reset(struct ss_list *path); +struct ss_buf *ss_fs_read_relative_file_record(const struct ss_list *path, + uint16_t file_id, + uint8_t record_number); diff --git a/src/softsim/uicc/fs_chg.c b/src/softsim/uicc/fs_chg.c new file mode 100644 index 0000000..a033a34 --- /dev/null +++ b/src/softsim/uicc/fs_chg.c @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fs_chg.h" + +struct ss_file_blacklist { + size_t path_len; + char path[SS_FS_CHG_PATH_MAXLEN]; +}; + +/* Some files serve special internal purposes even though they have 16 bit file + * identifiers. We don't want the terminal to be notified about file changes + * in those files even when there is a file change */ +const static struct ss_file_blacklist blacklist[] = { + {.path_len = 4,.path = "\x3F\x00\xA0\x01" }, + {.path_len = 4,.path = "\x3F\x00\xA0\x02" }, + {.path_len = 4,.path = "\x3F\x00\xA0\x03" }, + {.path_len = 4,.path = "\x3F\x00\xA0\x04" }, +}; + +/* Check if a path is blacklisted */ +static bool path_in_blacklist(const uint8_t *path, size_t path_len) +{ + size_t i; + + for (i = 0; i < SS_ARRAY_SIZE(blacklist); i++) { + if (blacklist[i].path_len != path_len) + continue; + if (memcmp(blacklist[i].path, path, path_len) == 0) + return true; + } + + return false; +} + +/* See also ETSI TS 102 223, section 8.18 */ +static int pack_path(uint8_t result[SS_FS_CHG_PATH_MAXLEN], const struct ss_list *path) +{ + struct ss_file *path_cursor; + uint8_t *result_ptr = result; + + if (ss_list_empty(path)) + return -EINVAL; + + memset(result, 0, SS_FS_CHG_PATH_MAXLEN); + + /* A FID consists of two bytes, so we expect a buffer size that is + * divisible by 2 */ + assert(SS_FS_CHG_PATH_MAXLEN % 2 == 0); + + SS_LIST_FOR_EACH(path, path_cursor, struct ss_file, list) { + /* Exclude internal files */ + if (path_cursor->fid > 0xFFFF) + return -EINVAL; + + /* Record path */ + if (result_ptr - result > SS_FS_CHG_PATH_MAXLEN) + return -EINVAL; + *result_ptr = (path_cursor->fid >> 8) & 0xff; + result_ptr++; + *result_ptr = path_cursor->fid & 0xff; + result_ptr++; + } + + if (result_ptr - result + 2 > SS_FS_CHG_PATH_MAXLEN) + return -EINVAL; + + /* Append 0x3f00 at the end of the path. This ending serves as a + * termination and delimeter symbol */ + *result_ptr = 0x3F; + result_ptr++; + *result_ptr = 0x00; + result_ptr++; + + return result_ptr - result; +} + +/*! Dump filelist contents. + * \param[in] filelist buffer with list of file pathes (ETSI TS 102 223, section 8.18) + * \param[in] indent indentation level of the generated output. + * \param[in] log_subsys log subsystem to generate the output for. + * \param[in] log_level log level to generate the output for. */ +void ss_fs_chg_dump(const uint8_t filelist[SS_FS_CHG_BUF_SIZE], uint8_t indent, + enum log_subsys subsys, enum log_level level) +{ + unsigned int i; + const uint8_t *files = filelist; + uint16_t fid; + char path_prn[SS_FS_CHG_PATH_MAXLEN * 2 + SS_FS_CHG_PATH_MAXLEN / 2 + + 1]; + char *path_prn_ptr; + char indent_str[256]; + + memset(indent_str, ' ', indent); + indent_str[indent] = '\0'; + + /* Advance to the end of the list */ + files++; + for (i = 0; i < filelist[0]; i++) { + path_prn_ptr = path_prn; + + /* Make sure the path starts with MF */ + fid = *files << 8; + files++; + fid |= *files; + files++; + if ((fid & 0xFF00) != 0x3F00) { + SS_LOGP(subsys, level, + "Filelist invalid, path does not begin with 0x3FXX (MF)\n"); + return; + } + + do { + snprintf(path_prn_ptr, + sizeof(path_prn) - (path_prn_ptr - path_prn), + "/%04x", fid); + path_prn_ptr += 5; + + fid = *files << 8; + files++; + if (files - filelist > SS_FS_CHG_BUF_SIZE) { + SS_LOGP(subsys, level, + "Filelist invalid, end of path not detected!"); + return; + } + fid |= *files; + files++; + if (files - filelist > SS_FS_CHG_BUF_SIZE) { + SS_LOGP(subsys, level, + "Filelist invalid, end of path not detected!"); + return; + } + + } while ((fid & 0xFF00) != 0x3F00); + + SS_LOGP(subsys, level, "%s%s\n", indent_str, path_prn); + + files -= 2; + } +} + +/*! Add path to file list. + * \param[in] filelist buffer with list of file pathes (ETSI TS 102 223, section 8.18) + * \param[in] path path to reset. + * \returns 0 on success, 1 when the buffer is 1/2 full, -ENOMEM when the buffer is full, -EINVAL on failure. */ +int ss_fs_chg_add(uint8_t filelist[SS_FS_CHG_BUF_SIZE], const struct ss_list *path) +{ + uint8_t *files = filelist; + uint16_t fid; + unsigned int i; + size_t bytes_free; + uint8_t path_packed[SS_FS_CHG_PATH_MAXLEN]; + int path_packed_len; + bool add = true; + + /* We expect a result of at least two byte, which would be just 0x3f00 */ + path_packed_len = pack_path(path_packed, path); + if (path_packed_len < 2) + return -EINVAL; + + /* Chack wehther the file is a blacklisted file */ + if (path_in_blacklist(path_packed, path_packed_len - 2)) + add = false; + + /* skip memory location where the number of files is stored */ + files++; + + /* Advance to the end of the list */ + for (i = 0; i < filelist[0]; i++) { + + /* Check whether the file is already known */ + if (files - filelist < SS_FS_CHG_BUF_SIZE - path_packed_len + && memcmp(path_packed, files, path_packed_len) == 0) + add = false; + + /* Make sure the path starts with MF */ + fid = *files << 8; + files++; + fid |= *files; + files++; + if ((fid & 0xFF00) != 0x3F00) + return -EINVAL; + + do { + fid = *files << 8; + files++; + if (files - filelist > SS_FS_CHG_BUF_SIZE) + return -EINVAL; + fid |= *files; + files++; + if (files - filelist > SS_FS_CHG_BUF_SIZE) + return -EINVAL; + } while ((fid & 0xFF00) != 0x3F00); + files -= 2; + } + + /* Check if there is still enough memory to store the new entry, inform + * the caller when we are out of memory */ + bytes_free = SS_FS_CHG_BUF_SIZE - (files - filelist); + if (bytes_free < path_packed_len) + return -ENOMEM; + + /* Store path in list and increment number of files, but only if the + * path is not already in the list. */ + if (add) { + memcpy(files, path_packed, path_packed_len); + filelist[0]++; + } + + /* Warn caller that the memory is soon full and action must be taken + * (sending REFRESH via CAT overdue) */ + if (bytes_free < SS_FS_CHG_PATH_MAXLEN * 2) + return 1; + + return 0; +} + +/*! Get filelist length in bytes. + * \param[in] filelist buffer with list of file pathes (ETSI TS 102 223, section 8.18) + * \returns length of filelist, -EINVAL on failure. */ +int ss_fs_chg_len(const uint8_t filelist[SS_FS_CHG_BUF_SIZE]) +{ + const uint8_t *files = filelist; + uint16_t fid; + unsigned int i; + + /* skip memory location where the number of files is stored */ + files++; + + /* Advance to the end of the list */ + for (i = 0; i < filelist[0]; i++) { + /* Make sure the path starts with MF */ + fid = *files << 8; + files++; + fid |= *files; + files++; + if ((fid & 0xFF00) != 0x3F00) + return -EINVAL; + + do { + fid = *files << 8; + files++; + if (files - filelist > SS_FS_CHG_BUF_SIZE) + return -EINVAL; + fid |= *files; + files++; + if (files - filelist > SS_FS_CHG_BUF_SIZE) + return -EINVAL; + } while (fid != 0x3F00); + files -= 2; + } + + return files - filelist; +} diff --git a/src/softsim/uicc/fs_chg.h b/src/softsim/uicc/fs_chg.h new file mode 100644 index 0000000..37d9e65 --- /dev/null +++ b/src/softsim/uicc/fs_chg.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once +struct ss_list; + +#define SS_FS_CHG_PATH_MAXLEN 20 /* bytes */ +#define SS_FS_CHG_BUF_SIZE SS_FS_CHG_PATH_MAXLEN * 100 /* bytes */ + +int ss_fs_chg_add(uint8_t filelist[SS_FS_CHG_BUF_SIZE], const struct ss_list *path); +int ss_fs_chg_len(const uint8_t filelist[SS_FS_CHG_BUF_SIZE]); +void ss_fs_chg_dump(const uint8_t filelist[SS_FS_CHG_BUF_SIZE], uint8_t indent, + enum log_subsys subsys, enum log_level level); diff --git a/src/softsim/uicc/fs_utils.c b/src/softsim/uicc/fs_utils.c new file mode 100644 index 0000000..4e9b17f --- /dev/null +++ b/src/softsim/uicc/fs_utils.c @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include +#include +#include +#include "fs.h" +#include "fs_utils.h" +#include "fcp.h" +#include "btlv.h" + +/*! Dump path to a human readable string (row of selected FIDs). + * \param[in] path path to dump. + * \returns string with path in human readable form. */ +char *ss_fs_utils_dump_path(const struct ss_list *path) +{ + static char result[1024]; + static char *result_ptr; + struct ss_file *path_cursor; + int rc; + + /* Don't accept uninitialized pathes! */ + assert(path); + + if (ss_list_empty(path)) + return "(no file selected)"; + + memset(result, 0, sizeof(result)); + result_ptr = result; + + SS_LIST_FOR_EACH(path, path_cursor, struct ss_file, list) { + rc = snprintf(result_ptr, + sizeof(result) - (result_ptr - result), "/%04x", + path_cursor->fid); + result_ptr += rc; + } + + return result; +} + +/*! Find first free record in a record oriented file. + * \param[in] path path to the file to examine. + * \returns 0 on failure, record number on success. */ +size_t ss_fs_utils_find_free_record(const struct ss_list *path) +{ + size_t i; + size_t k; + struct ss_file *file; + struct ss_buf *record; + bool free; + + file = ss_get_file_from_path(path); + if (!file) + return 0; + + for (i = 0; i < file->fcp_file_descr->number_of_records; i++) { + record = ss_fs_read_file_record(path, i + 1); + if (!record) { + SS_LOGP(SFS, LERROR, + "cannot read record %lu in internal file: %s\n", + i + 1, ss_fs_utils_dump_path(path)); + return 0; + } + if (record->len == 0) { + SS_LOGP(SFS, LERROR, + "file (%s) seems to contain zero length record (bad file descriptor?)\n", + ss_fs_utils_dump_path(path)); + return 0; + } + + free = true; + for (k = 0; k < record->len; k++) { + if (record->data[k] != 0xff) { + free = false; + break; + } + } + ss_buf_free(record); + if (free) + return i + 1; + } + + /* No free record found */ + return 0; +} + +/*! Find first free record in a record oriented file. + * \param[in] path path to the file to examine. + * \param[in] template to compare against record content. + * \param[in] mask a mask to tell which bits in the template matter. + * \param[in] len length of template and mask (both must be equal in length). + * \returns 0 on failure, record number on success. */ +size_t ss_fs_utils_find_record(const struct ss_list *path, + const uint8_t *template, + const uint8_t *mask, size_t len) +{ + struct ss_file *file; + size_t i; + size_t k; + struct ss_buf *record; + bool mismatch; + + file = ss_get_file_from_path(path); + if (!file) + return 0; + + if (len > file->fcp_file_descr->record_len) + return 0; + + for (i = 0; i < file->fcp_file_descr->number_of_records; i++) { + record = ss_fs_read_file_record(path, i + 1); + if (!record) { + SS_LOGP(SFS, LERROR, + "cannot read record %lu in internal file: %s\n", + i + 1, ss_fs_utils_dump_path(path)); + return 0; + } + + mismatch = false; + for (k = 0; k < len; k++) { + if ((record->data[k] & mask[k]) != (template[k] & mask[k])) + mismatch = true; + } + + ss_buf_free(record); + + if (!mismatch) + return i + 1; + } + + return 0; +} + +/*! Create an internal record oriented file to manage internal tasks. + * \param[in] path directory where the file shall be created. + * \param[in] fid file identifier to use. + * \param[in] record_len length of the records in the file. + * \param[in] number_of_records number of records in the file. + * \returns 0 success, -EINVAL on failure */ +int ss_fs_utils_create_record_file(const struct ss_list *path, uint32_t fid, + uint16_t record_len, + uint8_t number_of_records) +{ + struct ss_buf *fcp; + struct ss_fcp_file_descr fd; + int rc; + struct ss_list path_copy; + + /*! This function must not be used to create normal files (16 bit FID), + * which are accessible from outside. */ + assert(fid > 0xffff); + + /* ensure we won't work on an uninitialized list */ + ss_list_init(&path_copy); + + /* generate FCP for interhal SFI file */ + memset(&fd, 0, sizeof(fd)); + fd.type = SS_FCP_WORKING_EF; + fd.structure = SS_FCP_LINEAR_FIXED; + fd.record_len = record_len; + fd.number_of_records = number_of_records; + fcp = ss_fcp_gen(&fd, fid, fd.record_len * fd.number_of_records); + if (!fcp) + return -EINVAL; + + /* create new internal SFI file */ + rc = ss_fs_utils_path_clone(&path_copy, path); + if (rc < 0) { + rc = -EINVAL; + goto leave; + } + rc = ss_fs_create(&path_copy, fcp->data, fcp->len); + if (rc < 0) { + SS_LOGP(SFS, LERROR, + "cannot create internal file %08x in directory: %s\n", + fid, ss_fs_utils_dump_path(path)); + rc = -EINVAL; + goto leave; + } + + SS_LOGP(SFS, LDEBUG, + "created new internal record oriented file %08x in directory: %s\n", + fid, ss_fs_utils_dump_path(path)); + rc = 0; +leave: + ss_buf_free(fcp); + ss_path_reset(&path_copy); + return 0; +} + +/*! Obtain a deep copy of a path. + * \param[out] path_copy user provided memory to store the copyied path. + * \param[in] path path that shall be copied. + * \returns 0 success, -ENOMEM on failure */ +int ss_fs_utils_path_clone(struct ss_list *path_copy, const struct ss_list *path) +{ + struct ss_file *path_cursor; + + /*! NOTE: This currently does not copy the (original and decoded) FCP data, as it is + * not needed by the function's callers -- and would be even more + * computationally expensive. */ + + ss_list_init(path_copy); + + SS_LIST_FOR_EACH(path, path_cursor, struct ss_file, list) { + struct ss_file *latest; + struct ss_fcp_file_descr *cloned_details; + + latest = SS_ALLOC(struct ss_file); + if (!latest) + goto err; + cloned_details = SS_ALLOC(struct ss_fcp_file_descr); + if (!cloned_details) { + SS_FREE(latest); + goto err; + } + memcpy(latest, path_cursor, sizeof(*path_cursor)); + latest->fcp_file_descr = memcpy(cloned_details, latest->fcp_file_descr, sizeof(struct ss_fcp_file_descr)); + latest->fci = NULL; + latest->fcp_decoded = NULL; + latest->aid = NULL; + latest->access = NULL; + latest->list.previous = NULL; + latest->list.next = NULL; + ss_list_put(path_copy, &latest->list); + } + + return 0; +err: + /* Exit point for when out_path has been initialized but needs to be + * drained because it can not be allocated in full */ + ss_path_reset(path_copy); + return -ENOMEM; +} + +/*! Select a path by using the FIDs from another path. + * \param[out] path_out user provided memory to store the output path. + * \param[in] path path that shall be used as input for select. + * \returns 0 success, -EINVAL on failure + * + * In the failure case, the output path can be incomplete, and is selected up + * to the point in the path where that was possible. + * */ +int ss_fs_utils_path_select(struct ss_list *path_out, const struct ss_list *path) +{ + struct ss_file *path_cursor; + int rc; + + /*! The output path must not contain any previously selected files. + * it must be empty! (memoy leaks) */ + ss_list_init(path_out); + + SS_LIST_FOR_EACH(path, path_cursor, struct ss_file, list) { + rc = ss_fs_select(path_out, path_cursor->fid); + if (rc < 0) { + return -EINVAL; + } + } + + return 0; +} + +/*! Compare two path lists by checking whether both contain the same series of FIDs. + * \param[in] path_a first path to compare. + * \param[in] path_b second path to compare. + * \returns true when both path are equal, false otherwise. */ +bool ss_fs_utils_path_equals(const struct ss_list *path_a, + const struct ss_list *path_b) +{ + struct ss_file *path_cursor_a; + struct ss_file *path_cursor_b; + + path_cursor_b = SS_LIST_GET_NEXT(path_b, struct ss_file, list); + SS_LIST_FOR_EACH(path_a, path_cursor_a, struct ss_file, list) { + if (path_cursor_b == SS_LIST_GET(path_b, struct ss_file, list)) + return false; + if (path_cursor_a->fid != path_cursor_b->fid) + return false; + path_cursor_b = + SS_LIST_GET_NEXT(&path_cursor_b->list, struct ss_file, + list); + } + + /* Check that there are no remaining items in path_b, this is the case + * when path_a is shorter then path_b. */ + if (path_cursor_b == SS_LIST_GET(path_b, struct ss_file, list)) + return true; + + return false; +} + +/*! Get the selected DF from a path. + * \param[in] path path to search for the currently selected ADF. + * \returns DF on success, NULL on failure */ +struct ss_file *ss_fs_utils_get_current_df_from_path(const struct ss_list *path) +{ + struct ss_file *selected_file; + + if (ss_list_empty(path)) + return NULL; + + selected_file = ss_get_file_from_path(path); + if (!selected_file) + return NULL; + + /* The tip of the path is already a DF (or ADF) */ + if (selected_file->fcp_file_descr->type == SS_FCP_DF_OR_ADF) { + return selected_file; + } + + /* If the tip ia an EF, we can simply select the parent file as the + * parent of an EF must always be a DF (or ADF) */ + return ss_get_parent_file_from_path(path); +} diff --git a/src/softsim/uicc/fs_utils.h b/src/softsim/uicc/fs_utils.h new file mode 100644 index 0000000..28f56db --- /dev/null +++ b/src/softsim/uicc/fs_utils.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include + +char *ss_fs_utils_dump_path(const struct ss_list *path); +size_t ss_fs_utils_find_free_record(const struct ss_list *path); +size_t ss_fs_utils_find_record(const struct ss_list *path, + const uint8_t *template, + const uint8_t *mask, size_t len); +int ss_fs_utils_create_record_file(const struct ss_list *path, uint32_t fid, + uint16_t record_len, + uint8_t number_of_records); +int ss_fs_utils_path_clone(struct ss_list *path_copy, const struct ss_list *path); +int ss_fs_utils_path_select(struct ss_list *path_out, const struct ss_list *path); +bool ss_fs_utils_path_equals(const struct ss_list *path_a, + const struct ss_list *path_b); +struct ss_file *ss_fs_utils_get_current_df_from_path(const struct ss_list *path); diff --git a/src/softsim/uicc/log.c b/src/softsim/uicc/log.c new file mode 100644 index 0000000..6ce8f8a --- /dev/null +++ b/src/softsim/uicc/log.c @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include + +uint32_t ss_log_mask = 0xffffffff; + +/* TODO #64: add a mechanism to modify the log levels at runtime via getopt */ +static uint32_t subsys_lvl[_NUM_LOG_SUBSYS] = { + [SBTLV] = LDEBUG, + [SCTLV] = LDEBUG, + [SVPCD] = LINFO, + [SIFACE] = LDEBUG, + [SUICC] = LDEBUG, + [SCMD] = LDEBUG, + [SLCHAN] = LDEBUG, + [SFS] = LDEBUG, + [SSTORAGE] = LDEBUG, + [SACCESS] = LDEBUG, + [SADMIN] = LDEBUG, + [SSFI] = LDEBUG, + [SDFNAME] = LDEBUG, + [SFILE] = LDEBUG, + [SPIN] = LDEBUG, + [SAUTH] = LDEBUG, + [SPROACT] = LDEBUG, + [STLV8] = LDEBUG, + [SSMS] = LDEBUG, + [SREMOTECMD] = LDEBUG, + [SREFRESH] = LDEBUG, +}; + +static const char *subsys_str[_NUM_LOG_SUBSYS] = { + [SBTLV] = "BTLV", + [SCTLV] = "CTLV", + [SVPCD] = "VPCD", + [SIFACE] = "IFACE", + [SUICC] = "UICC", + [SCMD] = "CMD", + [SLCHAN] = "LCHAN", + [SFS] = "FS", + [SSTORAGE] = "STORAGE", + [SACCESS] = "ACCESS", + [SADMIN] = "ADMIN", + [SSFI] = "SFI", + [SDFNAME] = "DFNAME", + [SFILE] = "FILE", + [SPIN] = "PIN", + [SAUTH] = "AUTH", + [SPROACT] = "PROACT", + [SREMOTECMD] = "REMOTECMD", + [STLV8] = "TLV8", + [SSMS] = "SMS", + [SREFRESH] = "REFRESH", +}; + +static const char *level_str[_NUM_LOG_LEVEL] = { + [LERROR] = "ERROR", + [LINFO] = "INFO", + [LDEBUG] = "DEBUG", +}; + +/*! print a log line (called by IPA_LOGP, do not call directly). + * \param[in] subsys log subsystem identifier. + * \param[in] level log level identifier. + * \param[in] file source file name. + * \param[in] line source file line. + * \param[in] format formtstring (followed by arguments). */ +void ss_logp(uint32_t subsys, uint32_t level, const char *file, int line, const char *format, ...) +{ + va_list ap; + + if (!(ss_log_mask & (1 << subsys))) + return; + + assert(subsys < SS_ARRAY_SIZE(subsys_lvl)); + + if (level > subsys_lvl[subsys]) + return; + + /* TODO #67: print file and line, but make it an optional feature that + * can be selected via commandline option. The reason for this is that + * the unit-tests may compare the log output against .err files and + * even on minor changes we would constantly upset the unit-tests. */ + + fprintf(stderr, "%8s %8s ", subsys_str[subsys], level_str[level]); + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); +} diff --git a/src/softsim/uicc/proactive.c b/src/softsim/uicc/proactive.c new file mode 100644 index 0000000..40c4491 --- /dev/null +++ b/src/softsim/uicc/proactive.c @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include "context.h" +#include "proactive.h" +#include "uicc_refresh.h" +#include "btlv.h" +#include "ctlv.h" + +/* Number of poll cycles until a TERMINAL RESPONSE is concidered lost. */ +#define MAX_POLL_CYCLES 30 + +const struct ber_tlv_desc bertlv_cat_descr[] = { + { + .id = 1, + .id_parent = 0, + .title = "proprietary", + .tag_encoded = TS_101_220_IEI_PROPRITARY, + }, + { + .id = 2, + .id_parent = 0, + .title = "proactive-command", + .tag_encoded = TS_101_220_IEI_PROACTIVE_CMD, + }, + { + .id = 3, + .id_parent = 0, + .title = "SMS-PP-download", + .tag_encoded = TS_101_220_IEI_SMS_PP_DWNLD, + }, + { + .id = 4, + .id_parent = 0, + .title = "cell-broadcast-download", + .tag_encoded = TS_101_220_IEI_CBC_DWNLD, + }, + { + .id = 5, + .id_parent = 0, + .title = "menu-selection", + .tag_encoded = TS_101_220_IEI_MENU_SELECTION, + }, + { + .id = 6, + .id_parent = 0, + .title = "call-control", + .tag_encoded = TS_101_220_IEI_CALL_CTRL, + }, + { + .id = 7, + .id_parent = 0, + .title = "MO-short-message-control", + .tag_encoded = TS_101_220_IEI_MO_SMS_CTRL, + }, + { + .id = 8, + .id_parent = 0, + .title = "event-download", + .tag_encoded = TS_101_220_IEI_EVENT_DWNLD, + }, + { + .id = 9, + .id_parent = 0, + .title = "timer-expiration", + .tag_encoded = TS_101_220_IEI_TIMER_EXPIR, + }, + { + .id = 10, + .id_parent = 0, + .title = "intra-uicc", + .tag_encoded = TS_101_220_IEI_INTRA_UICC, + }, + { + .id = 11, + .id_parent = 0, + .title = "USSD-download", + .tag_encoded = TS_101_220_IEI_USSD_DWNLD, + }, + { + .id = 12, + .id_parent = 0, + .title = "MMS-transfer-status", + .tag_encoded = TS_101_220_IEI_MMS_TRX_STAT, + }, + { + .id = 13, + .id_parent = 0, + .title = "MMS-notification-download", + .tag_encoded = TS_101_220_IEI_MMS_NOTIF_DWNLD, + }, + { + .id = 14, + .id_parent = 0, + .title = "Terminal-application-tag", + .tag_encoded = TS_101_220_IEI_TERM_APP, + }, + { + .id = 15, + .id_parent = 0, + .title = "geo-location-reporting-tag", + .tag_encoded = TS_101_220_IEI_GEO_LOC, + }, + { + .id = 16, + .id_parent = 0, + .title = "envelope-container", + .tag_encoded = TS_101_220_IEI_ENVELOPE_CONTNR, + }, + { + .id = 17, + .id_parent = 0, + .title = "ProSe-report-tag", + .tag_encoded = TS_101_220_IEI_PROSE_REPORT, + }, + { + .id = 0, + } +}; + +/*! Get a btlv description for card application toolkit templates. + * \returns description for use with ss_btlv_decode(). */ +const struct ber_tlv_desc *ss_proactive_get_cat_descr(void) +{ + return bertlv_cat_descr; +} + +const struct ss_proactive_task proactive_tasks[] = { + { + .name = "SM QUEUE", + .handler = ss_uicc_sms_tx_poll, + }, + { + .name = "REFRESH", + .handler = ss_uicc_refresh_poll, + }, +}; + +/* A defeult callback function that is used in case the caller does not supply + * a callback function to handle TERMINAL RESPONSE */ +static void default_term_response_cb(struct ss_context *ctx, + uint8_t *resp_data, uint8_t resp_data_len) +{ + struct ss_list *ctlv_data = NULL; + + if (!resp_data) { + SS_LOGP(SPROACT, LERROR, "terminal did not respond!\n"); + return; + } + + SS_LOGP(SPROACT, LDEBUG, "terminal responded: %s\n", + ss_hexdump(resp_data, resp_data_len)); + + ctlv_data = ss_ctlv_decode(resp_data, resp_data_len); + if (!ctlv_data) { + SS_LOGP(SPROACT, LERROR, + "Unable to decode response - non valid COMPRESNSION-TLV?\n"); + goto leave; + } + ss_ctlv_dump(ctlv_data, 2, SPROACT, LDEBUG); + +leave: + ss_ctlv_free(ctlv_data); +} + +/*! Poll proactive tasks and do regular housekeeping. + * \param[inout] ctx softsim context. */ +void ss_proactive_poll(struct ss_context *ctx) +{ + size_t i; + + /* Go through all proactive tasks and call their handler functions. */ + for (i = 0; i < SS_ARRAY_SIZE(proactive_tasks); i++) { + SS_LOGP(SPROACT, LDEBUG, "polling proactive task %s\n", + proactive_tasks[i].name); + proactive_tasks[i].handler(ctx); + } + + /* If for some reason the TERMINAL RESPONSE gets lost or is not sent by + * the terminal we will reset te command handling after some time. To + * make sure that the caller is informed the callback function will + * be called with NULL data. The timeout counting starts immediately + * after the data is FETCHed. */ + if (ctx->proactive.term_resp_cb && ctx->proactive.data_len > 0) { + SS_LOGP(SPROACT, LDEBUG, + "waiting for the terminal FETCH %u bytes of data\n", + ctx->proactive.data_len); + } else if (ctx->proactive.term_resp_cb) { + if (ctx->proactive.term_resp_poll_ctr >= MAX_POLL_CYCLES) { + SS_LOGP(SPROACT, LDEBUG, + "giving up waiting for TERMINAL RESPONSE after %u poll cycles\n", + ctx->proactive.term_resp_poll_ctr); + term_resp_cb callback = ctx->proactive.term_resp_cb; + ss_proactive_reset(ctx); + callback(ctx, NULL, 0); + } else { + SS_LOGP(SPROACT, LDEBUG, + "waiting %u poll cycles for TERMINAL RESPONSE\n", + ctx->proactive.term_resp_poll_ctr); + ctx->proactive.term_resp_poll_ctr++; + } + } +} + +/*! Check if we are ready to send a PROACTIVE COMMAND right now. + * \param[in] ctx softsim context. + * \returns true when ready, false when another command is pending. */ +bool ss_proactive_rts(const struct ss_context *ctx) +{ + if (ctx->proactive.data_len) + return false; + if (ctx->proactive.term_resp_cb) + return false; + return true; +} + +/*! Put a PROACTIVE COMMAND for getting it FTECHed by the terminal. + * \param[inout] ctx softsim context. + * \param[in] term_resp_cb callback function to handle TERMINAL RESPONSE. + * \param[in] data command data (COMPRENSION TLV). + * \param[in] len length of data. + * \returns 0 on success, -EINVAL on error. */ +int ss_proactive_put(struct ss_context *ctx, term_resp_cb term_resp_cb, + const uint8_t *data, size_t len) +{ + struct ss_list *proact_cmd; + int rc = 0; + + /*! The parameters *data and len are mandatory. */ + assert(data); + assert(len > 0); + + if (ctx->proactive.data_len) { + SS_LOGP(SPROACT, LERROR, + "unable to put data, previous data not fetched yet!\n"); + return -EBUSY; + } + + if (ctx->proactive.term_resp_cb) { + SS_LOGP(SPROACT, LERROR, + "unable to put data, still waiting for the response to the previous transaction!\n"); + return -EBUSY; + } + + proact_cmd = SS_ALLOC(struct ss_list); + ss_list_init(proact_cmd); + ss_btlv_new_ie(proact_cmd, "proactive-command", + TS_101_220_IEI_PROACTIVE_CMD, len, data); + + ctx->proactive.data_len = + ss_btlv_encode(ctx->proactive.data, sizeof(ctx->proactive.data), + proact_cmd); + if (ctx->proactive.data_len == 0) { + SS_LOGP(SPROACT, LERROR, + "unable to put data, cannot encode BER-TLV enevelope!\n"); + rc = -EINVAL; + } + + ss_btlv_free(proact_cmd); + + if (!term_resp_cb) + ctx->proactive.term_resp_cb = default_term_response_cb; + else + ctx->proactive.term_resp_cb = term_resp_cb; + + return rc; +} + +/*! Reset proactive command handling (called after TERMINAL RESPONSE and also after timeout). + * \param[inout] ctx softsim context. */ +void ss_proactive_reset(struct ss_context *ctx) +{ + memset(ctx->proactive.data, 0, sizeof(ctx->proactive.data)); + ctx->proactive.data_len = 0; + ctx->proactive.term_resp_cb = NULL; + ctx->proactive.term_resp_poll_ctr = 0; +} + +/*! Utility function to extract the return code from a TERMINAL RESPONSE + * \param[in] rep_data COMPREHENSION-TLV encoded response setring. + * \param[in] resp_data_len length of the COMPREHEINSION-TLV encoded response string. + * \returns return code or -EINVAL when no returncode can be extracted. */ +int ss_proactive_get_rc(const uint8_t *resp_data, uint8_t resp_data_len, + enum log_subsys log_subsys) +{ + struct ss_list *ctlv_data = NULL; + struct cmp_tlv_ie *cmd_result_ie; + int rc; + + /* No response at all */ + if (!resp_data) { + SS_LOGP(log_subsys, LDEBUG, + "no terminal response received -- command unsuccessful!\n"); + return -EINVAL; + } + + SS_LOGP(log_subsys, LDEBUG, "terminal responded: %s\n", + ss_hexdump(resp_data, resp_data_len)); + + /* Decode TLV */ + ctlv_data = ss_ctlv_decode(resp_data, resp_data_len); + if (!ctlv_data) { + SS_LOGP(log_subsys, LERROR, + "Unable to decode response -- command unsuccessful!\n"); + rc = -EINVAL; + goto leave; + } + ss_ctlv_dump(ctlv_data, 2, log_subsys, LDEBUG); + + /* Check result IE (mandatory) */ + cmd_result_ie = + ss_ctlv_get_ie_minlen(ctlv_data, TS_101_220_IEI_RESULT, 1); + if (!cmd_result_ie) { + /* The result IE does not have the */ + SS_LOGP(log_subsys, LERROR, + "result lacks mandatory RESULT IE -- command unsuccessful!\n"); + rc = -EINVAL; + } else { + SS_LOGP(log_subsys, LDEBUG, "command result: %02x\n", + cmd_result_ie->value->data[0]); + rc = cmd_result_ie->value->data[0]; + } + +leave: + ss_ctlv_free(ctlv_data); + return rc; +} + +/*! Check the support of a certain feature in TERMINAL PROFILE. + * \param[inout] ctx softsim context. + * \param[in] byte_idx byte index as defined in ETSI TS 102 223, section 5.2. + * \param[in] bit_idx as defined in ETSI TS 102 223, section 5.2. + * \returns true when feature is supported, false otherwiese. */ +bool ss_proactive_term_prof_bit(const struct ss_context *ctx, size_t byte_idx, + uint8_t bit_idx) +{ + uint8_t byte; + + if (byte_idx == 0) { + SS_LOGP(SPROACT, LERROR, + "TERMINAL PROFILE byte indexes start countingt at 1!\n"); + assert(false); + } + if (bit_idx == 0) { + SS_LOGP(SPROACT, LERROR, + "TERMINAL PROFILE bit indexes start countingt at 1!\n"); + assert(false); + } + if (byte_idx > sizeof(ctx->proactive.term_profile)) { + SS_LOGP(SPROACT, LERROR, + "Tried to access TERMINAL PROFILE byte %zu, but end ist at byte %zu!\n", + byte_idx, sizeof(ctx->proactive.term_profile)); + assert(false); + } + if (bit_idx > 8) { + SS_LOGP(SPROACT, LERROR, + "Tried to access TERMINAL PROFILE byte at bit %u!\n", + bit_idx); + assert(false); + } + + /* Convert into indexes that start counting at 0 */ + byte_idx -= 1; + bit_idx -= 1; + + byte = ctx->proactive.term_profile[byte_idx]; + return ((byte >> bit_idx) & 1) == 1; +} diff --git a/src/softsim/uicc/proactive.h b/src/softsim/uicc/proactive.h new file mode 100644 index 0000000..a86ef8c --- /dev/null +++ b/src/softsim/uicc/proactive.h @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include +#include +#include "uicc_sms_tx.h" +#include "uicc_sms_rx.h" +#include "uicc_refresh.h" +struct ss_context; +struct ss_apdu; + +typedef void (*term_resp_cb)(struct ss_context * ctx, uint8_t *resp_data, + uint8_t resp_data_len); + +enum ss_proactive_cat_templates { + /* ETSI TS 101 220 Section 7.2 */ + TS_101_220_IEI_PROPRITARY = 0xCF, + TS_101_220_IEI_PROACTIVE_CMD = 0xD0, + TS_101_220_IEI_SMS_PP_DWNLD = 0xD1, + TS_101_220_IEI_CBC_DWNLD = 0xD2, + TS_101_220_IEI_MENU_SELECTION = 0xD3, + TS_101_220_IEI_CALL_CTRL = 0xD4, + TS_101_220_IEI_MO_SMS_CTRL = 0xD5, + TS_101_220_IEI_EVENT_DWNLD = 0xD6, + TS_101_220_IEI_TIMER_EXPIR = 0xD7, + TS_101_220_IEI_INTRA_UICC = 0xD8, + TS_101_220_IEI_USSD_DWNLD = 0xD9, + TS_101_220_IEI_MMS_TRX_STAT = 0xDA, + TS_101_220_IEI_MMS_NOTIF_DWNLD = 0xDB, + TS_101_220_IEI_TERM_APP = 0xDC, + TS_101_220_IEI_GEO_LOC = 0xDD, + TS_101_220_IEI_ENVELOPE_CONTNR = 0xDE, + TS_101_220_IEI_PROSE_REPORT = 0xDF, +}; +const struct ber_tlv_desc *ss_proactive_get_cat_descr(void); + +enum ss_proactive_cat_data_obj { + /* ETSI TS 101 220 Section 7.2 */ + TS_101_220_IEI_CMD_DETAILS = 0x01, + TS_101_220_IEI_DEV_ID = 0x02, + TS_101_220_IEI_RESULT = 0x03, + TS_101_220_IEI_DURATION = 0x04, + TS_101_220_IEI_ALPHA_ID = 0x05, + TS_101_220_IEI_ADDR = 0x06, + TS_101_220_IEI_CAP_CONF = 0x07, + TS_101_220_IEI_SUB_ADDR = 0x08, + TS_101_220_IEI_SS_STR_OR_PLMN_ID = 0x09, + TS_101_220_IEI_USSD_STR = 0x0A, + TS_101_220_IEI_SMS_TPDU = 0x0B, + TS_101_220_IEI_CBC_PAGE = 0x0C, + TS_101_220_IEI_TEXT_STR = 0x0D, + TS_101_220_IEI_TONE_OR_ECAD_CLNT_PROF = 0x0E, + TS_101_220_IEI_ITEM_OR_ECAD_CLNT_ID = 0x0F, + TS_101_220_IEI_ITEM_ID_OR_ENVELOPE = 0x10, + TS_101_220_IEI_RESP_LEN_OR_CC_RESULT = 0x11, + TS_101_220_IEI_FILE_LST_OR_CAT_SERV_LST = 0x12, + TS_101_220_IEI_LOCI = 0x13, + TS_101_220_IEI_IMEI = 0x14, + TS_101_220_IEI_HLP_REQ = 0x15, + TS_101_220_IEI_NET_MEAS_RSLT = 0x16, + TS_101_220_IEI_DEFAULT_TEXT = 0x17, + TS_101_220_IEI_ITEMS_NEXT_ACT = 0x18, + TS_101_220_IEI_EVENT_LST = 0x19, + TS_101_220_IEI_CAUSE = 0x1A, + TS_101_220_IEI_LOCAT_STATUS = 0x1B, + TS_101_220_IEI_TRANS_ID = 0x1C, + TS_101_220_IEI_BCCH_CHAN_LST = 0x1D, + TS_101_220_IEI_ICON_ID = 0x1E, + TS_101_220_IEI_ICON_ID_LST = 0x1F, + TS_101_220_IEI_CARDRDR_STAT = 0x20, + TS_101_220_IEI_CARD_ATR_OR_ECAT_SEQNUM = 0x21, + TS_101_220_IEI_C_APDU_OR_ENC_TLV_LST = 0x22, + TS_101_220_IEI_R_APDU_OR_SA_TEMPLATE = 0x23, + TS_101_220_IEI_TIMER_ID = 0x24, + TS_101_220_IEI_TIMER_VALUE = 0x25, + TS_101_220_IEI_DATE_TIME = 0x26, + TS_101_220_IEI_CC_REQ_ACT = 0x27, + TS_101_220_IEI_AT_CMD = 0x28, + TS_101_220_IEI_AT_RESP = 0x29, + TS_101_220_IEI_BC_REPEAT = 0x2A, + TS_101_220_IEI_IMM_RESP = 0x2B, + TS_101_220_IEI_DTMF_STR = 0x2C, + TS_101_220_IEI_LANGUAGE = 0x2D, + TS_101_220_IEI_TIMING_ADV = 0x2E, + TS_101_220_IEI_AID = 0x2F, + TS_101_220_IEI_BROWSER_ID = 0x30, + TS_101_220_IEI_URL = 0x31, + TS_101_220_IEI_BEARER = 0x32, + TS_101_220_IEI_PROV_REF_FILE = 0x33, + TS_101_220_IEI_BROWSER_CAUSE = 0x34, + TS_101_220_IEI_BEARER_DESC = 0x35, + TS_101_220_IEI_CHAN_DATA = 0x36, + TS_101_220_IEI_CHAN_DATA_LEN = 0x37, + TS_101_220_IEI_CHAN_STAT = 0x38, + TS_101_220_IEI_CHAN_BUF_SIZE = 0x39, + TS_101_220_IEI_DISP_PAR_OR_DNS_ADDR = 0x40, + TS_101_220_IEI_SERV_REC = 0x41, + TS_101_220_IEI_DEV_FLTR = 0x42, + TS_101_220_IEI_SERV_SEARCH = 0x43, + TS_101_220_IEI_ATTRIB_INFO = 0x44, + TS_101_220_IEI_SERV_AVAIL = 0x45, + TS_101_220_IEI_3GPP_1 = 0x46, + TS_101_220_IEI_NET_ACC_NAME = 0x47, + TS_101_220_IEI_3GPP_2 = 0x48, + TS_101_220_IEI_REM_ENTITY_ADDR = 0x49, + TS_101_220_IEI_WLAN_ID = 0x4A, + TS_101_220_IEI_WLAN_ACC_STAT = 0x4B, + TS_101_220_IEI_TEXT_ATTR = 0x50, + TS_101_220_IEI_ITEM_TEXT_ATTR = 0x51, + TS_101_220_IEI_PDP_CTX_ACT = 0x52, + TS_101_220_IEI_CONTACTLESS_STAT = 0x53, + TS_101_220_IEI_CONTACTLESS_FUNCT = 0x54, + TS_101_220_IEI_CELL_SEL_STAT = 0x55, + TS_101_220_IEI_CSG_ID = 0x56, + TS_101_220_IEI_HNB_NAE = 0x57, + TS_101_220_IEI_MAC = 0x60, + TS_101_220_IEI_3GPP_3 = 0x61, + TS_101_220_IEI_IMEISV = 0x62, + TS_101_220_IEI_BATTERY_STAT = 0x63, + TS_101_220_IEI_BROWSER_STAT = 0x64, + TS_101_220_IEI_NET_SEARCH_MODE = 0x65, + TS_101_220_IEI_FRAME_LAYOUT = 0x66, + TS_101_220_IEI_FRAMES_INFO = 0x67, + TS_101_220_IEI_FRAME_ID = 0x68, + TS_101_220_IEI_MEAS_QUALIF = 0x69, + TS_101_220_IEI_MMS_REF = 0x6A, + TS_101_220_IEI_MMS_ID = 0x6B, + TS_101_220_IEI_MMS_TRANS_STAT = 0x6C, + TS_101_220_IEI_3GPP_4 = 0x6D, + TS_101_220_IEI_MMS_CONTENT_ID = 0x6E, + TS_101_220_IEI_MMS_NOTIF = 0x6F, + TS_101_220_IEI_LAST_ENVELOPE = 0x70, + TS_101_220_IEI_REG_APP = 0x71, + TS_101_220_IEI_PLMNwAcT_LST = 0x72, + TS_101_220_IEI_RA_INFO = 0x73, + TS_101_220_IEI_UPD_ATT_TYPE_OR_PROSE_REP = 0x74, + TS_101_220_IEI_REJECTION_CAUSE = 0x75, + TS_101_220_IEI_GEO_LOC_OR_IARI = 0x76, + TS_101_220_IEI_NEMA_SENTENCE_OR_IMS_STAT = 0x78, + TS_101_220_IEI_PLMN_LST_OR_EUTRAN_MEAS = 0x79, + TS_101_220_IEI_BCAST_NET_INFO_OR_EXT_REG = 0x7A, + TS_101_220_IEI_ACTIVATE_DESC = 0x7B, + TS_101_220_IEI_EPS_PDN_CONN = 0x7C, + TS_101_220_IEI_TAI = 0x7D, + TS_101_220_IEI_CSG_ID_LST = 0x7E, +}; + +enum ss_proactive_type_of_cmd { + TS_102_223_TOC_REFRESH = 0x01, + TS_102_223_TOC_MORE_TIME = 0x02, + TS_102_223_TOC_POLL_INTERVAL = 0x03, + TS_102_223_TOC_POLLING_OFF = 0x04, + TS_102_223_TOC_SET_UP_EVENT_LIST = 0x05, + TS_102_223_TOC_SET_UP_CALL = 0x10, + TS_102_223_TOC_SEND_SS = 0x11, + TS_102_223_TOC_SEND_USSD = 0x12, + TS_102_223_TOC_SEND_SHORT_MESSAGE = 0x13, + TS_102_223_TOC_SEND_DTMF = 0x14, + TS_102_223_TOC_LAUNCH_BROWSER = 0x15, + TS_102_223_TOC_GEOGRAPHICAL_LOCATION_REQUEST = 0x16, + TS_102_223_TOC_PLAY_TONE = 0x20, + TS_102_223_TOC_DISPLAY_TEXT = 0x21, + TS_102_223_TOC_GET_INKEY = 0x22, + TS_102_223_TOC_GET_INPUT = 0x23, + TS_102_223_TOC_SELECT_ITEM = 0x24, + TS_102_223_TOC_SET_UP_MENU = 0x25, + TS_102_223_TOC_PROVIDE_LOCAL_INFORMATION = 0x26, + TS_102_223_TOC_TIMER_MANAGEMENT = 0x27, + TS_102_223_TOC_SET_UP_IDLE_MODE_TEXT = 0x28, + TS_102_223_TOC_PERFORM_CARD_APDU = 0x30, + TS_102_223_TOC_POWER_ON_CARD = 0x31, + TS_102_223_TOC_POWER_OFF_CARD = 0x32, + TS_102_223_TOC_GET_READER_STATUS = 0x33, + TS_102_223_TOC_RUN_AT_COMMAND = 0x34, + TS_102_223_TOC_LANGUAGE_NOTIFICATION = 0x35, + TS_102_223_TOC_OPEN_CHANNEL = 0x40, + TS_102_223_TOC_CLOSE_CHANNEL = 0x41, + TS_102_223_TOC_RECEIVE_DATA = 0x42, + TS_102_223_TOC_SEND_DATA = 0x43, + TS_102_223_TOC_GET_CHANNEL_STATUS = 0x44, + TS_102_223_TOC_SERVICE_SEARCH = 0x45, + TS_102_223_TOC_GET_SERVICE_INFORMATION = 0x46, + TS_102_223_TOC_DECLARE_SERVICE = 0x47, + TS_102_223_TOC_SET_FRAMES = 0x50, + TS_102_223_TOC_GET_FRAMES_STATUS = 0x51, + TS_102_223_TOC_RETRIEVE_MULTIMEDIA_MESSAGE = 0x60, + TS_102_223_TOC_SUBMIT_MULTIMEDIA_MESSAGE = 0x61, + TS_102_223_TOC_DISPLAY_MULTIMEDIA_MESSAGE = 0x62, + TS_102_223_TOC_ACTIVATE = 0x70, + TS_102_223_TOC_CONTACTLESS_STATE_CHANGED = 0x71, + TS_102_223_TOC_COMMAND_CONTAINER = 0x72, + TS_102_223_TOC_ENCAPSULATED_SESSION_CONTROL = 0x73, + TS_102_223_TOC_END_OF_PROACT_UICC_SESSION = 0x73, +}; + +/*! handler for proactive SIM tasks */ +struct ss_proactive_task { + /*! human readable name that describes the proactive task. */ + const char *name; + /*! CLA and MASK against which to compare CLA from APDU header */ + void (*handler)(struct ss_context * ctx); +}; + +/*! global context for proactive SIM tasks */ +struct ss_proactive_ctx { + /*! proactive SIM enabled (true after TERMINAL PROFILE command is received) */ + bool enabled; + /*! TERMINAL PROFILE data */ + uint8_t term_profile[256]; + /*! data to be fetched by FETCH command */ + uint8_t data[256]; + /*! length of data to be fetched by FETCH command */ + uint8_t data_len; + /*! callback to hanle data from TERMINAL RESPONSE command */ + term_resp_cb term_resp_cb; + /*! counter to count the poll cycles until a TERMINAL RESPONSE arrives */ + unsigned int term_resp_poll_ctr; + /*! state to handle the reception of short messages (SMS) */ + struct ss_uicc_sms_rx_state sms_rx_state; + /*! state to handle the sending of short messages (SMS) */ + struct ss_uicc_sms_tx_state sms_tx_state; + /*! state to handle the sending of refresh information (file chnages) */ + struct ss_uicc_refresh_state refresh_state; +}; + +void ss_proactive_poll(struct ss_context *ctx); +bool ss_proactive_rts(const struct ss_context *ctx); +int ss_proactive_put(struct ss_context *ctx, term_resp_cb term_resp_cb, + const uint8_t *data, size_t len); +void ss_proactive_reset(struct ss_context *ctx); +int ss_proactive_get_rc(const uint8_t *resp_data, uint8_t resp_data_len, + enum log_subsys log_subsys); +bool ss_proactive_term_prof_bit(const struct ss_context *ctx, size_t byte_idx, + uint8_t bit_idx); diff --git a/src/softsim/uicc/sfi.c b/src/softsim/uicc/sfi.c new file mode 100644 index 0000000..251f275 --- /dev/null +++ b/src/softsim/uicc/sfi.c @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include +#include "fs.h" +#include "fs_utils.h" +#include "fcp.h" +#include "btlv.h" + +#define SFI_FID 0x5F100001 + +/*! Create an internal record to manage SFI to FID translation. + * \param[in] path directory where the file shall be created. + * \returns 0 success, -EINVAL on failure. */ +int ss_sfi_create(const struct ss_list *path) +{ + return ss_fs_utils_create_record_file(path, SFI_FID, 2, 0x1f); +} + +/*! Register a FID in the internal SFI to FID translation file. + * \param[in] path path to the file that shall be registered. + * \returns 0 success, -EINVAL on failure. */ +int ss_sfi_update(const struct ss_list *path) +{ + struct ss_list path_copy; + struct ss_file *file; + struct ber_tlv_ie *fcp_sfi_ie; + struct ber_tlv_ie *fcp_fid_ie; + int rc; + uint8_t sfi; + + /*! NOTE: This function will always search in the currently selected DF + * or ADF for the SFI to FID translation file */ + + /* ensure we won't work on an uninitialized list */ + ss_list_init(&path_copy); + + file = ss_get_file_from_path(path); + + /* get FID */ + fcp_fid_ie = + ss_btlv_get_ie_minlen(file->fcp_decoded, + TS_102_221_IEI_FCP_FILE_ID, 2); + if (!fcp_fid_ie) + return -EINVAL; + + /* get SFI */ + fcp_sfi_ie = + ss_btlv_get_ie(file->fcp_decoded, + TS_102_221_IEI_FCP_SHORT_FILE_ID); + if (!fcp_sfi_ie) { + /* See also ETSI TS 102 221 11.1.1.4.8 */ + sfi = fcp_fid_ie->value->data[1] & 0x1f; + } else { + if (fcp_sfi_ie->value->len == 0) { + /* Files without SFI are accepted */ + rc = 0; + goto leave; + } else { + sfi = fcp_sfi_ie->value->data[0] >> 3; + } + } + + /* select SFI file and update record nr. SFI with FID */ + rc = ss_fs_utils_path_clone(&path_copy, path); + if (rc < 0) + return -EINVAL; + rc = ss_fs_select(&path_copy, SFI_FID); + if (rc < 0) { + SS_LOGP(SSFI, LERROR, + "cannot register SFI=%02x, unable to select lookup file %s\n", + sfi, ss_fs_utils_dump_path(&path_copy)); + rc = -EINVAL; + goto leave; + } + rc = ss_fs_write_file_record(&path_copy, sfi, + fcp_fid_ie->value->data, + fcp_fid_ie->value->len); + if (rc < 0) { + rc = -EINVAL; + goto leave; + } + + SS_LOGP(SSFI, LDEBUG, + "registered SFI=%02x for FID=%s in lookup file %s\n", sfi, + ss_hexdump(fcp_fid_ie->value->data, fcp_fid_ie->value->len), + ss_fs_utils_dump_path(&path_copy)); + rc = 0; +leave: + ss_path_reset(&path_copy); + return rc; +} + +/*! Resolve an SFI to FID by quering the SFI to FID translation file. + * \param[in] path path to the current directory. + * \param[in] sfi SFI to look for. + * \returns 0 success, -EINVAL on failure. */ +int ss_sfi_resolve(const struct ss_list *path, uint8_t sfi) +{ + struct ss_list path_copy; + int rc; + struct ss_buf *fid = NULL; + + /*! NOTE: This function will always search in the currently selected DF + * or ADF for the SFI to FID translation file */ + + /* ensure we won't work on an uninitialized list */ + ss_list_init(&path_copy); + + /* read full FID from SFI file */ + rc = ss_fs_utils_path_clone(&path_copy, path); + if (rc < 0) + return -EINVAL; + rc = ss_fs_select(&path_copy, SFI_FID); + if (rc < 0) { + SS_LOGP(SSFI, LERROR, + "cannot resolve SFI=%02x, unable to select lookup file %s\n", + sfi, ss_fs_utils_dump_path(&path_copy)); + rc = -EINVAL; + goto leave; + } + fid = ss_fs_read_file_record(&path_copy, sfi); + if (!fid) { + SS_LOGP(SSFI, LERROR, + "unable to resolve SFI=%02x to FID - lookup file %s is not readable\n", + sfi, ss_fs_utils_dump_path(&path_copy)); + rc = -EINVAL; + goto leave; + } + + rc = ss_uint32_from_array(fid->data, fid->len); + SS_LOGP(SSFI, LDEBUG, + "resolved SFI=%02x to FID=%04x using lookup file %s\n", sfi, rc, + ss_fs_utils_dump_path(&path_copy)); +leave: + ss_path_reset(&path_copy); + return rc; +} diff --git a/src/softsim/uicc/sfi.h b/src/softsim/uicc/sfi.h new file mode 100644 index 0000000..7cb85bd --- /dev/null +++ b/src/softsim/uicc/sfi.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +struct ss_list; + +int ss_sfi_create(const struct ss_list *path); +int ss_sfi_update(const struct ss_list *path); +int ss_sfi_resolve(const struct ss_list *path, uint8_t sfi); diff --git a/src/softsim/uicc/sms.c b/src/softsim/uicc/sms.c new file mode 100644 index 0000000..d317f40 --- /dev/null +++ b/src/softsim/uicc/sms.c @@ -0,0 +1,527 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sms.h" + +/* see also: 3GPP TS 23.040, section 9.2.3.1 */ +static enum ss_sms_tp_mti decode_mti(const uint8_t *sms_tpdu, + size_t sms_tpdu_len, bool ms_to_sc) +{ + uint8_t mti; + + if (sms_tpdu_len < 1) + return SMS_MTI_INVALID; + + mti = sms_tpdu[0] & 0x03; + if (mti > 2) + return SMS_MTI_INVALID; + if (ms_to_sc) + mti |= 0x10; + + return mti; +} + +static int decode_addr(struct ss_sms_addr *addr_dec, + const uint8_t *addr, size_t addr_len) +{ + uint8_t n_digits; + uint8_t n_bytes; + uint8_t i; + char digit; + char *digits_buf = addr_dec->digits; + + memset(addr_dec, 0, sizeof(*addr_dec)); + + if (addr_len < 2) + return -EINVAL; + + /* Decode number of digits */ + n_digits = addr[0]; + n_bytes = n_digits / 2; + if (n_digits % 2) + n_bytes++; + if (addr_len < n_bytes + 2) + return -EINVAL; + addr++; + + /* Decode header */ + addr_dec->extension = (*addr && 0x80); + addr_dec->type_of_number = *addr >> 4 & 0x07; + addr_dec->numbering_plan = *addr & 0x0F; + addr++; + + /* Decode digits to printable string */ + for (i = 0; i < n_bytes; i++) { + /* odd digit */ + digit = *addr & 0x0f; + if (digit > 9) + return -EINVAL + 1; + *digits_buf = digit | 0x30; + digits_buf++; + + /* even digit */ + digit = (*addr >> 4) & 0x0F; + if (digit == 0x0F && n_bytes == i + 1) + break; + else if (digit > 9) + return -EINVAL + 2; + *digits_buf = digit | 0x30; + digits_buf++; + + addr++; + } + + /* Return number of decoded bytes */ + return n_bytes + 2; +} + +static int encode_addr(uint8_t *addr, size_t addr_len, + const struct ss_sms_addr *addr_dec) +{ + uint8_t n_digits; + uint8_t n_bytes; + size_t bytes_used = 0; + uint8_t d = 0; + uint8_t i; + + if (addr_len < 2) + return -ENOMEM; + + /* Encode number of digits */ + n_digits = + (uint8_t) ss_strnlen(addr_dec->digits, sizeof(addr_dec->digits)); + addr[bytes_used] = n_digits; + bytes_used++; + + /* Encode header */ + addr[bytes_used] = 0x00; + if (addr_dec->extension) + addr[bytes_used] |= 0x80; + addr[bytes_used] |= ((addr_dec->type_of_number & 0x07) << 4); + addr[bytes_used] |= addr_dec->numbering_plan & 0x0F; + bytes_used++; + + if (addr_len < bytes_used + n_digits + 1 / 2) + return -ENOMEM; + + /* Encode digits */ + n_bytes = n_digits / 2; + if (n_digits % 2) + n_bytes++; + for (i = 0; i < n_bytes; i++) { + addr[bytes_used + i] = addr_dec->digits[d] & 0x0F; + d++; + if (d < n_digits) + addr[bytes_used + i] |= + ((addr_dec->digits[d] & 0x0F) << 4); + else + addr[bytes_used + i] |= 0xF0; + d++; + } + bytes_used += n_bytes; + + return bytes_used; +} + +/* see also: 3GPP TS 23.040, section 9.2.2.1 */ +static int rx_sms_deliver(struct ss_sms_deliver *sm, const uint8_t *sms_tpdu, + size_t sms_tpdu_len) +{ + int addr_len; + size_t bytes_used = 0; + + /* TP-MMS */ + sm->tp_mms = ((sms_tpdu[0] >> 2) & 1) == 1; + + /* TP-RP */ + sm->tp_rp = ((sms_tpdu[0] >> 7) & 1) == 1; + + /* TP-UDHI */ + sm->tp_udhi = ((sms_tpdu[0] >> 6) & 1) == 1; + + /* TP-SRI */ + sm->tp_sri = ((sms_tpdu[0] >> 5) & 1) == 1; + bytes_used++; + + /* TP-OA */ + addr_len = + decode_addr(&sm->tp_oa, sms_tpdu + bytes_used, + sms_tpdu_len - bytes_used); + if (addr_len < 0) { + SS_LOGP(SSMS, LERROR, + "invalid address (TP-OA) -- reception of SMS-DELIVER failed!\n"); + return -EINVAL; + } + bytes_used += addr_len; + + /* TP-PID */ + if (sms_tpdu_len < bytes_used + 1) { + SS_LOGP(SSMS, LERROR, + "unexpected end of message (TP-PID) -- reception of SMS-DELIVER failed!\n"); + return -EINVAL; + } + sm->tp_pid = sms_tpdu[bytes_used]; + bytes_used++; + + /* TP-DCS */ + if (sms_tpdu_len < bytes_used + 1) { + SS_LOGP(SSMS, LERROR, + "unexpected end of message (TP-DCS) -- reception of SMS-DELIVER failed!\n"); + return -EINVAL; + } + sm->tp_dcs = sms_tpdu[bytes_used]; + bytes_used++; + + /* TP-SCTS */ + if (sms_tpdu_len < bytes_used + 7) { + SS_LOGP(SSMS, LERROR, + "unexpected end of message (TP-SCTS) -- reception of SMS-DELIVER failed!\n"); + return -EINVAL; + } + memcpy(sm->tp_scts, sms_tpdu + bytes_used, sizeof(sm->tp_scts)); + bytes_used += 7; + + /* TP-UDL */ + if (sms_tpdu_len < bytes_used + 1) { + SS_LOGP(SSMS, LERROR, + "unexpected end of message (TP-UDL) -- reception of SMS-DELIVER failed!\n"); + return -EINVAL; + } + sm->tp_udl = sms_tpdu[bytes_used]; + bytes_used++; + + return bytes_used; +} + +/* see also: 3GPP TS 23.040, section 9.2.2.3 */ +static int rx_sms_status_report(struct ss_sms_status_report *sm, + const uint8_t *sms_tpdu, size_t sms_tpdu_len) +{ + int addr_len; + size_t bytes_used = 0; + + /* TP-MMS */ + sm->tp_mms = ((sms_tpdu[0] >> 2) & 1) == 1; + bytes_used++; + + /* TP-MR */ + if (sms_tpdu_len < bytes_used + 1) { + SS_LOGP(SSMS, LERROR, + "unexpected end of message (TP-MR) -- reception of SMS-STATUS-REPORT failed!\n"); + return -EINVAL; + } + sm->tp_mr = sms_tpdu[bytes_used]; + bytes_used++; + + /* TP-RA */ + addr_len = + decode_addr(&sm->tp_ra, sms_tpdu + bytes_used, + sms_tpdu_len - bytes_used); + if (addr_len < 0) { + SS_LOGP(SSMS, LERROR, + "invalid address (TP-RA) -- reception of SMS-STATUS-REPORT failed!\n"); + return -EINVAL; + } + bytes_used += addr_len; + + /* TP-SCTS */ + if (sms_tpdu_len < bytes_used + 7) { + SS_LOGP(SSMS, LERROR, + "unexpected end of message (TP-SCTS) -- reception of SMS-STATUS-REPORT failed!\n"); + return -EINVAL; + } + memcpy(sm->tp_scts, sms_tpdu + bytes_used, sizeof(sm->tp_scts)); + bytes_used += 7; + + /* TP-DT */ + if (sms_tpdu_len < bytes_used + 7) { + SS_LOGP(SSMS, LERROR, + "unexpected end of message (TP-DT) -- reception of SMS-STATUS-REPORT failed!\n"); + return -EINVAL; + } + memcpy(sm->tp_dt, sms_tpdu + bytes_used, sizeof(sm->tp_scts)); + bytes_used += 7; + + /* TP-ST */ + if (sms_tpdu_len < bytes_used + 1) { + SS_LOGP(SSMS, LERROR, + "unexpected end of message (TP-ST) -- reception of SMS-STATUS-REPORT failed!\n"); + return -EINVAL; + } + sm->tp_st = sms_tpdu[bytes_used]; + bytes_used++; + + return bytes_used; +} + +/* see also: 3GPP TS 23.040, section 9.2.2.2 */ +static int rx_sms_submit_report(struct ss_sms_submit_report *sm, + const uint8_t *sms_tpdu, size_t sms_tpdu_len) +{ + size_t bytes_used = 0; + + /* First byte only contains the TP-MTI, which we have already parsed */ + bytes_used++; + + /* TP-ST */ + if (sms_tpdu_len < bytes_used + 1) { + SS_LOGP(SSMS, LERROR, + "unexpected end of message (TP-ST) -- reception of SMS-SUBMIT-REPORT failed!\n"); + return -EINVAL; + } + sm->tp_fcs = sms_tpdu[bytes_used]; + bytes_used++; + + return bytes_used; +} + +/*! Decode SMS TPDU header. + * \param[out] sm_hdr pointer to user struct that holds the decoding results. + * \param[in] sms_tpdu buffer with binary SMS message header to decode. + * \param[in] sms_tpdu_len maximum length of sms_tpdu buffer. + * \returns 0 on success, -EINVAL on error. */ +int ss_sms_hdr_decode(struct ss_sm_hdr *sm_hdr, const uint8_t *sms_tpdu, + size_t sms_tpdu_len) +{ + memset(sm_hdr, 0, sizeof(*sm_hdr)); + + sm_hdr->tp_mti = decode_mti(sms_tpdu, sms_tpdu_len, false); + + switch (sm_hdr->tp_mti) { + case SMS_MTI_DELIVER: + return rx_sms_deliver(&sm_hdr->u.sms_deliver, sms_tpdu, + sms_tpdu_len); + case SMS_MTI_STATUS_REPORT: + return rx_sms_status_report(&sm_hdr->u.sms_status_report, + sms_tpdu, sms_tpdu_len); + case SMS_MTI_SUBMIT_REPORT: + return rx_sms_submit_report(&sm_hdr->u.sms_submit_report, + sms_tpdu, sms_tpdu_len); + default: + SS_LOGP(SSMS, LERROR, + "unexpected or invalid message type (mti=%u) received\n", + sm_hdr->tp_mti & 0x03); + return -EINVAL; + } + + return -EINVAL; +} + +/* see also: 3GPP TS 23.040, section 9.2.2.1a */ +static int tx_sms_deliver_report(uint8_t *sms_tpdu, size_t sms_tpdu_len, + const struct ss_sms_deliver_report *sm) +{ + size_t bytes_used = 0; + + /* TP-UDHI */ + if (sm->tp_udhi) + sms_tpdu[bytes_used] |= (1 << 6); + bytes_used++; + + /* TP-FCS */ + if (sms_tpdu_len < bytes_used + 1) + return -ENOMEM; + sms_tpdu[bytes_used] = sm->tp_fcs; + bytes_used++; + + /* TP-PI */ + if (sms_tpdu_len < bytes_used + 1) + return -ENOMEM; + if (sm->tp_pid_present) + sms_tpdu[bytes_used] |= 1; + if (sm->tp_dcs_present) + sms_tpdu[bytes_used] |= (1 << 1); + if (sm->tp_udl_present) + sms_tpdu[bytes_used] |= (1 << 2); + bytes_used++; + + /* TP-PID */ + if (sm->tp_pid_present) { + if (sms_tpdu_len < bytes_used + 1) + return -ENOMEM; + sms_tpdu[bytes_used] = sm->tp_pid; + bytes_used++; + } + + /* TP-DCS */ + if (sm->tp_dcs_present) { + if (sms_tpdu_len < bytes_used + 1) + return -ENOMEM; + sms_tpdu[bytes_used] = sm->tp_dcs; + bytes_used++; + } + + /* TP-UDL */ + if (sm->tp_udl_present) { + if (sms_tpdu_len < bytes_used + 1) + return -ENOMEM; + sms_tpdu[bytes_used] = sm->tp_udl; + bytes_used++; + } + + return bytes_used; +} + +/* see also: 3GPP TS 23.040, section 9.2.2.1a */ +static int tx_sms_command(uint8_t *sms_tpdu, size_t sms_tpdu_len, + const struct ss_sms_command *sm) +{ + size_t bytes_used = 0; + int rc; + + /* TP-UDHI and TP-SRR */ + if (sm->tp_udhi) + sms_tpdu[bytes_used] |= (1 << 6); + if (sm->tp_srr) + sms_tpdu[bytes_used] |= (1 << 5); + bytes_used++; + + /* TP-MR */ + if (sms_tpdu_len < bytes_used + 1) + return -ENOMEM; + sms_tpdu[bytes_used] = sm->tp_mr; + bytes_used++; + + /* TP-PID */ + if (sms_tpdu_len < bytes_used + 1) + return -ENOMEM; + sms_tpdu[bytes_used] = sm->tp_pid; + bytes_used++; + + /* TP-CT */ + if (sms_tpdu_len < bytes_used + 1) + return -ENOMEM; + sms_tpdu[bytes_used] = sm->tp_ct; + bytes_used++; + + /* TP-MN */ + if (sms_tpdu_len < bytes_used + 1) + return -ENOMEM; + sms_tpdu[bytes_used] = sm->tp_mn; + bytes_used++; + + /* TP-DA */ + rc = encode_addr(&sms_tpdu[bytes_used], sms_tpdu_len - bytes_used, + &sm->tp_da); + if (rc < 0) + return -EINVAL; + bytes_used += rc; + + /* TP-CDL */ + if (sms_tpdu_len < bytes_used + 1) + return -ENOMEM; + sms_tpdu[bytes_used] = sm->tp_cdl; + bytes_used++; + + return bytes_used; +} + +/* see also: 3GPP TS 23.040, section 9.2.2.2 */ +static int tx_sms_submit(uint8_t *sms_tpdu, size_t sms_tpdu_len, + const struct ss_sms_submit *sm) +{ + size_t bytes_used = 0; + int rc; + + /* TP-RD, TP-VPF, TP-RP, TP-UDHI and TP-SRR */ + if (sm->tp_rd) + sms_tpdu[bytes_used] |= (1 << 2); + sms_tpdu[bytes_used] |= ((sm->tp_vpf & 0x03) << 3); + if (sm->tp_rp) + sms_tpdu[bytes_used] |= (1 << 7); + if (sm->tp_udhi) + sms_tpdu[bytes_used] |= (1 << 6); + if (sm->tp_srr) + sms_tpdu[bytes_used] |= (1 << 5); + bytes_used++; + + /* TP-MR */ + if (sms_tpdu_len < bytes_used + 1) + return -ENOMEM; + sms_tpdu[bytes_used] = sm->tp_mr; + bytes_used++; + + /* TP-DA */ + rc = encode_addr(&sms_tpdu[bytes_used], sms_tpdu_len - bytes_used, + &sm->tp_da); + if (rc < 0) + return -EINVAL; + bytes_used += rc; + + /* TP-PID */ + if (sms_tpdu_len < bytes_used + 1) + return -ENOMEM; + sms_tpdu[bytes_used] = sm->tp_pid; + bytes_used++; + + /* TP-DCS */ + if (sms_tpdu_len < bytes_used + 1) + return -ENOMEM; + sms_tpdu[bytes_used] = sm->tp_dcs; + bytes_used++; + + /* TP-VP */ + if (sm->tp_vpf != SMS_VPF_NONE) { + if (sms_tpdu_len - bytes_used < sizeof(sm->tp_vp)) + return -ENOMEM; + memcpy(&sms_tpdu[bytes_used], sm->tp_vp, sizeof(sm->tp_vp)); + bytes_used += sizeof(sm->tp_vp); + } + + /* TP-UDL */ + if (sms_tpdu_len < bytes_used + 1) + return -ENOMEM; + sms_tpdu[bytes_used] = sm->tp_udl; + bytes_used++; + + return bytes_used; +} + +/*! Encode SMS TPDU header. + * \param[out] sms_tpdu buffer to store resulting binary SMS message header. + * \param[in] sms_tpdu_len maximum length of sms_tpdu buffer. + * \param[in] sm_hdr pointer to user struct that holds the header data to encode. + * \returns 0 on success, -EINVAL on error. */ +int ss_sms_hdr_encode(uint8_t *sms_tpdu, size_t sms_tpdu_len, + const struct ss_sm_hdr *sm_hdr) +{ + memset(sms_tpdu, 0, sms_tpdu_len); + + if (sms_tpdu_len < 1) + return -ENOMEM; + + sms_tpdu[0] = sm_hdr->tp_mti & 0x03; + + switch (sm_hdr->tp_mti) { + case SMS_MTI_DELIVER_REPORT: + return tx_sms_deliver_report(sms_tpdu, sms_tpdu_len, + &sm_hdr->u.sms_deliver_report); + case SMS_MTI_COMMAND: + return tx_sms_command(sms_tpdu, sms_tpdu_len, + &sm_hdr->u.sms_command); + case SMS_MTI_SUBMIT: + return tx_sms_submit(sms_tpdu, sms_tpdu_len, + &sm_hdr->u.sms_submit); + default: + SS_LOGP(SSMS, LERROR, + "cannot encode message with unexpected message type (mti=%u)\n", + sm_hdr->tp_mti & 0x03); + return -EINVAL; + } + + return 0; +} diff --git a/src/softsim/uicc/sms.h b/src/softsim/uicc/sms.h new file mode 100644 index 0000000..0d636b8 --- /dev/null +++ b/src/softsim/uicc/sms.h @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#define SMS_MAX_SIZE 140 +#define SMS_HDR_MAX_SIZE 24 + +enum ss_sms_tp_mti { + SMS_MTI_DELIVER = 0x00, /* sc to ms */ + SMS_MTI_DELIVER_REPORT = 0x10, /* ms to sc */ + SMS_MTI_STATUS_REPORT = 0x02, /* sc to ms */ + SMS_MTI_COMMAND = 0x12, /* ms to sc */ + SMS_MTI_SUBMIT = 0x11, /* ms to sc */ + SMS_MTI_SUBMIT_REPORT = 0x01, /* sc to ms */ + SMS_MTI_INVALID = 0xFF, +}; + +enum ss_sms_tp_vpf { + SMS_VPF_NONE = 0x00, + SMS_VPF_RELATIVE = 0x02, + SMS_VPF_ENHANCED = 0x01, + SMS_VPF_ABSOLUTE = 0x03, +}; + +enum ss_sms_tp_ct { + SMS_CT_ENQUIRY = 0x00, + SMS_CT_CANCEL_STATUS = 0x01, + SMS_CT_DELETE_MSG = 0x02, + SMS_CT_ENABLE_STATUS = 0x03, +}; + +enum ss_sms_udh_ie { + TS_23_040_IEI_CONCAT_SMS = 0x00, + TS_23_040_IEI_SPECIAL_SMS_IND = 0x01, + TS_23_040_IEI_APP_PORT_ADDR_8 = 0x04, + TS_23_040_IEI_APP_PORT_ADDR_16 = 0x05, + TS_23_040_IEI_SMSC_CTRL_PARAM = 0x06, + TS_23_040_IEI_UDH_SRC_IND = 0x07, + TS_23_040_IEI_CONCAT_SMS_REF = 0x08, + TS_23_040_IEI_WL_CTRL_MSG_PROT = 0x09, + TS_23_040_IEI_TXT_FORMATTING = 0x0A, + TS_23_040_IEI_PREDEF_SOUND = 0x0B, + TS_23_040_IEI_USRDEF_SOUND = 0x0C, + TS_23_040_IEI_PREDEF_ANIM = 0x0D, + TS_23_040_IEI_LARGE_ANIM = 0x0E, + TS_23_040_IEI_SMALL_ANIM = 0x0F, + TS_23_040_IEI_LARGE_PICT = 0x10, + TS_23_040_IEI_SMALL_PICT = 0x11, + TS_23_040_IEI_VAR_PICT = 0x12, + TS_23_040_IEI_USER_PROMPT_IND = 0x13, + TS_23_040_IEI_EXT_OBJECT = 0x14, + TS_23_040_IEI_REUSED_EXT_OBJECT = 0x15, + TS_23_040_IEI_COMPRESSION_CTRL = 0x16, + TS_23_040_IEI_OBJ_DIST_IND = 0x17, + TS_23_040_IEI_STD_WVG_OBJ = 0x18, + TS_23_040_IEI_CHAR_SIZE_WVG_OBJ = 0x19, + TS_23_040_IEI_EXT_OBJ_DATA_REQ_CMD = 0x1A, + TS_23_040_IEI_RFC_5322_EMAIL_HDR = 0x20, + TS_23_040_IEI_HYPERLINK_FMT_ELEM = 0x21, + TS_23_040_IEI_REPLY_ADDR_ELEM = 0x22, + TS_23_040_IEI_ENH_VOICE_MAIL_INF = 0x23, + TS_23_040_IEI_NAT_LANG_SING_SHIFT = 0x24, + TS_23_040_IEI_NAT_LANG_LOCK_SHIFT = 0x25, +}; + +struct ss_sms_addr { + bool extension; + uint8_t type_of_number; + uint8_t numbering_plan; + char digits[21]; +}; + +struct ss_sms_deliver { + bool tp_mms; + bool tp_rp; + bool tp_udhi; + bool tp_sri; + struct ss_sms_addr tp_oa; + uint8_t tp_pid; + uint8_t tp_dcs; + uint8_t tp_scts[7]; + uint8_t tp_udl; +}; + +struct ss_sms_deliver_report { + bool tp_udhi; + uint8_t tp_fcs; + uint8_t tp_pid; + bool tp_pid_present; + uint8_t tp_dcs; + bool tp_dcs_present; + uint8_t tp_udl; + bool tp_udl_present; +}; + +struct ss_sms_submit { + bool tp_rd; + uint8_t tp_vpf; + bool tp_rp; + bool tp_udhi; + bool tp_srr; + uint8_t tp_mr; + struct ss_sms_addr tp_da; + uint8_t tp_pid; + uint8_t tp_dcs; + uint8_t tp_vp[7]; + uint8_t tp_udl; +}; + +struct ss_sms_submit_report { + uint8_t tp_fcs; +}; + +struct ss_sms_status_report { + uint8_t tp_mr; + bool tp_mms; + struct ss_sms_addr tp_ra; + uint8_t tp_scts[7]; + uint8_t tp_dt[7]; + uint8_t tp_st; +}; + +struct ss_sms_command { + bool tp_rd; + bool tp_udhi; + bool tp_srr; + uint8_t tp_mr; + uint8_t tp_pid; + uint8_t tp_ct; + uint8_t tp_mn; + struct ss_sms_addr tp_da; + uint8_t tp_cdl; +}; + +struct ss_sm_hdr { + enum ss_sms_tp_mti tp_mti; + union { + struct ss_sms_deliver sms_deliver; + struct ss_sms_deliver_report sms_deliver_report; + struct ss_sms_status_report sms_status_report; + struct ss_sms_submit_report sms_submit_report; + struct ss_sms_submit sms_submit; + struct ss_sms_command sms_command; + } u; +}; + +int ss_sms_hdr_decode(struct ss_sm_hdr *sm_hdr, const uint8_t *sms_tpdu, + size_t sms_tpdu_len); +int ss_sms_hdr_encode(uint8_t *sms_tpdu, size_t sms_tpdu_len, + const struct ss_sm_hdr *sm_hdr); diff --git a/src/softsim/uicc/softsim.c b/src/softsim/uicc/softsim.c new file mode 100644 index 0000000..09cfc13 --- /dev/null +++ b/src/softsim/uicc/softsim.c @@ -0,0 +1,482 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + * + * This module provides tha API to communicate with softsim. The APDU input and + * output is communicated via ss_apdu structs. + */ + +#include +#include +#include +#include +#include +#include "access.h" +#include "fs.h" +#include "fs_utils.h" +#include "sw.h" +#include "command.h" +#include "uicc_lchan.h" +#include "context.h" +#include "apdu.h" +#include "uicc_ins.h" +#include "proactive.h" + +/*! Create a new softsim context. + * \returns allocated softsim context. */ +struct ss_context *ss_new_ctx(void) +{ + struct ss_context *ctx; + uint8_t *fs_chg_filelist; + ctx = SS_ALLOC(struct ss_context); + if (ctx == NULL) + return NULL; + fs_chg_filelist = SS_ALLOC(uint8_t[SS_FS_CHG_BUF_SIZE]); + if (fs_chg_filelist == NULL) { + SS_FREE(ctx); + return NULL; + } + + /* Note: filling the entire context struct with zeros at the beginning + * is important since the softsim code relies on the fact that in the + * begining all context state and state in its sub structs is in a + * defined state (all-zero). */ + memset(ctx, 0, sizeof(*ctx)); + + ctx->fs_chg_filelist = fs_chg_filelist; + /* Set length indicator; the rest may stay uninitialized */ + ctx->fs_chg_filelist[0] = 0; + + return ctx; +} + +/*! Create a new softsim context that is recording into the file list of + * another context given in @p fs_chg_filelist. + * \returns allocated softsim context. */ +struct ss_context *ss_new_reporting_ctx(uint8_t *fs_chg_filelist) +{ + struct ss_context *ctx; + ctx = SS_ALLOC(struct ss_context); + if (ctx == NULL) + return NULL; + + /* Zeroing as in ss_new_ctx */ + memset(ctx, 0, sizeof(*ctx)); + + ctx->fs_chg_is_borrowed = true; + + ctx->fs_chg_filelist = fs_chg_filelist; + if (fs_chg_filelist != NULL) + ctx->fs_chg_record = true; + + return ctx; +} + +/*! Free a new softsim context. */ +void ss_free_ctx(struct ss_context *ctx) +{ + /* Clear all proactive sim related state (cat) */ + ss_uicc_sms_rx_clear(ctx); + ss_uicc_sms_tx_clear(ctx); + + ss_uicc_lchan_free(ctx); + + if (!ctx->fs_chg_is_borrowed) + SS_FREE(ctx->fs_chg_filelist); + + SS_FREE(ctx); +} + +/*! Reset the UICC state. + * \param[inout] ctx softsim context. */ +void ss_reset(struct ss_context *ctx) +{ + SS_LOGP(SLCHAN, LDEBUG, + "------------------------------- reset -------------------------------\n"); + + /* Reset lchan(s) */ + ss_uicc_lchan_reset(ctx); + + /* Clear all proactive sim related state (cat) */ + /* NOTE: we clear the cat_sms_state before the memset since this struct + * may hold a linked list, which needs to be freed first. After that it + * is safe to wipe out everythig with zeros. */ + ss_uicc_sms_rx_clear(ctx); + ss_uicc_sms_tx_clear(ctx); + memset(&ctx->proactive, 0, sizeof(ctx->proactive)); + + return; +} + +/*! Poll the UICC to process proactive SIM tasks. + * \param[inout] ctx softsim context. */ +void ss_poll(struct ss_context *ctx) +{ + if (ctx->proactive.enabled) + ss_proactive_poll(ctx); + return; +} + +/*! Get an ATR (without resetting the UICC state). + * \param[inout] ctx softsim context. + * \param[out] atr_buf user provided memory to store the resulting ATR. + * \param[in] atr_buf_len maxium length of the user provided memory. + * \returns length of the resulting ATR. */ +size_t ss_atr(struct ss_context *ctx, uint8_t *atr_buf, size_t atr_buf_len) +{ + uint8_t tck = 0; + size_t i; + + uint8_t atr[] = + { 0x3B, 0x9F, 0x01, 0x80, 0x1F, 0x87, 0x80, 0x31, 0xE0, 0x73, 0xFE, + 0x21, 0x00, 0x67, 0x4A, 0x4C, 0x75, 0x30, 0x34, 0x05, 0x4B + }; + + for (i = 1; i < sizeof(atr); i++) { + tck ^= atr[i]; + } + + assert(atr_buf_len >= sizeof(atr) + 1); + + memcpy(atr_buf, atr, sizeof(atr)); + atr_buf[sizeof(atr)] = tck; + + return sizeof(atr) + 1; +} + +/* Perform transaction with UICC using an already parsed APDU */ +static int apdu_transact(struct ss_context *ctx, struct ss_apdu *apdu) +{ + const struct ss_command *cmd; + struct ss_list backup_path; + int rc; + struct ss_apdu *last_apdu = NULL; + + int processed_length = -1; + + SS_LOGP(SLCHAN, LDEBUG, + "------------------------- transaction begins ------------------------\n"); + + SS_LOGP(SLCHAN, LDEBUG, "Rx C-APDU %02X-%02X %02X-%02X %02X\n", + apdu->hdr.cla, apdu->hdr.ins, apdu->hdr.p1, apdu->hdr.p2, + apdu->hdr.p3); + + /* TS 102 221 Table 10.3 */ + if (apdu->hdr.cla & 0x0C) { + /* we don't support secure messaging */ + apdu->sw = SS_SW_ERR_FUNCTION_IN_CLA_NOT_SUPP_SM; + goto out; + } + + /* Find an lchan (UICC state) for the incoming APDU */ + apdu->lchan = ss_uicc_lchan_get(ctx, apdu->hdr.cla); + if (!apdu->lchan) { + apdu->sw = SS_SW_ERR_FUNCTION_IN_CLA_NOT_SUPP_LCHAN; + goto out; + } + + /* Reset the APDU keep flag. This is related to GET RESPONSE. The decision + * is made for each individual transaction. */ + apdu->lchan->last_apdu_keep = false; + + /* We do not ask of commands that they keep the path where it was in their + * error cases. */ + ss_fs_utils_path_clone(&backup_path, &apdu->lchan->fs_path); + + if (((apdu->hdr.cla & 0x70) == 0x00) + && apdu->hdr.ins == TS_102_221_INS_GET_RESPONSE) { + /* The GET RESPONSE command is of command case 2 (see also command.h) */ + processed_length = 5; + + /* Handle GET RESPONSE command */ + SS_LOGP(SLCHAN, LDEBUG, "handling GET RESPONSE command...\n"); + last_apdu = apdu->lchan->last_apdu; + + /* Check parameters and abort if no response can be returned */ + if (apdu->hdr.p1 != 0 || apdu->hdr.p2 != 0) { + apdu->sw = SS_SW_ERR_CHECKING_WRONG_P1_P2; + apdu->le = 0; + SS_LOGP(SLCHAN, LERROR, + "P1 and P2 must be 0x00 -- abort.\n"); + goto out; + } + if (!last_apdu) { + apdu->sw = SS_SW_ERR_CHECKING_NO_PRECISE_DIAG; + apdu->le = 0; + SS_LOGP(SLCHAN, LERROR, + "no previous APDU in storage, cannot return any response -- abort.\n"); + goto out; + } + if (last_apdu->rsp_len == 0) { + apdu->rsp_len = 0; + apdu->sw = SS_SW_ERR_CHECKING_WRONG_LENGTH; + apdu->le = 0; + SS_LOGP(SLCHAN, LERROR, + "last command did not return any response -- abort.\n"); + goto out; + } + + /* When more data than available is requested, tell the correct + * length of the available data */ + if (apdu->hdr.p3 > last_apdu->rsp_len) { + assert(last_apdu->rsp_len <= 0xff); + apdu->sw = 0x6c00 | last_apdu->rsp_len; + /* We must keep the last APDU so that the terminal has a chance to pick + * up the data in a second try. */ + apdu->lchan->last_apdu_keep = true; + apdu->le = 0; + SS_LOGP(SLCHAN, LERROR, + "incorrect response length requested (%u), expecting %lu\n", + apdu->hdr.p3, last_apdu->rsp_len); + } else { + memcpy(apdu->rsp, last_apdu->rsp, last_apdu->rsp_len); + apdu->rsp_len = last_apdu->rsp_len; + apdu->sw = SS_SW_NORMAL_ENDING; + } + } else { + /* Match APDU and execute command handler */ + cmd = ss_command_match(apdu); + if (cmd) { + + /* Verify properties */ + switch (cmd->case_) { + case SS_COMMAND_CASE_UNDEF: + SS_LOGP(SLCHAN, LERROR, + "Command %s found, but not executing for lack of case definition\n", + cmd->name); + apdu->sw = SS_SW_ERR_CHECKING_INS_INVALID; + goto out; + case SS_COMMAND_CASE_1: + processed_length = 4; + break; + case SS_COMMAND_CASE_2: + processed_length = 4 + 1; + apdu->le = apdu->hdr.p3; + break; + case SS_COMMAND_CASE_3: + case SS_COMMAND_CASE_4: + apdu->lc = apdu->hdr.p3; + if (apdu->lc < apdu->hdr.p3) { + /* Abort here, the handler would treat + * p3 as Lc and then peek into + * uninitialized memory */ + SS_LOGP(SLCHAN, LERROR, + "Insufficient data for Case 3/4 command\n"); + apdu->sw = + SS_SW_ERR_CHECKING_WRONG_LENGTH; + goto out; + } + processed_length = 4 + 1 + apdu->hdr.p3; + break; + } + + SS_LOGP(SLCHAN, LDEBUG, + "Command %s is APDU CASE %u => lc=%u, le=%u\n", + cmd->name, cmd->case_, apdu->lc, apdu->le); + + if (apdu->lc) + SS_LOGP(SLCHAN, LDEBUG, + "Rx C-APDU body %s (%u bytes)\n", + ss_hexdump(apdu->cmd, apdu->lc), + apdu->lc); + + apdu->sw = 0; + rc = cmd->handler(apdu); + + if (apdu->sw == 0) { + /* If the handler does not set a status word, we will either + * use the rc as SW or set a generic one here, + * depending on the handler return code */ + if (rc < 0) + apdu->sw = + SS_SW_ERR_CHECKING_NO_PRECISE_DIAG; + else if (rc == 0) + apdu->sw = SS_SW_NORMAL_ENDING; + else + apdu->sw = rc; + } + } else { + apdu->sw = SS_SW_ERR_CHECKING_INS_INVALID; + } + } + +out: + /* Check result and re-populate the status word if necessary */ + if (apdu->lc) { + /* if the response is successful, and we have response data, signal + * the length via SW=91xx */ + if (apdu->sw == SS_SW_NORMAL_ENDING && apdu->rsp_len) { + assert(apdu->rsp_len <= 0xff); + apdu->sw = 0x6100 | apdu->rsp_len; + } + } else { + if (apdu->le == 0) { + SS_LOGP(SLCHAN, LDEBUG, + "Returning rsp_len = %lu bytes after le = 0\n", + apdu->rsp_len); + } else if (apdu->le != apdu->rsp_len) { + SS_LOGP(SLCHAN, LERROR, + "invalid response data, le (%u) != rsp_len (%lu), changing SW=%04x to SW=%04x (wrong length)\n", + apdu->le, apdu->rsp_len, apdu->sw, + SS_SW_ERR_CHECKING_WRONG_LENGTH); + apdu->sw = SS_SW_ERR_CHECKING_WRONG_LENGTH; + apdu->rsp_len = 0; + } + } + + /* Add length of proactive sim data */ + if (ctx->proactive.enabled && apdu->sw == 0x9000 + && ctx->proactive.data_len) + apdu->sw = 0x9100 | ctx->proactive.data_len; + + if (apdu->rsp_len) { + SS_LOGP(SLCHAN, LDEBUG, "Tx R-APDU SW=%04x %s (%lu bytes)\n", + apdu->sw, ss_hexdump(apdu->rsp, apdu->rsp_len), + apdu->rsp_len); + } else { + SS_LOGP(SLCHAN, LDEBUG, "Tx R-APDU SW=%04x\n", apdu->sw); + } + + if (!ss_sw_is_successful(apdu->sw) + /* If the command just fails with "wrong length", it usually didn't change the path */ + && (apdu->sw >> 8) != 0x6c) { + SS_LOGP(SLCHAN, LINFO, + "Unsuccessful response %04x, restoring backup path.\n", + apdu->sw); + assert(apdu->sw >> 8 != 0x61); + /* We could apply various levels of smart here: + * + * - Leave the fs_path alone if it's still on the same file. + * - Start restoring at the point where they diverge,and only restore the + * parts that diverged. + * + * But in the end, this only happens in the error case for which + * computation time barely matters. + */ + ss_path_reset(&apdu->lchan->fs_path); + rc = ss_fs_utils_path_select(&apdu->lchan->fs_path, + &backup_path); + if (rc < 0) { + SS_LOGP(SLCHAN, LERROR, "Failed to restore path.\n"); + /* Not taking any further actions -- the SW is already unsuccessful (with + * the original error, which is likely more helpful). The fs_path may now + * be in an empty state, which would usually indicate the absence of an + * MF, in which case it will fail future access checks (unless the card + * was really reset into personalization mode, which is currently not + * implemented). */ + } + if (!ss_list_empty(&apdu->lchan->fs_path)) + ss_access_populate(apdu->lchan); + } + ss_path_reset(&backup_path); + + if (apdu->lchan) + ss_uicc_lchan_dump(apdu->lchan); + if (ctx->fs_chg_record) { + SS_LOGP(SLCHAN, LDEBUG, "file changes since last refresh:\n"); + if (ctx->fs_chg_filelist[0] != 0x00) + ss_fs_chg_dump(ctx->fs_chg_filelist, 1, SLCHAN, LDEBUG); + else + SS_LOGP(SLCHAN, LDEBUG, " (none)\n"); + } + SS_LOGP(SPROACT, LDEBUG, "proactive sim: %s\n", + ctx->proactive.enabled ? "active" : "inactive"); + + SS_LOGP(SLCHAN, LDEBUG, + "------------------------- transaction ended -------------------------\n"); + return processed_length; +} + +/*! Perform transaction with UICC. + * \param[inout] ctx softsim context. + * \param[out] response_buf user provided memory to store the resulting response APDU (at least 2+256 bytes). + * \param[in] response_buf_len maxium length of the resulting response APDU. + * \param[in] request_buf user provided memory with request APDU. + * \param[inout] request_len length of the request APDU. + * \returns length of the resulting response APDU. */ +size_t ss_transact(struct ss_context *ctx, + uint8_t *response_buf, size_t response_buf_len, + uint8_t *request_buf, size_t *request_len) +{ + /*! Note: The request_len parameter may indicate a request length + * longer than 5+255 bytes. At return, command_len indicates the true + * number of bytes consumed by the command (which is "all of them" + * in case the command was not known) */ + + struct ss_apdu *apdu; + size_t response_len = 0; + int processed_length; + size_t _request_len = *request_len; + + /* A valid APDU must have a length of at least 5 bytes, any shorter + * APDU counts as invalid and must not be processed any further. */ + if (_request_len < 5) { + SS_LOGP(SIFACE, LERROR, "ignoring short APDU: %s\n", + ss_hexdump(request_buf, _request_len)); + response_buf[response_len++] = + SS_SW_ERR_CHECKING_WRONG_LENGTH >> 8; + response_buf[response_len++] = + SS_SW_ERR_CHECKING_WRONG_LENGTH & 0xff; + return 2; + } + + /* A card response can consume up to 255 bytes, it must be ensured that + * the response buffer is large enough. */ + assert(response_buf_len >= sizeof(apdu->rsp) + sizeof(apdu->sw)); + + /* Limit the request length to the maximum length of an APDU. It is + * legal to call this function with a request lengths far longer than + * a valid APDU would be. This may be the case when the actual length + * of the request is not yet known and the APDUs come in a concatenated + * list without delimiters or length information. */ + if (_request_len > sizeof(apdu->cmd) + sizeof(apdu->hdr)) { + SS_LOGP(SIFACE, LINFO, + "request exceeds maximum length %lu > %lu, will truncate.\n", + _request_len, sizeof(apdu->cmd) + sizeof(apdu->hdr)); + _request_len = sizeof(apdu->cmd) + sizeof(apdu->hdr); + } + + apdu = ss_apdu_new(ctx); + + /* Parse APDU */ + memcpy(&apdu->hdr, request_buf, sizeof(apdu->hdr)); + memcpy(apdu->cmd, request_buf + sizeof(apdu->hdr), + _request_len - sizeof(apdu->hdr)); + + /* Note: We cannot determine the apdu->le and apdu->lc fields yet since + * those depend on extra knowledge about the command itsself. We will + * populate those fields in apdu_transact() when the exact command is + * known. */ + + /* Process APDU (softsim) */ + processed_length = apdu_transact(ctx, apdu); + if (ss_sw_is_successful(apdu->sw)) { + if (processed_length != _request_len) { + /* This is not necessarily an error -- on the remote file handling side, + * it's pretty much to be expected. */ + SS_LOGP(SIFACE, LINFO, + "Processed %d bytes, but request was %zu\n", + processed_length, *request_len); + } + /* The case switch should have caught that already; ensure things fail hard + * if we accessed uninitialized memory */ + assert(processed_length <= _request_len); + } + + /* Return the actual request length back to the caller. */ + if (processed_length >= 0) + *request_len = processed_length; + + if (apdu->lc == 0) { + /* no command data present (Case 2), we can return a response */ + memcpy(response_buf, apdu->rsp, apdu->rsp_len); + response_len = apdu->rsp_len; + } + response_buf[response_len++] = apdu->sw >> 8; + response_buf[response_len++] = apdu->sw & 0xff; + + ss_apdu_toss(apdu); + + return response_len; +} diff --git a/src/softsim/uicc/sw.c b/src/softsim/uicc/sw.c new file mode 100644 index 0000000..44a46cf --- /dev/null +++ b/src/softsim/uicc/sw.c @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include "sw.h" + +/*! Check if a status word refers to a successful outcome. + * \param[in] sw status word to check + * \returns true when SW refers to a successful outcome, false otherwise. */ +bool ss_sw_is_successful(uint16_t sw) +{ + if (sw == SS_SW_NORMAL_ENDING) + return true; + sw = sw >> 8; + if (sw == 0x91) + /* 9000 and proactive data is pending */ + return true; + if (sw >= 0x61 && sw <= 0x63) + return true; + return false; +} diff --git a/src/softsim/uicc/sw.h b/src/softsim/uicc/sw.h new file mode 100644 index 0000000..8a598d5 --- /dev/null +++ b/src/softsim/uicc/sw.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +/* collection of status words described in TS 102 221 or 31.102 */ +enum sim_status_word { + SS_SW_NORMAL_ENDING = 0x9000, + SS_SW_NORMAL_ENDING_EXTRA_INFO = 0x9100, + SS_SW_NORMAL_ENDING_EXTRA_INFO_XFER = 0x9200, + SS_SW_POSTP_SAT_BUSY_CANNOT_EXEC = 0x9300, + + SS_SW_WARN_NO_INFO_NV_UNCHANGED = 0x6200, + SS_SW_WARNN_PART_OF_RET_MAY_CORRUPT = 0x6281, + SS_SW_WARN_EOF_BEFORE_LE = 0x6282, + SS_SW_WARN_SELECTED_FILE_INVALIDATED = 0x6283, + SS_SW_WARN_SELECTED_FILE_IN_TERMINATED = 0x6285, + SS_SW_WARN_MORE_DATA_AVAIL = 0x62f1, + SS_SW_WARN_MORE_DATA_AVAIL_AND_PROACT_PEND = 0x62f3, + SS_SW_WARN_MORE_DATA_EXPECTED = 0x63f1, + SS_SW_WARN_VERIFICATION_FAILED_X_REMAIN = 0x63c0, + + SS_SW_ERR_EXEC_NO_INFO_NV_UNCHANGED = 0x6400, + SS_SW_ERR_EXEC_MEMORY_PROBLEM = 0x6581, + + SS_SW_ERR_CHECKING_WRONG_LENGTH = 0x6700, + SS_SW_ERR_CHECKING_WRONG_P1_P2 = 0x6b00, + SS_SW_ERR_CHECKING_INS_INVALID = 0x6d00, + SS_SW_ERR_CHECKING_CLA_INVALID = 0x6e00, + SS_SW_ERR_CHECKING_NO_PRECISE_DIAG = 0x6f00, + SS_SW_ERR_FUNCTION_IN_CLA_NOT_SUPP = 0x6800, + SS_SW_ERR_FUNCTION_IN_CLA_NOT_SUPP_LCHAN = 0x6881, + SS_SW_ERR_FUNCTION_IN_CLA_NOT_SUPP_SM = 0x6882, + + SS_SW_ERR_CMD_NOT_ALLOWED_NO_INFO = 0x6900, + SS_SW_ERR_CMD_NOT_ALLOWED_INCOMP_FILE_STRUCT = 0x6981, + SS_SW_ERR_CMD_NOT_ALLOWED_SECURITY_STATUS = 0x6982, + SS_SW_ERR_CMD_NOT_ALLOWED_PIN_BLOCKED = 0x6983, + SS_SW_ERR_CMD_NOT_ALLOWED_REF_DATA_INVALIDATED = 0x6984, + SS_SW_ERR_CMD_NOT_ALLOWED_CONDITONS_NOT_SATISFIED = 0x6985, + SS_SW_ERR_CMD_NOT_ALLOWED_NO_EF_SELECTED = 0x6986, + SS_SW_ERR_CMD_NOT_ALLOWED_SEC_CHAN_NOT_SATISFIED = 0x6989, + + SS_SW_ERR_WRONG_PARAM_INCORRECT_DATA = 0x6a80, + SS_SW_ERR_WRONG_PARAM_FUNCTION_NOT_SUPPORTED = 0x6a81, + SS_SW_ERR_WRONG_PARAM_FILE_NOT_FOUND = 0x6a82, + SS_SW_ERR_WRONG_PARAM_RECORD_NOT_FOUND = 0x6a83, + SS_SW_ERR_WRONG_PARAM_ENOMEM = 0x6a84, + SS_SW_ERR_WRONG_PARAM_INCORRECT_P1_TO_P2 = 0x6a86, + SS_SW_ERR_WRONG_PARAM_LC_INCONSISTENT_WITH_P1_TO_P2 = 0x6a87, + SS_SW_ERR_WRONG_PARAM_REFERENCED_DATA_NOT_FOUND = 0x6a88, + + SS_SW_APP_ERR_INCREASE_MAX_VALUE_REACHED = 0x9850, + SS_SW_APP_ERR_AUTH_ERR_APP_SPECIFIC = 0x9862, + SS_SW_APP_ERR_SECURITY_SESSION_OR_ASSOC_EXPIRED = 0x9863, +}; + +bool ss_sw_is_successful(uint16_t sw); diff --git a/src/softsim/uicc/tlv8.c b/src/softsim/uicc/tlv8.c new file mode 100644 index 0000000..8999746 --- /dev/null +++ b/src/softsim/uicc/tlv8.c @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "tlv8.h" + +/* Advance the bufer and check if we are still in bounds. The parameter "inc" + * sets how many bytes the buffer pointer (enc) should be be advanced. The + * parameter "bytes_ahead" sets the minimum valid bytes that the caller expects + * to be available after the buffer pointer (enc) has been advanced. */ +#define CHECK_AND_ADVANCE(inc, bytes_ahead) \ + if (len < bytes_used + inc + bytes_ahead) { \ + SS_LOGP(STLV8, LDEBUG, "exceeding buffer bounds: len=%zu, inc=%zu, bytes_ahead=%zu, cannot decode IE\n", \ + len, (size_t) inc, (size_t) bytes_ahead); \ + return NULL; \ + } \ + bytes_used+=inc; \ + enc+=inc \ + +static struct tlv8_ie *decode_ie(size_t *used_len, const uint8_t *enc, + size_t len) +{ + struct tlv8_ie ie; + struct tlv8_ie *ie_ret; + size_t bytes_used = 0; + uint8_t ie_len = 0; + const uint8_t *value; + + *used_len = 0; + memset(&ie, 0, sizeof(ie)); + + /* We expect at least 1 byte tag + 1 byte len */ + if (len < 2) + return NULL; + + /* Decode tag */ + ie.tag = *enc; + CHECK_AND_ADVANCE(1, 1); + + /* Decode length */ + ie_len = *enc; + CHECK_AND_ADVANCE(1, ie_len); + + value = enc; + CHECK_AND_ADVANCE(ie_len, 0); + *used_len = bytes_used; + + /* Create output struct */ + ie_ret = SS_ALLOC(struct tlv8_ie); + if (!ie_ret) + return NULL; + memcpy(ie_ret, &ie, sizeof(ie)); + + /* Copy data part to the newly allocated item */ + ie_ret->value = ss_buf_alloc(ie_len); + memcpy(ie_ret->value->data, value, ie_len); + + return ie_ret; +} + +/*! Decode binary TLV8 encoded data. + * \param[in] enc pointer to buffer with encoded TLV8 data. + * \param[in] len length of the buffer that contains the TLV8 encoded data. + * \returns pointer to allocated linked list with TLV8 data (can be empty). */ +struct ss_list *ss_tlv8_decode(const uint8_t *enc, size_t len) +{ + size_t i; + struct tlv8_ie *ie; + size_t used_len; + size_t remaining_len = len; + + struct ss_list *list; + + list = SS_ALLOC(struct ss_list); + ss_list_init(list); + do { + /* Decode IE and store it in the given list */ + ie = decode_ie(&used_len, enc, remaining_len); + if (ie) { + ss_list_put(list, &ie->list); + } else if (remaining_len > 0) { + for (i = used_len; i < used_len + remaining_len; ++i) { + if (enc[i] != 0xff) { + SS_LOGP(STLV8, LERROR, + "Error decoding TLV8 (%s).\n", + ss_hexdump(&enc[used_len], + remaining_len)); + ss_tlv8_free(list); + return NULL; + } + } + } + + /* Go to the next IE */ + enc += used_len; + remaining_len -= used_len; + } while (ie != NULL); + + return list; +} + +static void dump_ie(struct tlv8_ie *ie, uint8_t indent, + enum log_subsys subsys, enum log_level level) +{ + char indent_str[256]; + char *value_str; + size_t value_len; + char delimiter; + + memset(indent_str, ' ', indent); + indent_str[indent] = '\0'; + + if (ie == NULL) { + SS_LOGP(subsys, level, "%s(NULL)\n", indent_str); + return; + } + + if (ie->value) { + value_str = ss_hexdump(ie->value->data, ie->value->len); + value_len = ie->value->len; + delimiter = ':'; + } else { + value_str = ""; + value_len = 0; + delimiter = ' '; + } + + SS_LOGP(subsys, level, "%s(tag=0x%02x, len=%zu)%c %s\n", indent_str, + ie->tag, value_len, delimiter, value_str); +} + +/*! Dump decoded TLV8 data. + * \param[in] list linked list begin of the TLV8 list. + * \param[in] indent indentation level of the generated output. + * \param[in] log_subsys log subsystem to generate the output for. + * \param[in] log_level log level to generate the output for. */ +void ss_tlv8_dump(const struct ss_list *list, uint8_t indent, + enum log_subsys log_subsys, enum log_level log_level) +{ + struct tlv8_ie *ie; + + SS_LIST_FOR_EACH(list, ie, struct tlv8_ie, list) { + dump_ie(ie, indent, log_subsys, log_level); + } +} + +static void free_ie(struct tlv8_ie *ie) +{ + if (ie == NULL) + return; + + if (ie->value) + ss_buf_free(ie->value); + + /* Make sure all data vanishes from memory */ + memset(ie, 0, sizeof(*ie)); + + SS_FREE(ie); +} + +/*! Free TLV8 data (including list begin). + * \param[in] list linked list begin of the TLV8 list. */ +void ss_tlv8_free(struct ss_list *list) +{ + struct tlv8_ie *ie; + struct tlv8_ie *ie_pre; + + if (!list) + return; + + if (ss_list_empty(list)) + return; + + SS_LIST_FOR_EACH_SAVE(list, ie, ie_pre, struct tlv8_ie, list) { + /* Unlink the element from the list and free it. */ + ss_list_remove(&ie->list); + free_ie(ie); + } + + /* Get rid of the list isself */ + SS_FREE(list); +} + +/*! Allocate a new TLV8 IE. + * \param[out] list linked list parent of the TLV8 list. + * \param[in] tag TLV8 tag (encoded format). + * \param[in] cr TLV8 comprehension flag. + * \param[in] len TLV8 value length. + * \param[in] value pointer to TLV8 value (data is copied). + * \returns pointer to allocated IE struct. */ +struct tlv8_ie *ss_tlv8_new_ie(struct ss_list *list, uint8_t tag, size_t len, + const uint8_t *value) +{ + struct tlv8_ie *ie = SS_ALLOC(struct tlv8_ie); + + memset(ie, 0, sizeof(*ie)); + ie->tag = tag; + + if (value) { + ie->value = ss_buf_alloc(len); + memcpy(ie->value->data, value, len); + } else { + ie->value = ss_buf_alloc(0); + } + + if (list) + ss_list_put(list, &ie->list); + return ie; +} + +/*! Get an IE from the list by its tag (on the current level). + * \param[in] list linked list begin of the TLV8 tree. + * \param[in] tag TLV8 (encoded format) tag to look for. + * \returns pointer to IE struct on success, NULL if IE is not found. */ +struct tlv8_ie *ss_tlv8_get_ie(const struct ss_list *list, uint8_t tag) +{ + struct tlv8_ie *ie; + + if (!list) + return NULL; + + SS_LIST_FOR_EACH(list, ie, struct tlv8_ie, list) { + if (ie->tag == tag) + return ie; + } + + return NULL; +} + +/*! Get an IE from the list by its tag, ensure minimum length (on the current level). + * \param[in] list linked list begin of the TLV8 tree. + * \param[in] tag TLV8 tag to look for. + * \param[in] min_len minimum required length. + * \returns pointer to IE struct on success, NULL if IE is not found. */ +struct tlv8_ie *ss_tlv8_get_ie_minlen(const struct ss_list *list, uint8_t tag, + size_t min_len) +{ + struct tlv8_ie *ie = ss_tlv8_get_ie(list, tag); + if (!ie) + return NULL; + if (ie->value->len < min_len) + return NULL; + return ie; +} + +static size_t encode_ie(uint8_t *enc, size_t len, struct tlv8_ie *ie) +{ + size_t ie_len; + + ie_len = ie->value->len + 2; + + /* Do not encode anything when we are unable to determine the length + * or when the predicted length exceeds the buffer. */ + if (ie_len == 0 || ie_len > len) { + SS_LOGP(STLV8, LERROR, + "not enough buffer space to encode TLV string, aborting at IE %02x.\n", + ie->tag); + return 0; + } + + /* Encode tag */ + *enc = ie->tag; + enc++; + + /* Encode len */ + *enc = ie->value->len; + enc++; + + /* Encode data */ + memcpy(enc, ie->value->data, ie->value->len); + + return ie_len; +} + +/*! Encode linked list with TLV8 data to its binary encoded representation. + * \param[out] enc pointer to buffer to output the encoded TLV8 data. + * \param[in] len length of the buffer that will store the output. + * \param[in] list linked list with TLV8 data to encode. + * \returns number of encoed bytes. */ +size_t ss_tlv8_encode(uint8_t *enc, size_t len, const struct ss_list *list) +{ + size_t bytes_remain = len; + struct tlv8_ie *ie; + size_t rc; + + /* clear output buffer (just to be sure) */ + memset(enc, 0, len); + + SS_LIST_FOR_EACH(list, ie, struct tlv8_ie, list) { + rc = encode_ie(enc, bytes_remain, ie); + if (rc == 0) + return 0; + bytes_remain -= rc; + enc += rc; + } + + return len - bytes_remain; +} + +static size_t calc_tlv8_len(const struct ss_list *list) +{ + size_t bytes_needed = 0; + struct tlv8_ie *ie; + size_t rc; + + SS_LIST_FOR_EACH(list, ie, struct tlv8_ie, list) { + rc = ie->value->len + 2; + if (rc == 0) + return 0; + bytes_needed += rc; + } + + return bytes_needed; +} + +/*! Encode linked list with TLV8 data to its binary encoded representation (to ss_buf). + * \param[inout] list linked list with TLV8 data to encode. + * \returns ss_buf with encoded result. */ +struct ss_buf *ss_tlv8_encode_to_ss_buf(const struct ss_list *list) +{ + size_t bytes_needed; + struct ss_buf *buf; + + bytes_needed = calc_tlv8_len(list); + buf = ss_buf_alloc(bytes_needed); + ss_tlv8_encode(buf->data, buf->len, list); + + return buf; +} diff --git a/src/softsim/uicc/tlv8.h b/src/softsim/uicc/tlv8.h new file mode 100644 index 0000000..ea1807f --- /dev/null +++ b/src/softsim/uicc/tlv8.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include +#include +#include + +struct tlv8_ie { + struct ss_list list; + uint8_t tag; + struct ss_buf *value; +}; + +struct ss_list *ss_tlv8_decode(const uint8_t *enc, size_t len); +void ss_tlv8_dump(const struct ss_list *list, uint8_t indent, + enum log_subsys log_subsys, enum log_level log_level); +void ss_tlv8_free(struct ss_list *list); + +struct tlv8_ie *ss_tlv8_new_ie(struct ss_list *list, uint8_t tag, size_t len, + const uint8_t *value); +struct tlv8_ie *ss_tlv8_get_ie(const struct ss_list *list, uint8_t tag); +struct tlv8_ie *ss_tlv8_get_ie_minlen(const struct ss_list *list, uint8_t tag, + size_t min_len); +size_t ss_tlv8_encode(uint8_t *enc, size_t len, const struct ss_list *list); +struct ss_buf *ss_tlv8_encode_to_ss_buf(const struct ss_list *list); diff --git a/src/softsim/uicc/uicc_admin.c b/src/softsim/uicc/uicc_admin.c new file mode 100644 index 0000000..04c0381 --- /dev/null +++ b/src/softsim/uicc/uicc_admin.c @@ -0,0 +1,481 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include +#include "access.h" +#include "sw.h" +#include "btlv.h" +#include "uicc_lchan.h" +#include "fs.h" +#include "command.h" +#include "uicc_ins.h" +#include "uicc_admin.h" +#include "fcp.h" +#include "sfi.h" +#include "df_name.h" +#include "apdu.h" + +/* In some dialects files are sometimes created with an FCP that contains a + * shortended file descriptor. The number of records is then missing and it + * is expected to calculate the number of records from the record length + * and the file size. This function will do exactly that and will fix the + * file descriptor so that it is spec compliant. */ +static int fix_short_fd(struct ss_list *fcp_decoded_envelope) +{ + struct ss_buf *fd_fixed; + struct ss_fcp_file_descr fd_decoded; + struct ber_tlv_ie *fcp_ie_fd; + struct ber_tlv_ie *fcp_ie_file_size; + size_t file_size; + uint8_t number_of_records; + int rc; + + fcp_ie_fd = + ss_btlv_get_ie_minlen(fcp_decoded_envelope, + TS_102_221_IEI_FCP_FILE_DESCR, 2); + if (!fcp_ie_fd) { + SS_LOGP(SADMIN, LERROR, + "invalid FCP: no file descriptor (%02X), cannot fix!\n", + TS_102_221_IEI_FCP_FILE_DESCR); + rc = -EINVAL; + } + + fcp_ie_file_size = + ss_btlv_get_ie_minlen(fcp_decoded_envelope, + TS_102_221_IEI_FCP_FILE_SIZE, 1); + if (!fcp_ie_file_size) { + SS_LOGP(SADMIN, LERROR, + "invalid FCP: short file descr -- no size IE (%02X), cannot fix!\n", + TS_102_221_IEI_FCP_FILE_SIZE); + rc = -EINVAL; + } + + /* A file descriptor for a record oriented file is always 5 bytes long. + * the shortended format is 4 bytes long. (file descriptors for non + * record oriented files are 3 byte long. */ + if (fcp_ie_fd->value->len == 4) { + /* extend the length of the file descriptor length */ + fd_fixed = ss_buf_alloc(5); + memcpy(fd_fixed->data, fcp_ie_fd->value->data, + fcp_ie_fd->value->len); + fd_fixed->data[4] = 0x00; + + /* we should now be able to decode the file descriptor */ + rc = ss_fcp_dec_file_descr(&fd_decoded, fd_fixed); + if (rc < 0) { + ss_buf_free(fd_fixed); + SS_LOGP(SADMIN, LERROR, + "invalid FCP: short file descr -- cannot fix!\n"); + return -EINVAL; + } + + /* make sure the file descriptor is indeed describing a record + * oriented file */ + if (fd_decoded.structure != SS_FCP_LINEAR_FIXED && + fd_decoded.structure != SS_FCP_CYCLIC) { + ss_buf_free(fd_fixed); + SS_LOGP(SADMIN, LERROR, + "invalid FCP: short file descr -- cannot fix!\n"); + return -EINVAL; + } + + /* calculate missing number of records */ + file_size = + ss_uint32_from_array(fcp_ie_file_size->value->data, + fcp_ie_file_size->value->len); + number_of_records = file_size / fd_decoded.record_len; + if (number_of_records > 254) + number_of_records = 254; + else + number_of_records = (uint8_t) number_of_records; + fd_fixed->data[4] = number_of_records; + + /* replace fd in TLV structure */ + ss_buf_free(fcp_ie_fd->value); + fcp_ie_fd->value = fd_fixed; + + SS_LOGP(SADMIN, LERROR, + "invalid FCP: short file descr -- fixed (%s)\n", + ss_hexdump(fcp_ie_fd->value->data, + fcp_ie_fd->value->len)); + return 0; + } + + SS_LOGP(SADMIN, LERROR, + "invalid FCP: short file descr -- cannot fix!\n"); + return -EINVAL; +} + +/* Validate the FCP, make sure mandatory information elements are present. + * + * This calls out to \ref fix_short_fd to populate missing information, see + * there. + * + * This returns as late as possible to gather as much debug output as + * practical, as long as the basic structure is present. + * */ +static int validate_fcp(struct ss_list *fcp_decoded_envelope) +{ + int rc = 0; + struct ber_tlv_ie *fcp_ie; + struct ss_fcp_file_descr file_descr; + if (!fcp_decoded_envelope) { + SS_LOGP(SADMIN, LERROR, "invalid FCP: no data\n"); + return -EINVAL; + } + + fcp_ie = + ss_btlv_get_ie_minlen(fcp_decoded_envelope, + TS_102_221_IEI_FCP_FILE_DESCR, 2); + if (!fcp_ie) { + SS_LOGP(SADMIN, LERROR, + "invalid FCP: no file descriptor (%02X)\n", + TS_102_221_IEI_FCP_FILE_DESCR); + rc = -EINVAL; + } else { + rc = ss_fcp_dec_file_descr(&file_descr, fcp_ie->value); + if (rc < 0) { + SS_LOGP(SADMIN, LERROR, + "invalid FCP: unable to decode file descriptor -- trying to fix (%s)\n", + ss_hexdump(fcp_ie->value->data, fcp_ie->value->len)); + rc = fix_short_fd(fcp_decoded_envelope); + if (rc < 0) + rc = -EINVAL; + else + rc = 0; + } + } + + fcp_ie = + ss_btlv_get_ie_minlen(fcp_decoded_envelope, + TS_102_221_IEI_FCP_FILE_ID, 2); + if (!fcp_ie) { + SS_LOGP(SADMIN, LERROR, + "invalid FCP: no file identifier (%02X)\n", + TS_102_221_IEI_FCP_FILE_ID); + rc = -EINVAL; + } + + fcp_ie = + ss_btlv_get_ie_minlen(fcp_decoded_envelope, + TS_102_221_IEI_FCP_LIFE_CYCLE_ST, 1); + if (!fcp_ie) { + SS_LOGP(SADMIN, LERROR, + "invalid FCP: no file lifecycle status byte (%02X)\n", + TS_102_221_IEI_FCP_LIFE_CYCLE_ST); + rc = -EINVAL; + } + + /* we don't support compact or expanded security attributes */ + if (ss_btlv_get_ie(fcp_decoded_envelope, TS_102_221_IEI_FCP_SEC_ATTR_8C) + || ss_btlv_get_ie(fcp_decoded_envelope, + TS_102_221_IEI_FCP_SEC_ATTR_AB)) { + SS_LOGP(SADMIN, LERROR, + "invalid FCP: compact or expanded security rules not supported (%02X || %02X)\n", + TS_102_221_IEI_FCP_SEC_ATTR_8C, + TS_102_221_IEI_FCP_SEC_ATTR_AB); + rc = -EINVAL; + } + + /* we must have referenced security attributes */ + fcp_ie = + ss_btlv_get_ie(fcp_decoded_envelope, TS_102_221_IEI_FCP_SEC_ATTR_8B); + if (!fcp_ie) { + SS_LOGP(SADMIN, LERROR, + "invalid FCP: no referenced security attributes (%02X)\n", + TS_102_221_IEI_FCP_SEC_ATTR_8B); + rc = -EINVAL; + } + + switch (file_descr.type) { + case SS_FCP_WORKING_EF: + fcp_ie = + ss_btlv_get_ie_minlen(fcp_decoded_envelope, + TS_102_221_IEI_FCP_FILE_SIZE, 1); + if (!fcp_ie) { + SS_LOGP(SADMIN, LERROR, + "invalid FCP: no size (%02X)\n", + TS_102_221_IEI_FCP_FILE_SIZE); + rc = -EINVAL; + } + + switch (file_descr.structure) { + case SS_FCP_TRANSPARENT: + case SS_FCP_LINEAR_FIXED: + break; + default: + SS_LOGP(SADMIN, LERROR, + "invalid FCP: unsupported file struture (%u)\n", + file_descr.structure); + rc = -EINVAL; + } + + break; + case SS_FCP_DF_OR_ADF: + fcp_ie = + ss_btlv_get_ie(fcp_decoded_envelope, + TS_102_221_IEI_FCP_PIN_STAT_TMPL); + if (fcp_ie) { + SS_LOGP(SADMIN, LERROR, + "FCP needlessly contains pin status template (%02X)\n", + TS_102_221_IEI_FCP_PIN_STAT_TMPL); + rc = -EINVAL; + } + + break; + default: + SS_LOGP(SADMIN, LERROR, + "invalid FCP: unsupported file type (%u)\n", + file_descr.type); + rc = -EINVAL; + } + + return rc; +} + +/** If an EF is currently selected, select the enclosing DF + * + * This is necessary in operations that generally work on DFs but can also be + * performed when there is an EF selected, such as CREATE FILE or DELETE FILE. + * + * The rewinding could be deferred to performing the operation if not for + * access control: That check must happen with the right kind of selected file + * for its scope, for otherwise the wrong file's access control would be + * checked, which on top of it has completely different meaning for its bits. + * + * (There is no strong need to defer rewinding, though: commands may alter the + * selected path before failing, and the path is restored automatically). + */ +static void rewind_to_df(struct ss_apdu *apdu) +{ + struct ss_file *selected_file = ss_get_file_from_path(&apdu->lchan->fs_path); + if (!selected_file) + return; + + if (selected_file->fcp_file_descr->type != SS_FCP_DF_OR_ADF) { + ss_fs_select_parent(&apdu->lchan->fs_path); + } +} + +/*! CREATE FILE (TS 102 222 Section 6.3) */ +int ss_uicc_admin_cmd_create_file(struct ss_apdu *apdu) +{ + struct ss_list *fcp_decoded; + struct ss_buf *fcp_reencoded; + struct ber_tlv_ie *fcp_fid_ie; + struct ber_tlv_ie *fcp_file_descr_ie; + struct ss_fcp_file_descr file_descr; + struct ber_tlv_ie *fcp_tmpl_ie; + uint32_t fid; + int rc; + struct ss_file *selected_file; + + /* Reset current record for record oriented files. */ + apdu->lchan->current_record = 0; + + if (apdu->hdr.p1 != 0 || apdu->hdr.p2 != 0) + return SS_SW_ERR_CHECKING_WRONG_P1_P2; + + fcp_decoded = ss_btlv_decode(apdu->cmd, apdu->lc, ss_fcp_get_descr()); + if (!fcp_decoded) { + SS_LOGP(SADMIN, LERROR, "unable to decode FCP template -- cannot create file\n"); + goto err_inval; + } + + ss_btlv_dump(fcp_decoded, 0, SFS, LINFO); + + /* Extract fid */ + fcp_tmpl_ie = ss_btlv_get_ie(fcp_decoded, TS_102_221_IEI_FCP_TMPL); + if (!fcp_tmpl_ie) { + SS_LOGP(SADMIN, LERROR, "missing FCP template -- cannot create file\n"); + goto err_inval; + } + + rc = validate_fcp(fcp_tmpl_ie->nested); + if (rc < 0) { + SS_LOGP(SADMIN, LERROR, "invalid or incomplete FCP -- cannot create file\n"); + goto err_inval; + } + + fcp_fid_ie = + ss_btlv_get_ie_minlen(fcp_tmpl_ie->nested, + TS_102_221_IEI_FCP_FILE_ID, 2); + if (!fcp_fid_ie) { + SS_LOGP(SADMIN, LERROR, "unable to decode FCP template -- cannot create file\n"); + goto err_inval; + } + fid = + ss_uint32_from_array(fcp_fid_ie->value->data, + fcp_fid_ie->value->len); + + /* Extract description to determine type (EF or DF) to decide which + * access rules to apply */ + /* FIXME #57: After FID decoding, this is the second step that is performed + * twice, here and in ss_fs_create; consider refactoring. */ + fcp_file_descr_ie = + ss_btlv_get_ie_minlen(fcp_tmpl_ie->nested, + TS_102_221_IEI_FCP_FILE_DESCR, 2); + if (!fcp_file_descr_ie) { + SS_LOGP(SADMIN, LERROR, "missing FILE DESCR template -- cannot create file\n"); + goto err_inval; + } + if (ss_fcp_dec_file_descr(&file_descr, fcp_file_descr_ie->value) < 0) { + SS_LOGP(SADMIN, LERROR, "malformed FILE DESCR template -- cannot create file\n"); + goto err_inval; + } + + SS_LOGP(SADMIN, LDEBUG, "file descriptor:\n"); + ss_fcp_dump_file_descr(&file_descr, 1, SFILE, LDEBUG); + + rewind_to_df(apdu); + + if (!ss_access_check_command(apdu, + file_descr.type == SS_FCP_DF_OR_ADF ? + SS_ACCESS_INTENTION_DF_CREATE_DF : + SS_ACCESS_INTENTION_DF_CREATE_EF + )) + { + ss_btlv_free(fcp_decoded); + return SS_SW_ERR_CMD_NOT_ALLOWED_NO_INFO; + } + + /* FIXME #58: check we don't already have a file with that FID or SFID */ + + fcp_reencoded = ss_btlv_encode_to_ss_buf(fcp_decoded); + if (!fcp_reencoded) { + SS_LOGP(SADMIN, LERROR, "unable to reencode FCP -- cannot create file\n"); + goto err_inval; + } + + rc = ss_fs_create(&apdu->lchan->fs_path, fcp_reencoded->data, fcp_reencoded->len); + ss_btlv_free(fcp_decoded); + ss_buf_free(fcp_reencoded); + + if (rc < 0) + return SS_SW_ERR_WRONG_PARAM_FUNCTION_NOT_SUPPORTED; + + /* Select the file we just created so that the file definition is + * reloaded and properly parsed. */ + rc = ss_fs_select(&apdu->lchan->fs_path, fid); + if (rc < 0) { + ss_storage_delete(&apdu->lchan->fs_path); + return SS_SW_ERR_EXEC_MEMORY_PROBLEM; + } + + /* Create lookup files, register FID. */ + selected_file = ss_get_file_from_path(&apdu->lchan->fs_path); + if (selected_file->fcp_file_descr->type == SS_FCP_DF_OR_ADF) { + rc = ss_sfi_create(&apdu->lchan->fs_path); + rc += ss_df_name_update(&apdu->lchan->fs_path); + } else { + rc = ss_sfi_update(&apdu->lchan->fs_path); + } + if (rc < 0) { + ss_storage_delete(&apdu->lchan->fs_path); + return SS_SW_ERR_EXEC_MEMORY_PROBLEM; + } + + return 0; + +err_inval: + ss_btlv_free(fcp_decoded); + + return SS_SW_ERR_WRONG_PARAM_INCORRECT_DATA; +} + +/*! DELETE FILE (TS 102 222 Section 6.4) */ +int ss_uicc_admin_cmd_delete_file(struct ss_apdu *apdu) +{ + uint32_t fid; + int rc; + + if (apdu->hdr.p1 != 0 || apdu->hdr.p2 != 0) + return SS_SW_ERR_CHECKING_WRONG_P1_P2; + + if (apdu->lc != 2) + return SS_SW_ERR_CHECKING_WRONG_LENGTH; + + fid = ss_uint32_from_array(apdu->cmd, apdu->lc); + + rewind_to_df(apdu); + + /* Not checking for SS_ACCESS_INTENTION_DF_DELETE_FILE (child) on the + * container as per TS 102 222 V6.6.0 6.4.1: 'The access condition "DELETE + * FILE (child)" shall not be used.' */ + + rc = ss_fs_select(&apdu->lchan->fs_path, fid); + if (rc < 0) + return SS_SW_ERR_WRONG_PARAM_FILE_NOT_FOUND; + + if (!ss_access_check_command(apdu, SS_ACCESS_INTENTION_EFDF_DELETE_FILE_SELF)) + return SS_SW_ERR_CMD_NOT_ALLOWED_NO_INFO; + + rc = ss_storage_delete(&apdu->lchan->fs_path); + if (rc < 0) + return SS_SW_ERR_EXEC_MEMORY_PROBLEM; + + /* make sure that the file we just deleted is no longer selected. */ + ss_fs_select_parent(&apdu->lchan->fs_path); + + return 0; +} + +/*! ACTIVATE FILE (TS 102 221 Section 11.1.15) + * + * This is implemented only to the extent to which it is necessary to bring the + * MF from the personalization into the operational phase. + * */ +int ss_uicc_admin_cmd_activate_file(struct ss_apdu *apdu) +{ + uint32_t fid; + int rc; + + /* P1 would have allowed values, but they are not implemented here */ + if (apdu->hdr.p1 != 0 || apdu->hdr.p2 != 0) + return SS_SW_ERR_CHECKING_WRONG_P1_P2; + + /* Different lengths would be allowed, but are not implemented here */ + if (apdu->lc != 2) + return SS_SW_ERR_CHECKING_WRONG_LENGTH; + + fid = ss_uint32_from_array(apdu->cmd, apdu->lc); + + rc = ss_fs_select(&apdu->lchan->fs_path, fid); + if (rc < 0) + return SS_SW_ERR_WRONG_PARAM_FILE_NOT_FOUND; + + /* Practically, this will likely not be set -- but in personalization mode it + * can be set anyway. */ + if (!ss_access_check_command(apdu, SS_ACCESS_INTENTION_EFDF_ACTIVATE_FILE)) + return SS_SW_ERR_CMD_NOT_ALLOWED_NO_INFO; + + struct ber_tlv_ie *fcp_decoded_lifecycle; + struct ss_file *selected_file = ss_get_file_from_path(&apdu->lchan->fs_path); + fcp_decoded_lifecycle = + ss_btlv_get_ie_minlen(selected_file->fcp_decoded, TS_102_221_IEI_FCP_LIFE_CYCLE_ST, 1); + fcp_decoded_lifecycle->value->data[0] = 0x05; + + /* Store encoded FCP again */ + rc = ss_fcp_reencode(selected_file); + if (rc < 0) + return SS_SW_ERR_EXEC_MEMORY_PROBLEM; + + rc = ss_storage_update_def(&apdu->lchan->fs_path); + if (rc < 0) + return SS_SW_ERR_EXEC_MEMORY_PROBLEM; + + return 0; +} + +/*! TODO #63: implemet the following missing commands: + * - TERMINATE DF (TS 102 222 Section 6.7) + * - TERMINATE EF (TS 102 222 Section 6.8) + * - TERMINATE CARD USAGE (TS 102 222 Section 6.9) + * - RESIZE FILE (TS 102 222 Section 6.10) */ diff --git a/src/softsim/uicc/uicc_admin.h b/src/softsim/uicc/uicc_admin.h new file mode 100644 index 0000000..0059439 --- /dev/null +++ b/src/softsim/uicc/uicc_admin.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +int ss_uicc_admin_cmd_create_file(struct ss_apdu *apdu); +int ss_uicc_admin_cmd_delete_file(struct ss_apdu *apdu); +int ss_uicc_admin_cmd_activate_file(struct ss_apdu *apdu); diff --git a/src/softsim/uicc/uicc_auth.c b/src/softsim/uicc/uicc_auth.c new file mode 100644 index 0000000..fead262 --- /dev/null +++ b/src/softsim/uicc/uicc_auth.c @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "sw.h" +#include "uicc_lchan.h" +#include "../milenage/milenage.h" +#include "../milenage/milenage_usim.h" +#include "command.h" +#include "uicc_ins.h" +#include "apdu.h" +#include "fs.h" +#include "fs_utils.h" + +/* 3GPP TS 31.102 Table 7.1.2-1 + Table 7.1.2-2 */ +enum usim_auth_ctx { + USIM_AUTH_CTX_GSM = 0, + USIM_AUTH_CTX_3G = 1, + USIM_AUTH_CTX_VGCS_VBS = 2, + USIM_AUTH_CTX_GBA = 4, + USIM_AUTH_CTX_MBMS = 5, + USIM_AUTH_CTX_LOCAL_KEY = 6, +}; + +/* convenience struct for the response APDU */ +struct auth_res_success_3g { + uint8_t tag; + uint8_t res_len; + uint8_t res[8]; + uint8_t ck_len; + uint8_t ck[16]; + uint8_t ik_len; + uint8_t ik[16]; + /* Generally we do make service 27 available; consequently, that bit + * needs to be set when configuring the file system. */ + uint8_t kc_len; + uint8_t kc[8]; +} __attribute__ ((packed)); + +#define KEY_DATA_FID 0xA001 +#define SEQ_DATA_FID 0xA002 + +/* Populate key data from file */ +static int get_key_data(struct milenage_key_data *key_data) +{ + struct ss_list key_data_path; + struct ss_buf *key_data_raw; + int rc; + + /* File format: + * |16 byte K|16 byte OP/OPc|1 byte flag| + * flag: 0x01 = OP/OPc is OP, 0x00 = OP/OPc is OPc */ + + ss_fs_init(&key_data_path); + rc = ss_fs_select(&key_data_path, KEY_DATA_FID); + if (rc < 0) { + SS_LOGP(SAUTH, LERROR, + "key data file (%04x) not found -- abort\n", + KEY_DATA_FID); + ss_path_reset(&key_data_path); + return -EINVAL; + } + + key_data_raw = + ss_storage_read_file(&key_data_path, 0, + ss_storage_get_file_len(&key_data_path)); + if (!key_data_raw) { + SS_LOGP(SAUTH, LERROR, + "key data file (%s) not readable -- abort\n", + ss_fs_utils_dump_path(&key_data_path)); + ss_path_reset(&key_data_path); + return -EINVAL; + } + + if (key_data_raw->len < sizeof(key_data->k) + sizeof(key_data->opc) + 1) { + SS_LOGP(SAUTH, LERROR, + "key data file (%s) too short -- abort\n", + ss_fs_utils_dump_path(&key_data_path)); + ss_path_reset(&key_data_path); + ss_buf_free(key_data_raw); + return -EINVAL; + } + + memcpy(key_data->k, key_data_raw->data, sizeof(key_data->k)); + memcpy(key_data->opc, key_data_raw->data + sizeof(key_data->k), + sizeof(key_data->opc)); + if (key_data_raw->data[sizeof(key_data->k) + sizeof(key_data->opc)] == + 0x01) + key_data->opc_is_op = true; + else + key_data->opc_is_op = false; + + SS_LOGP(SAUTH, LDEBUG, "key data file (%s) loaded\n", + ss_fs_utils_dump_path(&key_data_path)); + + ss_path_reset(&key_data_path); + ss_buf_free(key_data_raw); + return 0; +} + +/* Populate SEQ data from file */ +static int get_seq_data(struct milenage_seq_data *seq_data) +{ + struct ss_list seq_data_path; + struct ss_buf *seq_data_raw; + int rc; + + /* File format: + * | 32 64-bit SEQ_M values | 64-bit delta | + * (all big endian) + * */ + + ss_fs_init(&seq_data_path); + rc = ss_fs_select(&seq_data_path, SEQ_DATA_FID); + if (rc < 0) { + SS_LOGP(SAUTH, LERROR, + "seq data file (%04x) not found -- abort\n", + KEY_DATA_FID); + ss_path_reset(&seq_data_path); + return -EINVAL; + } + + seq_data_raw = + ss_storage_read_file(&seq_data_path, 0, + ss_storage_get_file_len(&seq_data_path)); + if (!seq_data_raw) { + SS_LOGP(SAUTH, LERROR, + "seq data file (%s) not readable -- abort\n", + ss_fs_utils_dump_path(&seq_data_path)); + ss_path_reset(&seq_data_path); + return -EINVAL; + } + + if (seq_data_raw->len < sizeof(seq_data->seq) + sizeof(seq_data->delta)) { + SS_LOGP(SAUTH, LERROR, + "seq data file (%s) too short -- abort\n", + ss_fs_utils_dump_path(&seq_data_path)); + ss_path_reset(&seq_data_path); + ss_buf_free(seq_data_raw); + return -EINVAL; + } + + for (int i = 0; i < SS_ARRAY_SIZE(seq_data->seq); ++i) + seq_data->seq[i] = ss_uint64_load_from_be(&seq_data_raw->data[i * 8]); + seq_data->delta = ss_uint64_load_from_be(&seq_data_raw->data[SS_ARRAY_SIZE(seq_data->seq) * 8]); + + SS_LOGP(SAUTH, LDEBUG, "seq data file (%s) loaded\n", + ss_fs_utils_dump_path(&seq_data_path)); + ss_path_reset(&seq_data_path); + ss_buf_free(seq_data_raw); + return 0; +} + +/* Sync SEQ data back to file */ +static int update_seq_data(struct milenage_seq_data *seq_data) +{ + struct ss_list seq_data_path; + uint8_t seq_data_raw[sizeof(seq_data->seq) + sizeof(seq_data->delta)]; + int rc; + + ss_fs_init(&seq_data_path); + rc = ss_fs_select(&seq_data_path, SEQ_DATA_FID); + if (rc < 0) { + SS_LOGP(SAUTH, LERROR, + "seq data file (%04x) not found -- abort\n", + KEY_DATA_FID); + ss_path_reset(&seq_data_path); + return -EINVAL; + } + + for (int i = 0; i < SS_ARRAY_SIZE(seq_data->seq); ++i) + ss_uint64_store_to_be(&seq_data_raw[i * 8], seq_data->seq[i]); + ss_uint64_store_to_be(&seq_data_raw[SS_ARRAY_SIZE(seq_data->seq) * 8], seq_data->delta); + + rc = ss_storage_write_file(&seq_data_path, seq_data_raw, 0, + sizeof(seq_data_raw)); + if (rc < 0) { + SS_LOGP(SAUTH, LERROR, + "seq data file (%s) not writeable -- abort\n", + ss_fs_utils_dump_path(&seq_data_path)); + ss_path_reset(&seq_data_path); + return -EINVAL; + } + + SS_LOGP(SAUTH, LDEBUG, "seq data file (%s) updated\n", + ss_fs_utils_dump_path(&seq_data_path)); + ss_path_reset(&seq_data_path); + return 0; +} + +/* determine OPc: Either we have OPC already in akd, or we generate it from K+OP */ +static int gen_opc(uint8_t *opc, const struct milenage_key_data *akd) +{ + if (akd->opc_is_op) { + return milenage_opc_gen(opc, akd->k, akd->opc); + } else { + memcpy(opc, akd->opc, sizeof(akd->opc)); + return 0; + } +} + +static int authenticate_milenage(struct ss_apdu *apdu, enum usim_auth_ctx auth_ctx, + const uint8_t *rand, uint8_t rand_len, + const uint8_t *autn, uint8_t autn_len) +{ + struct milenage_key_data mkd_storage; + struct milenage_key_data *mkd = &mkd_storage; + struct milenage_seq_data msd_storage; + struct milenage_seq_data *msd = &msd_storage; + struct milenage_result mres; + struct auth_res_success_3g *res_3g; + int rc; + + /* Load key material and SEQ from file */ + rc = get_key_data(mkd); + if (rc < 0) + return -EINVAL; + rc = get_seq_data(msd); + if (rc < 0) + return -EINVAL; + + u8 opc[16]; + gen_opc(opc, mkd); + + memset(&mres, 0, sizeof(mres)); + + switch (auth_ctx) { + case USIM_AUTH_CTX_GSM: + if (rand_len != 128/8) { + SS_LOGP(SAUTH, LERROR, "unexpected RAND len -- authentication failed\n"); + goto out_err; + } + /* actually perform authentication */ + rc = gsm_milenage(opc, mkd->k, rand, &apdu->rsp[1], &apdu->rsp[1+4+1]); + if (rc < 0) { + SS_LOGP(SAUTH, LERROR, "milenage computation failed -- authentication failed\n"); + goto out_err; + } + /* put together response data */ + apdu->rsp[0] = 4; /* length of SRES */ + apdu->rsp[1+4] = 8; /* length of Kc */ + apdu->rsp_len = 1 + 4 + 1 + 8; + break; + case USIM_AUTH_CTX_3G: + if (rand_len != 128/8) { + SS_LOGP(SAUTH, LERROR, "unexpected RAND len -- authentication failed\n"); + goto out_err; + } + if (autn_len != 128/8) { + SS_LOGP(SAUTH, LERROR, "unexpected AUTN len -- authentication failed\n"); + goto out_err; + } + /* actually perform authentication */ + rc = milenage_usim_check(mkd, msd, &mres, rand, autn); + switch (rc) { + case 0: /* successful case */ + SS_LOGP(SAUTH, LDEBUG, "Milenage successful\n"); + assert(mres.res_len == 8); + /* FIXME #59: update SEQ bucket for IND */ + /* generate response */ + res_3g = (struct auth_res_success_3g *) apdu->rsp; + res_3g->tag = 0xDB; + res_3g->res_len = mres.res_len; + memcpy(res_3g->res, mres.res, mres.res_len); + res_3g->ck_len = sizeof(mres.ck); + memcpy(res_3g->ck, mres.ck, sizeof(mres.ck)); + res_3g->ik_len = sizeof(mres.ik); + memcpy(res_3g->ik, mres.ik, sizeof(mres.ik)); + + res_3g->kc_len = 8; + rc = gsm_milenage(opc, mkd->k, rand, NULL, res_3g->kc); + if (rc < 0) { + SS_LOGP(SAUTH, LERROR, "Failing milenage: GSM Kc could not be derived.\n"); + goto out_err; + } + + /* Sync SEQ to file */ + rc = update_seq_data(msd); + if (rc < 0) { + SS_LOGP(SAUTH, LERROR, "Failing milenage: Sequence data could not be stored\n"); + return -EINVAL; + } + + apdu->rsp_len = sizeof(*res_3g); + return 0; + case -2: + SS_LOGP(SAUTH, LINFO, "Milenage requesting resync (returning AUTS)\n"); + apdu->rsp[0] = 0xDC; + apdu->rsp[1] = sizeof(mres.auts); + memcpy(&apdu->rsp[2], mres.auts, sizeof(mres.auts)); + apdu->rsp_len = 2 + sizeof(mres.auts); + return 0; + default: + SS_LOGP(SAUTH, LERROR, "authentication failed\n"); + break; + } + break; + default: + assert(0); + } + +out_err: + memset(&mres, 0, sizeof(mres)); + return -1; +} + +/* AUTHENTICATE, see 3GPP TS 31.102 Section 7.1 */ +int ss_uicc_auth_cmd_authenticate_even_fn(struct ss_apdu *apdu) +{ + uint8_t auth_ctx; + const uint8_t *rand, *autn; + uint8_t rand_len, autn_len; + + if (apdu->hdr.p1 != 0x00) { + apdu->sw = SS_SW_ERR_CHECKING_WRONG_P1_P2; + return 0; + } + + if (!(apdu->hdr.p2 & 0x80)) { + apdu->sw = SS_SW_ERR_CHECKING_WRONG_P1_P2; + return 0; + } + + /* we only support GSM and 3G context */ + auth_ctx = apdu->hdr.p2 & 0x07; + switch (auth_ctx) { + case USIM_AUTH_CTX_GSM: + case USIM_AUTH_CTX_3G: + break; + default: + apdu->sw = SS_SW_ERR_CHECKING_WRONG_P1_P2; + return 0; + } + + if (apdu->lc < 1) + goto err_len; + rand_len = apdu->cmd[0]; + + if (apdu->lc < 1 + rand_len) + goto err_len; + rand = &apdu->cmd[1]; + + if (auth_ctx == USIM_AUTH_CTX_3G) { + if (apdu->lc < 1 + rand_len + 1) + goto err_len; + autn_len = apdu->cmd[1+rand_len]; + + if (apdu->lc < 1 + rand_len + 1 + autn_len) + goto err_len; + autn = &apdu->cmd[1+rand_len+1]; + } else { + autn_len = 0; + autn = NULL; + } + + return authenticate_milenage(apdu, auth_ctx, rand, rand_len, autn, autn_len); + +err_len: + apdu->sw = SS_SW_ERR_CHECKING_WRONG_LENGTH; + return 0; +} diff --git a/src/softsim/uicc/uicc_auth.h b/src/softsim/uicc/uicc_auth.h new file mode 100644 index 0000000..7adbd71 --- /dev/null +++ b/src/softsim/uicc/uicc_auth.h @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +int ss_uicc_auth_cmd_authenticate_even_fn(struct ss_apdu *apdu); diff --git a/src/softsim/uicc/uicc_cat.c b/src/softsim/uicc/uicc_cat.c new file mode 100644 index 0000000..104942b --- /dev/null +++ b/src/softsim/uicc/uicc_cat.c @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "sw.h" +#include "command.h" +#include "uicc_cat.h" +#include "uicc_sms_rx.h" +#include "uicc_ins.h" +#include "uicc_lchan.h" +#include "apdu.h" +#include "context.h" +#include "btlv.h" +#include "ctlv.h" +#include "sms.h" + +/*! handler to handle CAT enevelope commands */ +struct ss_cat_envelope_command { + /*! human readable name that describes handler function. */ + const char *name; + /*! IEI of the CAT template that this handler function is processing */ + uint32_t iei; + /*! expected minimum length of the CAT template (prevent noise and invalid data) */ + uint32_t minlen; + /*! CLA and MASK against which to compare CLA from APDU header */ + int (*handler)(struct ss_apdu *apdu, struct ss_buf *cat_template); +}; + +/* Handler function to handle CAT SMS PP Download */ +int handle_sms_pp_dwnld(struct ss_apdu *apdu, struct ss_buf *cat_template) +{ + struct ss_list *ctlv_data = NULL; + struct cmp_tlv_ie *sms_tpdu_ie; + int rc; + + ctlv_data = ss_ctlv_decode(cat_template->data, cat_template->len); + if (!ctlv_data) { + SS_LOGP(SPROACT, LERROR, + "failed to decode COMPREHENSION-TLV encoded SMS-PP data\n"); + return SS_SW_ERR_WRONG_PARAM_INCORRECT_DATA; + } + ss_ctlv_dump(ctlv_data, 2, SPROACT, LDEBUG); + + sms_tpdu_ie = ss_ctlv_get_ie(ctlv_data, TS_101_220_IEI_SMS_TPDU); + if (!sms_tpdu_ie) { + SS_LOGP(SPROACT, LERROR, + "failed to receive SMS-PP, SMS-TPDU IE missing\n"); + rc = SS_SW_ERR_WRONG_PARAM_INCORRECT_DATA; + goto leave; + } + + apdu->rsp_len = SS_ARRAY_SIZE(apdu->rsp); + rc = ss_uicc_sms_rx(apdu->ctx, + sms_tpdu_ie->value, &apdu->rsp_len, apdu->rsp); + if (rc != 0) + apdu->rsp_len = 0; + +leave: + ss_ctlv_free(ctlv_data); + return 0; +} + +const struct ss_cat_envelope_command cat_envelope_commands[] = { + { + .name = "SMS PP DOWNLOAD", + .iei = TS_101_220_IEI_SMS_PP_DWNLD, + .handler = handle_sms_pp_dwnld, + .minlen = 3, + }, +}; + +/*! TERMINAL PROFILE (TS 102 221 Section 11.2.1) */ +int ss_uicc_cat_cmd_term_profile(struct ss_apdu *apdu) +{ + size_t term_profile_len; + + /* Clear old profile */ + memset(apdu->ctx->proactive.term_profile, 0, + sizeof(apdu->ctx->proactive.term_profile)); + + /* Store profile */ + if (apdu->lc <= sizeof(apdu->ctx->proactive.term_profile)) { + term_profile_len = apdu->lc; + } else { + /* Note: the buffer size is chosen large enough, so that this + * error should never occur. */ + SS_LOGP(SPROACT, LERROR, + "transmitted TERMINAL PROFILE too large for internal buffer\n"); + term_profile_len = sizeof(apdu->ctx->proactive.term_profile); + } + memcpy(apdu->ctx->proactive.term_profile, apdu->cmd, term_profile_len); + + /* Enable proactive behaviour globally */ + apdu->ctx->proactive.enabled = true; + + return 0; +} + +/*! ENVELOPE (TS 102 221 Section 11.2.2) */ +int ss_uicc_cat_cmd_envelope(struct ss_apdu *apdu) +{ + struct ss_list *envelope = NULL; + int rc = 0; + + unsigned int i; + struct ber_tlv_ie *cat_template; + + SS_LOGP(SPROACT, LDEBUG, "Data fed into BTLV: %s\n", ss_hexdump(apdu->cmd, apdu->hdr.p3)); + size_t data_len = apdu->hdr.p3; /* The announced length */ + /* Let's not read outside initialized data */ + if (apdu->lc < data_len) { + SS_LOGP(SPROACT, LERROR, "Data length anounced in P3 exceeds available data\n"); + return SS_SW_ERR_WRONG_PARAM_INCORRECT_DATA; + } + envelope = + ss_btlv_decode(apdu->cmd, data_len, ss_proactive_get_cat_descr()); + if (!envelope) { + SS_LOGP(SPROACT, LERROR, + "failed to decode BER-TLV encoded envelope\n"); + return SS_SW_ERR_WRONG_PARAM_INCORRECT_DATA; + } + ss_btlv_dump(envelope, 2, SPROACT, LDEBUG); + + rc = SS_SW_ERR_WRONG_PARAM_FUNCTION_NOT_SUPPORTED; + for (i = 0; i < SS_ARRAY_SIZE(cat_envelope_commands); i++) { + cat_template = + ss_btlv_get_ie_minlen(envelope, + cat_envelope_commands[i].iei, + cat_envelope_commands[i].minlen); + if (cat_template) { + SS_LOGP(SPROACT, LDEBUG, + "executing handler function for CAT TEMPLATE %02x: %s\n", + cat_envelope_commands[i].iei, + cat_envelope_commands[i].name); + rc = cat_envelope_commands[i].handler(apdu, + cat_template-> + value); + } + } + + ss_btlv_free(envelope); + return rc; +} + +/*! FETCH (TS 102 221 Section 11.2.3) */ +int ss_uicc_cat_cmd_fetch(struct ss_apdu *apdu) +{ + int rc = 0; + + if (!apdu->ctx->proactive.enabled) { + apdu->le = 0; + rc = SS_SW_ERR_WRONG_PARAM_FUNCTION_NOT_SUPPORTED; + goto leave; + } + + memcpy(apdu->rsp, apdu->ctx->proactive.data, + apdu->ctx->proactive.data_len); + apdu->rsp_len = apdu->ctx->proactive.data_len; + +leave: + /* Mark data as consumed so that the status word will no longer + * announce proactive data. */ + apdu->ctx->proactive.data_len = 0; + + return rc; +} + +/*! TERMINAL RESPONSE (TS 102 221 Section 11.2.4) */ +int ss_uicc_cat_cmd_term_resp(struct ss_apdu *apdu) +{ + if (!apdu->ctx->proactive.enabled) + return SS_SW_ERR_WRONG_PARAM_FUNCTION_NOT_SUPPORTED; + + + term_resp_cb callback = apdu->ctx->proactive.term_resp_cb; + ss_proactive_reset(apdu->ctx); + callback(apdu->ctx, apdu->cmd, apdu->lc); + + return 0; +} diff --git a/src/softsim/uicc/uicc_cat.h b/src/softsim/uicc/uicc_cat.h new file mode 100644 index 0000000..1c94eaa --- /dev/null +++ b/src/softsim/uicc/uicc_cat.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +int ss_uicc_cat_cmd_term_profile(struct ss_apdu *apdu); +int ss_uicc_cat_cmd_envelope(struct ss_apdu *apdu); +int ss_uicc_cat_cmd_fetch(struct ss_apdu *apdu); +int ss_uicc_cat_cmd_term_resp(struct ss_apdu *apdu); diff --git a/src/softsim/uicc/uicc_file_ops.c b/src/softsim/uicc/uicc_file_ops.c new file mode 100644 index 0000000..28328bf --- /dev/null +++ b/src/softsim/uicc/uicc_file_ops.c @@ -0,0 +1,1142 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "access.h" +#include "sw.h" +#include "fs.h" +#include "fs_utils.h" +#include "command.h" +#include "uicc_file_ops.h" +#include "uicc_ins.h" +#include "uicc_lchan.h" +#include "fcp.h" +#include "sfi.h" +#include "df_name.h" +#include "apdu.h" +#include "btlv.h" +#include "uicc_pin.h" +#include "context.h" + +#define FID_CURRENT_APP 0x7fff + +/* Record a file change in the context file list. This data can be used to + * inform the terminal about file changes at a later point + * (e.g. REFRESH after RFM operation) */ +static void record_file_change(struct ss_apdu *apdu) +{ + int rc = 0; + + if (apdu->ctx->fs_chg_record) + rc = ss_fs_chg_add(apdu->ctx->fs_chg_filelist, &apdu->lchan->fs_path); + if (rc < 0) + SS_LOGP(SFILE, LERROR,"file change not recorded!\n"); +} + +/* Generate the FCP string as it is returned by the card to the outside world. + * This is essentially the FCP string that is read from the definition files + * but with additional IEs that reflect the current card state (Pin status) */ +static int fcp_reencode_full(struct ss_file *selected_file, char *command_name) +{ + struct ber_tlv_ie *pin_stat_templ_ie; + struct ss_buf *pin_stat_templ; + int rc; + + pin_stat_templ_ie = + ss_btlv_get_ie(selected_file->fcp_decoded, + TS_102_221_IEI_FCP_PIN_STAT_TMPL); + if (!pin_stat_templ_ie) { + pin_stat_templ = ss_uicc_pin_gen_pst_do(); + if (!pin_stat_templ) { + SS_LOGP(SFILE, LDEBUG, + "%s failed, could not generate PIN status template.\n", + command_name); + return -EINVAL; + } + pin_stat_templ_ie = ss_btlv_new_ie(selected_file->fcp_decoded, + "pin_status_template_do", + TS_102_221_IEI_FCP_PIN_STAT_TMPL, + pin_stat_templ->len, + pin_stat_templ->data); + ss_buf_free(pin_stat_templ); + + /* Note: There is no need to free the IE that we have just + * created. Since it is now liked into the TLV tree it will + * be freed along with all other elements in the tree when the + * file struct is freed. Contrary to the IE, the pin_stat_templ, + * we use as input for ss_btlv_new_ie(), must be freed. */ + + rc = ss_fcp_reencode(selected_file); + if (rc < 0) { + SS_LOGP(SFILE, LDEBUG, + "%s failed, unable to re-encode FCP data.\n", + command_name); + return -EINVAL; + } + } else { + rc = ss_uicc_pin_update_pst_do(pin_stat_templ_ie->value); + if (rc < 0) { + SS_LOGP(SFILE, LDEBUG, + "%s failed, could not update PIN status template.\n", + command_name); + return -EINVAL; + } + rc = ss_fcp_reencode(selected_file); + if (rc < 0) { + SS_LOGP(SFILE, LDEBUG, + "%s failed, unable to re-encode FCP data.\n", + command_name); + return -EINVAL; + } + } + + return 0; +} + +/*! STATUS (TS 102 221 Section 11.1.2) */ +int ss_uicc_file_ops_cmd_status(struct ss_apdu *apdu) +{ + struct ss_file *current_df; + struct ss_file *active_adf; + struct ber_tlv_ie *df_name; + int rc; + + switch (apdu->hdr.p1) { + case 0x00: /* no indication */ + case 0x01: /* Current application is initialized in the terminal */ + case 0x02: /* The terminal will initiate the termination of the current application */ + break; + default: + return SS_SW_ERR_CHECKING_WRONG_P1_P2; + } + + switch (apdu->hdr.p2) { + case 0x00: + current_df = ss_fs_utils_get_current_df_from_path(&apdu->lchan->fs_path); + if (!current_df) + return SS_SW_ERR_CMD_NOT_ALLOWED_NO_EF_SELECTED; + + rc = fcp_reencode_full(current_df, "status"); + if (rc < 0) { + SS_LOGP(SFILE, LDEBUG, + "status failed, unable to re-encode FCP data.\n"); + return SS_SW_ERR_EXEC_MEMORY_PROBLEM; + } + + if (apdu->le != current_df->fci->len) { + apdu->le = 0; + SS_LOGP(SFILE, LDEBUG, "Terminal requested status expecting length %u, returning actual FCI length %u\n", + apdu->le, + (unsigned)current_df->fci->len + ); + return 0x6c00 | (current_df->fci->len); + } + + memcpy(apdu->rsp, current_df->fci->data, + current_df->fci->len); + apdu->rsp_len = current_df->fci->len; + break; + case 0x01: + active_adf = ss_get_file_from_path(&apdu->lchan->adf_path); + if (!active_adf) + return SS_SW_ERR_CMD_NOT_ALLOWED_NO_EF_SELECTED; + + df_name = ss_btlv_get_ie(active_adf->fcp_decoded, TS_102_221_IEI_FCP_DF_NAME); + if (df_name) { + apdu->rsp[0] = TS_102_221_IEI_FCP_DF_NAME; + apdu->rsp[1] = df_name->value->len; + memcpy(apdu->rsp + 2, df_name->value->data, df_name->value->len); + apdu->rsp_len = df_name->value->len + 2; + return 0; + } + + return SS_SW_ERR_CMD_NOT_ALLOWED_NO_EF_SELECTED; + case 0x0c: + /* no data is returned */ + break; + default: + return SS_SW_ERR_CHECKING_WRONG_P1_P2; + } + + return 0; +} + +/* Calculate the read/write offset for a transparent file */ +static size_t calc_read_write_offset(struct ss_apdu *apdu) +{ + size_t result = apdu->hdr.p2; + + /* See also ETSI TS 102 221, table 11.10 */ + if ((apdu->hdr.p1 & 0x80) == 0) + result |= (apdu->hdr.p1 << 8); + + return result; +} + +/* Make sure that it is valid to operate with READ BINARY and UPDATE BINARY on + * the specified file. */ +static int verify_file_struct(struct ss_apdu *apdu, struct ss_file *file, + bool record_oriented) +{ + if (!file) { + apdu->le = 0; + return SS_SW_ERR_CMD_NOT_ALLOWED_NO_EF_SELECTED; + } + + if (file->fcp_file_descr->type != SS_FCP_WORKING_EF) { + apdu->le = 0; + return SS_SW_ERR_CMD_NOT_ALLOWED_INCOMP_FILE_STRUCT; + } + + if (record_oriented) { + if (file->fcp_file_descr->structure != SS_FCP_LINEAR_FIXED + && file->fcp_file_descr->structure != SS_FCP_CYCLIC) { + apdu->le = 0; + return SS_SW_ERR_CMD_NOT_ALLOWED_INCOMP_FILE_STRUCT; + } + } else { + if (file->fcp_file_descr->structure != SS_FCP_TRANSPARENT) { + apdu->le = 0; + return SS_SW_ERR_CMD_NOT_ALLOWED_INCOMP_FILE_STRUCT; + } + } + + return 0; +} + +/* Select a file by SFI + * + * This is usually used through \ref select_by_sfi_binarystyle or similar. + * + * \param[inout] apdu Current command, including lchan + * \param[in] sfi SFI dissected from the command's P1 / P2. + * + * On error, the ADPU is configured for immediate error return. + * + * \return 0 if successful, or a status word to immediately return + * */ +static int select_by_sfi(struct ss_apdu *apdu, uint8_t sfi) +{ + int rc = ss_sfi_resolve(&apdu->lchan->fs_path, sfi); + if (rc < 0) { + apdu->le = 0; + return SS_SW_ERR_CMD_NOT_ALLOWED_NO_EF_SELECTED; + } + ss_fs_select(&apdu->lchan->fs_path, rc); + ss_access_populate(apdu->lchan); + + /* Reset current record for record oriented files. */ + apdu->lchan->current_record = 0; + + return 0; +} + +/* Select a file by SFI if present in P1 + * + * This is applicable for READ BINARY and similar commands that follow IEC-7816 + * section 7.2.2, in which bit 1 of INS is 0. + * + * This acts like \ref select_by_sfi, but also return 0 immediately if no SFI + * is indicated. + */ +static int select_by_sfi_binarystyle(struct ss_apdu *apdu) +{ + if (apdu->hdr.p1 & 0x80) + return select_by_sfi(apdu, apdu->hdr.p1 & 0x1f); + return 0; +} + +/* Select a file by SFI if present in P2 + * + * This is applicable for READ RECORD and similar commands that follow IEC-7816 + * table 49. + * + * This acts like \ref select_by_sfi, but also return 0 immediately if no SFI + * is indicated. + */ +static int select_by_sfi_recordstyle(struct ss_apdu *apdu) +{ + if (apdu->hdr.p2 & 0xf8) + return select_by_sfi(apdu, apdu->hdr.p1 & apdu->hdr.p2 >> 3); + return 0; +} + +/*! READ BINARY (TS 102 221 Section 11.1.3) */ +int ss_uicc_file_ops_cmd_read_binary(struct ss_apdu *apdu) +{ + struct ss_file *selected_file; + size_t file_len; + struct ss_buf *buf; + size_t offset; + int rc; + size_t read_len = apdu->le; + + rc = select_by_sfi_binarystyle(apdu); + if (rc != 0) + return rc; + + /* Get currently selected file and verify that the structure is suitable + * to carry out the intended file operation. */ + selected_file = ss_get_file_from_path(&apdu->lchan->fs_path); + rc = verify_file_struct(apdu, selected_file, false); + if (rc != 0) + return rc; + + if (!ss_access_check_command(apdu, SS_ACCESS_INTENTION_EF_READ)) { + apdu->le = 0; + return SS_SW_ERR_CMD_NOT_ALLOWED_NO_INFO; + } + + /* FIXME #60: check invalidated / terminated */ + + offset = calc_read_write_offset(apdu); + file_len = ss_storage_get_file_len(&apdu->lchan->fs_path); + if (offset > file_len) { + apdu->le = 0; + return SS_SW_ERR_CHECKING_WRONG_P1_P2; + } + if (offset + apdu->le > file_len) { + /* return actual length */ + apdu->le = 0; + return 0x6c00 | (file_len - offset); + } + + if (read_len == 0) { + read_len = file_len - offset; + } + + buf = ss_storage_read_file(&apdu->lchan->fs_path, offset, + read_len); + if (!buf) + return SS_SW_ERR_WRONG_PARAM_ENOMEM; + + memcpy(apdu->rsp, buf->data, buf->len); + apdu->rsp_len = buf->len; + ss_buf_free(buf); + + return 0; +} + +/*! UPDATE BINARY (TS 102 221 Section 11.1.4) */ +int ss_uicc_file_ops_cmd_update_binary(struct ss_apdu *apdu) +{ + struct ss_file *selected_file; + size_t file_len; + size_t offset; + int rc; + + rc = select_by_sfi_binarystyle(apdu); + if (rc != 0) + return rc; + + /* Get currently selected file and verify that the structure is suitable + * to carry out the intended file operation. */ + selected_file = ss_get_file_from_path(&apdu->lchan->fs_path); + rc = verify_file_struct(apdu, selected_file, false); + if (rc != 0) + return rc; + + if (!ss_access_check_command(apdu, SS_ACCESS_INTENTION_EF_UPDATE_ERASE)) { + apdu->le = 0; + return SS_SW_ERR_CMD_NOT_ALLOWED_NO_INFO; + } + + /* FIXME #60: check invalidated / terminated */ + + offset = calc_read_write_offset(apdu); + file_len = ss_storage_get_file_len(&apdu->lchan->fs_path); + if (offset > file_len) { + apdu->le = 0; + return SS_SW_ERR_CHECKING_WRONG_P1_P2; + } + if (offset + apdu->lc > file_len) + return SS_SW_ERR_CHECKING_WRONG_LENGTH; + + rc = ss_storage_write_file(&apdu->lchan->fs_path, apdu->cmd, + offset, apdu->lc); + if (rc < 0) + return SS_SW_ERR_WRONG_PARAM_ENOMEM; + + record_file_change(apdu); + + return 0; +} + +/* Calculate the record number depending on the parameters P1, P2 and the + * current record pointer. */ +static int calc_record_number(uint8_t *record_number_new, + uint8_t *record_number, struct ss_apdu *apdu, + struct ss_file *selected_file) +{ + uint8_t n_records = selected_file->fcp_file_descr->number_of_records; + + /* Determine record number */ + switch (apdu->hdr.p2 & 0x07) { + case 0x02: + *record_number = apdu->lchan->current_record; + + /* Only linear cyclic files may wrap around, see also ETSI TS 102 221, + section 11.1.5.1 */ + if (*record_number == 0) { + /* See also ETSI TS 102 221, section 11.1.6.1, parapgraph "PREVIOUS" */ + *record_number = 1; + } else if (*record_number == n_records) { + if (selected_file->fcp_file_descr->structure == + SS_FCP_CYCLIC) + *record_number = 1; + else { + SS_LOGP(SFILE, LERROR,"last record (%u) of %u records, but not a cyclic file (%04x), cannot wrap around\n", + *record_number, n_records, selected_file->fid); + return SS_SW_ERR_WRONG_PARAM_RECORD_NOT_FOUND; + } + } else + (*record_number)++; + + *record_number_new = *record_number; + break; + case 0x03: + *record_number = apdu->lchan->current_record; + + /* See comment above */ + if (*record_number == 0) { + /* See also ETSI TS 102 221, section 11.1.6.1, parapgraph "PREVIOUS" */ + *record_number = n_records; + } else if (*record_number == 1) { + if (selected_file->fcp_file_descr->structure == + SS_FCP_CYCLIC) + *record_number = n_records; + else { + SS_LOGP(SFILE, LERROR,"first record (%u) of %u records, but not a cyclic file (%04x), cannot wrap around\n", + *record_number, n_records, selected_file->fid); + return SS_SW_ERR_WRONG_PARAM_RECORD_NOT_FOUND; + } + } else + (*record_number)--; + + *record_number_new = *record_number; + break; + case 0x04: + *record_number = apdu->hdr.p1; + + /* Keep record number as it is */ + *record_number_new = apdu->lchan->current_record; + break; + default: + return SS_SW_ERR_CHECKING_WRONG_P1_P2; + } + + return 0; +} + +/*! READ RECORD (TS 102 221 Section 11.1.5) */ +int ss_uicc_file_ops_cmd_read_record(struct ss_apdu *apdu) +{ + struct ss_file *selected_file; + struct ss_buf *buf; + uint8_t record_number; + uint8_t record_number_new; + int rc; + + rc = select_by_sfi_recordstyle(apdu); + if (rc != 0) + return rc; + + /* Get currently selected file and verify that the structure is suitable + * to carry out the intended file operation. */ + selected_file = ss_get_file_from_path(&apdu->lchan->fs_path); + rc = verify_file_struct(apdu, selected_file, true); + if (rc != 0) { + apdu->le = 0; + return rc; + } + + if (!ss_access_check_command(apdu, SS_ACCESS_INTENTION_EF_READ)) { + apdu->le = 0; + return SS_SW_ERR_CMD_NOT_ALLOWED_NO_INFO; + } + + /* FIXME #60: check invalidated / terminated */ + + /* Determine record number */ + rc = calc_record_number(&record_number_new, &record_number, apdu, + selected_file); + if (rc != 0) + return rc; + + if (apdu->le != selected_file->fcp_file_descr->record_len) { + /* return actual length */ + apdu->le = 0; + return 0x6c00 | selected_file->fcp_file_descr->record_len; + } + + buf = ss_fs_read_file_record(&apdu->lchan->fs_path, record_number); + if (!buf) + return SS_SW_ERR_WRONG_PARAM_ENOMEM; + + memcpy(apdu->rsp, buf->data, buf->len); + apdu->rsp_len = buf->len; + ss_buf_free(buf); + + /* Everything went successful, update record pointer */ + apdu->lchan->current_record = record_number_new; + return 0; +} + +/*! UPDATE RECORD (TS 102 221 Section 11.1.6) */ +int ss_uicc_file_ops_cmd_update_record(struct ss_apdu *apdu) +{ + struct ss_file *selected_file; + uint8_t record_number; + uint8_t record_number_new; + int rc; + + rc = select_by_sfi_recordstyle(apdu); + if (rc != 0) + return rc; + + /* Get currently selected file and verify that the structure is suitable + * to carry out the intended file operation. */ + selected_file = ss_get_file_from_path(&apdu->lchan->fs_path); + rc = verify_file_struct(apdu, selected_file, true); + if (rc != 0) + return rc; + + if (!ss_access_check_command(apdu, SS_ACCESS_INTENTION_EF_UPDATE_ERASE)) { + apdu->le = 0; + return SS_SW_ERR_CMD_NOT_ALLOWED_NO_INFO; + } + + /* FIXME #60: check invalidated / terminated */ + + /* Determine record number */ + rc = calc_record_number(&record_number_new, &record_number, apdu, + selected_file); + if (rc != 0) + return rc; + + if (apdu->lc != selected_file->fcp_file_descr->record_len) { + return SS_SW_ERR_CMD_NOT_ALLOWED_INCOMP_FILE_STRUCT; + } + + rc = ss_fs_write_file_record(&apdu->lchan->fs_path, record_number, + apdu->cmd, + selected_file->fcp_file_descr->record_len); + if (rc != 0) + return SS_SW_ERR_WRONG_PARAM_ENOMEM; + + /* Everything went successful, update record pointer */ + apdu->lchan->current_record = record_number_new; + + record_file_change(apdu); + + return 0; +} + +enum search_mode { + SEARCH_SIMPLE_FORWARD = 0x04, + SEARCH_SIMPLE_BACKWARD = 0x05, + SEARCH_ENHANCED = 0x06, +}; + +static int find_offset(struct ss_buf *buf, uint8_t search_byte) +{ + uint8_t i; + + /* It makes no sense to find an offset in a string smaller than 2 + * bytes. */ + if (buf->len < 2) + return -EINVAL; + + for (i = 0; i < buf->len - 1; i++) { + /* Search begins after the occurrence of the search byte, + * see also ETSI TS 102 221, table Table 11.13 */ + if (buf->data[i] == search_byte) + return i + 1; + } + + return -EINVAL; +} + +/*! SEARCH RECORD (TS 102 221 Section 11.1.7) */ +int ss_uicc_file_ops_cmd_search_record(struct ss_apdu *apdu) +{ + struct ss_file *selected_file; + uint8_t n_records; + int rc; + struct ss_buf *buf; + uint8_t n_results = 0; + + /* search parameter */ + enum search_mode search_mode; + uint8_t *search_string; + uint8_t search_string_len; + uint8_t search_offset = 0xff; + uint8_t search_byte; + bool search_offset_dyn; + bool search_dir_forward; + uint8_t search_record_number; + uint8_t enchanced_search_mode; + + rc = select_by_sfi_recordstyle(apdu); + if (rc != 0) + return rc; + + /* Get currently selected file and verify that the structure is suitable + * to carry out the intended file operation. */ + selected_file = ss_get_file_from_path(&apdu->lchan->fs_path); + rc = verify_file_struct(apdu, selected_file, true); + if (rc != 0) + return rc; + + n_records = selected_file->fcp_file_descr->number_of_records; + SS_LOGP(SFILE, LDEBUG, "number of records: %u\n", n_records); + + /* See also ETSI TS 102 221, table Table 11.12 */ + search_mode = apdu->hdr.p2 & 0x07; + + switch (search_mode) { + case SEARCH_ENHANCED: + /* See also ETSI TS 102 221, table Table 11.13 */ + enchanced_search_mode = apdu->cmd[0]; + + SS_LOGP(SFILE, LDEBUG, "search mode: \"enhanced search\"\n"); + if (apdu->lc < 3) { + SS_LOGP(SFILE, LERROR, "no search string!\n"); + return SS_SW_ERR_CHECKING_WRONG_P1_P2; + } + + search_string = apdu->cmd + 2; + search_string_len = apdu->lc - 2; + + if ((enchanced_search_mode >> 3 & 1) == 0) { + /* search byte encodes the offset */ + search_offset = apdu->cmd[1]; + search_offset_dyn = false; + } else { + /* search byte defines the offset dynamically + * by its first occurrence. */ + search_byte = apdu->cmd[1]; + search_offset_dyn = true; + } + + /* This search mode does not support using the current record */ + if (apdu->hdr.p1 == 0x00) { + SS_LOGP(SFILE, LERROR, + "record pointer not usable in enhanced search mode!\n"); + return SS_SW_ERR_CHECKING_WRONG_P1_P2; + } + + if (enchanced_search_mode >> 2 & 1) { + if (apdu->hdr.p1 == 0x00) + search_record_number = + apdu->lchan->current_record; + else + search_record_number = apdu->hdr.p1; + } else { + SS_LOGP(SFILE, LERROR, + "invalid enhanced search mode (%02x)!\n", + enchanced_search_mode); + return SS_SW_ERR_WRONG_PARAM_FUNCTION_NOT_SUPPORTED; + } + + switch (enchanced_search_mode & 0x03) { + case 0: + search_dir_forward = true; + break; + case 1: + search_dir_forward = false; + break; + case 2: + /* When we are already at the last record but the search shall begin at + * the following record, then the search makes no sense since there is + * nothing left to search. The command will execute successful though, but + * there will be no search results. */ + if (search_record_number == n_records) { + SS_LOGP(SFILE, LERROR, + "no next record, skipping search...\n"); + return 0; + } + search_record_number++; + search_dir_forward = true; + break; + case 3: + /* See comment above */ + if (search_record_number == 1) { + SS_LOGP(SFILE, LERROR, + "no previous record, skipping search...\n"); + return 0; + } + search_record_number--; + search_dir_forward = false; + break; + default: + /* Numerically unreachable, but -Werror=maybe-uninitialized doesn't know + * that */ + assert(false); + } + + break; + case SEARCH_SIMPLE_FORWARD: + case SEARCH_SIMPLE_BACKWARD: + SS_LOGP(SFILE, LDEBUG, "search mode: \"simple search\"\n"); + if (apdu->lc < 1) { + SS_LOGP(SFILE, LERROR, "no search string!\n"); + return SS_SW_ERR_CHECKING_WRONG_P1_P2; + } + search_string = apdu->cmd; + search_string_len = apdu->lc; + search_offset = 0; + search_offset_dyn = false; + if (apdu->hdr.p1 == 0x00) + search_record_number = apdu->lchan->current_record; + else + search_record_number = apdu->hdr.p1; + + if (search_mode == SEARCH_SIMPLE_FORWARD) + search_dir_forward = true; + else + search_dir_forward = false; + break; + default: + SS_LOGP(SFILE, LERROR, "invalid search mode (%02x)!\n", + search_mode); + return SS_SW_ERR_WRONG_PARAM_FUNCTION_NOT_SUPPORTED; + } + + SS_LOGP(SFILE, LDEBUG, "search parameter:\n"); + SS_LOGP(SFILE, LDEBUG, " search string: %s\n", + ss_hexdump(search_string, search_string_len)); + if (search_offset_dyn) + SS_LOGP(SFILE, LDEBUG, + " search offset: first occurrence of %02x in record\n", + search_byte); + else + SS_LOGP(SFILE, LDEBUG, " search offset: %u\n", search_offset); + SS_LOGP(SFILE, LDEBUG, " search begins at record: %u\n", + search_record_number); + SS_LOGP(SFILE, LDEBUG, " search direction: %s\n", + search_dir_forward ? "forward" : "backward"); + + while (1) { + buf = + ss_fs_read_file_record(&apdu->lchan->fs_path, + search_record_number); + if (!buf) + return SS_SW_ERR_WRONG_PARAM_ENOMEM; + + /* Find dynamic offset */ + if (search_offset_dyn) { + rc = find_offset(buf, search_byte); + if (rc < 0) { + SS_LOGP(SFILE, LDEBUG, + "skipping record %u since it does not contain a byte with vale %02x\n", + search_record_number, search_byte); + goto skip; + } else + search_offset = (uint8_t) rc; + } + + /* Ensure meaningful length parameters */ + if (search_offset >= buf->len) + goto skip; + if (search_string_len > buf->len - search_offset) + goto skip; + + if (memcmp + (search_string, buf->data + search_offset, + search_string_len) == 0) { + SS_LOGP(SFILE, LDEBUG, + "comparing record %u to search string at offset %u <== MATCH\n", + search_record_number, search_offset); + apdu->rsp[n_results] = search_record_number; + n_results++; + } else { + SS_LOGP(SFILE, LDEBUG, + "comparing record %u to search string at offset %u\n", + search_record_number, search_offset); + } + +skip: + ss_buf_free(buf); + + if (search_dir_forward) { + if (search_record_number < n_records) + search_record_number++; + else + break; + } else { + if (search_record_number > 1) + search_record_number--; + else + break; + } + } + + apdu->rsp_len = n_results; + + return 0; +} + +static int select_by_fid(struct ss_apdu *apdu) +{ + uint16_t fid; + int rc; + struct ss_file *selected_file; + + /* See also: ETSI TS 102 221, section 8.4.1 */ + + if (apdu->lc != 2) { + SS_LOGP(SFILE, LDEBUG, "selecting by FID: (invalid)\n"); + return -1; + } + + fid = apdu->cmd[0] << 8 | apdu->cmd[1]; + SS_LOGP(SFILE, LDEBUG, "selecting by FID: %04x\n", fid); + + /* NOTE: The function ss_fs_select() will handle the priority of the + * MF. There is no need to implement it here. */ + + /* Select the currently active application by its alias, + see also ETSI TS 102 221 section 8.4.1 */ + if (fid == FID_CURRENT_APP) { + ss_path_reset(&apdu->lchan->fs_path); + rc = ss_fs_utils_path_select(&apdu->lchan->fs_path, &apdu->lchan->adf_path); + if (rc < 0) + return SS_SW_ERR_WRONG_PARAM_FILE_NOT_FOUND; + goto leave; + } + + /* Try to find a DF or EF in the current directory */ + rc = ss_fs_select(&apdu->lchan->fs_path, fid); + if (rc == 0) { + SS_LOGP(SFILE, LDEBUG, "success: file (%04x) found in the current DF.\n", + fid); + goto leave; + } + + /* A failed select beforehand leaves us with the current DF. If the + * FID of the current DF matches the FID we intend to select, then + * the select was indeed successful. */ + SS_LOGP(SFILE, LDEBUG, "selecting by FID: %04x (2nd try)\n", fid); + selected_file = ss_get_file_from_path(&apdu->lchan->fs_path); + if (!selected_file) + return SS_SW_ERR_WRONG_PARAM_FILE_NOT_FOUND; + if (selected_file->fid == fid) { + SS_LOGP(SFILE, LDEBUG, "success: file (%04x) matches the current DF.\n", + fid); + goto leave; + } + + /* We now select the parent of the current directory. If the FID of + * that parent matches the FID we intend to select, then the select + * is successful. */ + SS_LOGP(SFILE, LDEBUG, "selecting by FID: %04x (3rd try)\n", fid); + rc = ss_fs_select_parent(&apdu->lchan->fs_path); + if (rc < 0) + return SS_SW_ERR_WRONG_PARAM_FILE_NOT_FOUND; + selected_file = ss_get_file_from_path(&apdu->lchan->fs_path); + if (!selected_file) + return SS_SW_ERR_WRONG_PARAM_FILE_NOT_FOUND; + if (selected_file->fid == fid) { + SS_LOGP(SFILE, LDEBUG, "success: file (%04x) matches the parent of the current DF.\n", + fid); + goto leave; + } + + /* Next we try to select the fid again. If we manage to select a DF + * (not an EF), then the correct file is selected and the select was + * successful */ + SS_LOGP(SFILE, LDEBUG, "selecting by FID: %04x (4th try)\n", fid); + rc = ss_fs_select(&apdu->lchan->fs_path, fid); + if (rc == 0) { + selected_file = ss_get_file_from_path(&apdu->lchan->fs_path); + if (!selected_file) + return SS_SW_ERR_WRONG_PARAM_FILE_NOT_FOUND; + if (selected_file->fcp_file_descr->type == SS_FCP_DF_OR_ADF) { + SS_LOGP(SFILE, LDEBUG, "success: file (%04x) found in the parent of the current DF.\n", + fid); + goto leave; + } + ss_fs_select_parent(&apdu->lchan->fs_path); + } + + /* As a last resort we may try to walk up the path and check if we meet + * a matching ADF at some point. (This would be unusual, since + * applications are usually selected via DF name.) */ + SS_LOGP(SFILE, LDEBUG, "selecting by FID: %04x (6th try)\n", fid); + while (1) { + rc = ss_fs_select_parent(&apdu->lchan->fs_path); + if (rc < 0) + break; + selected_file = ss_get_file_from_path(&apdu->lchan->fs_path); + if (!selected_file) + break; + + /* Check if the file has a DF name, then check if the FID of + * that file matches the FID we intended to select. */ + if (ss_fcp_get_df_name(selected_file->fcp_decoded)) { + if (selected_file->fid == fid) { + SS_LOGP(SFILE, LDEBUG, "success: file (%04x) is an ADF and was found in the current path.\n", + fid); + goto leave; + } + } + } + + return SS_SW_ERR_WRONG_PARAM_FILE_NOT_FOUND; +leave: + ss_access_populate(apdu->lchan); + + return 0; +} + +static int select_by_df_name(struct ss_apdu *apdu) +{ + int rc; + uint32_t fid; + struct ss_buf *df_name; + struct ss_file *selected_file; + + SS_LOGP(SFILE, LDEBUG, "selecting DF by name: %s\n", + ss_hexdump(apdu->cmd, apdu->lc)); + + rc = ss_df_name_resolve(&apdu->lchan->fs_path, apdu->cmd, apdu->lc); + if (rc < 0) { + /* If we cannot resolve the DF_name, the reason might be that + * we are deeper in the path. In this case will go back up and + * stop at the ADF. */ + while (1) { + rc = ss_fs_select_parent(&apdu->lchan->fs_path); + if (rc < 0) + break; + selected_file = + ss_get_file_from_path(&apdu->lchan->fs_path); + if (!selected_file) + break; + + /* NOTE: we are not taking ownership of the returned df_name. */ + df_name = + ss_fcp_get_df_name(selected_file->fcp_decoded); + if (df_name) { + SS_LOGP(SFILE, LDEBUG, + "trying parent file: %s, DF_name: %s\n", + ss_fs_utils_dump_path(&apdu->lchan-> + fs_path), + ss_hexdump(df_name->data, + df_name->len)); + if (memcmp(df_name->data, apdu->cmd, apdu->lc) + == 0) + goto leave; + } + } + + /* Last we can try is to select the MF and then try to select + * the supplied DF_name. */ + rc = ss_fs_select(&apdu->lchan->fs_path, 0x3f00); + if (rc < 0) + return SS_SW_ERR_WRONG_PARAM_FILE_NOT_FOUND; + rc = ss_df_name_resolve(&apdu->lchan->fs_path, apdu->cmd, + apdu->lc); + if (rc < 0) + return SS_SW_ERR_WRONG_PARAM_REFERENCED_DATA_NOT_FOUND; + } + + /* Select ADF by the resolved FID */ + fid = (uint32_t) (rc & 0xffff); + rc = ss_fs_select(&apdu->lchan->fs_path, fid); + if (rc < 0) + return SS_SW_ERR_WRONG_PARAM_FILE_NOT_FOUND; +leave: + ss_access_populate(apdu->lchan); + + return 0; +} + +static int select_parent_df_of_current_df(struct ss_apdu *apdu) +{ + int rc; + struct ss_file *selected_file; + + SS_LOGP(SFILE, LDEBUG, "selecting parent DF of current DF\n"); + + /* Make sure the current DF is selected */ + selected_file = ss_get_file_from_path(&apdu->lchan->fs_path); + if (!selected_file) + return SS_SW_ERR_WRONG_PARAM_FILE_NOT_FOUND; + if (selected_file->fcp_file_descr->type != SS_FCP_DF_OR_ADF) { + rc = ss_fs_select_parent(&apdu->lchan->fs_path); + if (rc < 0) + return SS_SW_ERR_WRONG_PARAM_FILE_NOT_FOUND; + } + + /* Select the parent of the current DF */ + rc = ss_fs_select_parent(&apdu->lchan->fs_path); + if (rc < 0) + return SS_SW_ERR_WRONG_PARAM_FILE_NOT_FOUND; + + ss_access_populate(apdu->lchan); + + return 0; +} + +static int select_path(struct ss_apdu *apdu, bool from_mf) +{ + unsigned int i; + uint32_t fid; + struct ss_file *selected_file; + int rc; + + if (from_mf) + SS_LOGP(SFILE, LDEBUG, "selecting by path from MF\n"); + else + SS_LOGP(SFILE, LDEBUG, "selecting by path from current DF\n"); + + /* Check for a messed up path. */ + if (apdu->lc % 2) + return SS_SW_ERR_WRONG_PARAM_INCORRECT_DATA; + + /* Select start position */ + if (from_mf) { + rc = ss_fs_select(&apdu->lchan->fs_path, 0x3f00); + if (rc < 0) + return SS_SW_ERR_WRONG_PARAM_FILE_NOT_FOUND; + } else { + selected_file = ss_get_file_from_path(&apdu->lchan->fs_path); + if (!selected_file) + return SS_SW_ERR_WRONG_PARAM_FILE_NOT_FOUND; + if (selected_file->fcp_file_descr->type != SS_FCP_DF_OR_ADF) { + rc = ss_fs_select_parent(&apdu->lchan->fs_path); + if (rc < 0) + return SS_SW_ERR_WRONG_PARAM_FILE_NOT_FOUND; + } + } + + /* Select path */ + for (i = 0; i < apdu->lc / 2; i++) { + fid = ss_uint32_from_array(apdu->cmd + i * 2, 2); + + if (fid == FID_CURRENT_APP) { + /* Select the currently active application by its alias, + see also ETSI TS 102 221 section 8.4.2 */ + ss_path_reset(&apdu->lchan->fs_path); + rc = ss_fs_utils_path_select(&apdu->lchan->fs_path, &apdu->lchan->adf_path); + } else { + rc = ss_fs_select(&apdu->lchan->fs_path, fid); + } + + if (rc < 0) + return SS_SW_ERR_WRONG_PARAM_FILE_NOT_FOUND; + } + + ss_access_populate(apdu->lchan); + + return 0; +} + +/* Update path to active ADF path. This is called after each select. The + * pathes are only updated when the currently active ADF changes. */ +static int update_active_adf(struct ss_lchan *lchan) +{ + struct ss_file *selected_file; + int rc; + + selected_file = ss_get_file_from_path(&lchan->fs_path); + assert(selected_file); + + /* Update active ADF (we recognize ADFs by the assigned DF Name) */ + if (ss_fcp_get_df_name(selected_file->fcp_decoded) + && ss_fs_utils_path_equals(&lchan->fs_path, + &lchan->adf_path) == false) { + ss_path_reset(&lchan->adf_path); + rc = ss_fs_utils_path_select(&lchan->adf_path, &lchan->fs_path); + if (rc < 0) { + SS_LOGP(SFILE, LERROR, + "cannot update path to active ADF!\n"); + return -EINVAL; + } + } + + return 0; +} + +/* SELECT, see also ETSI TS 102 221, section 11.1.1 */ +int ss_uicc_file_ops_cmd_select(struct ss_apdu *apdu) +{ + int rc; + struct ss_file *selected_file; + + /* Reset current record for record oriented files. */ + apdu->lchan->current_record = 0; + + switch (apdu->hdr.p1) { + case 0x00: /* DF, EF or MF by file ID */ + rc = select_by_fid(apdu); + break; + case 0x01: /* child DF of the current DF */ + /* TODO #62: The specification is not clear how this select + * method should be implemented. In particular it is unclear + * about which child DF should be picked in case the DF has + * more than a single child DF. If we manage to solve this + * problem, we can implement this selection method. */ + rc = SS_SW_ERR_WRONG_PARAM_FUNCTION_NOT_SUPPORTED; + break; + case 0x03: /* parent DF of current DF */ + rc = select_parent_df_of_current_df(apdu); + break; + case 0x04: /* DF name (AID) */ + rc = select_by_df_name(apdu); + break; + case 0x08: /* path from MF */ + rc = select_path(apdu, true); + break; + case 0x09: /* path from current DF */ + rc = select_path(apdu, false); + break; + default: + return SS_SW_ERR_CHECKING_WRONG_P1_P2; + } + + if (rc != 0) { + SS_LOGP(SFILE, LDEBUG, "select failed!\n"); + return rc; + } + selected_file = ss_get_file_from_path(&apdu->lchan->fs_path); + if (!selected_file) { + SS_LOGP(SFILE, LDEBUG, "select failed -- no file selected!\n"); + return SS_SW_ERR_EXEC_MEMORY_PROBLEM; + } + + /* generate full FCP string */ + rc = fcp_reencode_full(selected_file, "select"); + if (rc < 0) + return SS_SW_ERR_EXEC_MEMORY_PROBLEM; + + /* Update path to currently active ADF */ + rc = update_active_adf(apdu->lchan); + if (rc < 0) + return SS_SW_ERR_EXEC_MEMORY_PROBLEM; + + /* Return FCP template if requested */ + if ((apdu->hdr.p2 & 0x0c) == 0x04) { + memcpy(apdu->rsp, selected_file->fci->data, + selected_file->fci->len); + apdu->rsp_len = selected_file->fci->len; + } + + SS_LOGP(SFILE, LDEBUG, "Successfully selected: %s\n", ss_fs_utils_dump_path(&apdu->lchan->fs_path)); + ss_btlv_dump(selected_file->fcp_decoded, 1, SFILE, LDEBUG); + + return 0; +} diff --git a/src/softsim/uicc/uicc_file_ops.h b/src/softsim/uicc/uicc_file_ops.h new file mode 100644 index 0000000..ab9fd5d --- /dev/null +++ b/src/softsim/uicc/uicc_file_ops.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +int ss_uicc_file_ops_cmd_status(struct ss_apdu *apdu); +int ss_uicc_file_ops_cmd_read_binary(struct ss_apdu *apdu); +int ss_uicc_file_ops_cmd_update_binary(struct ss_apdu *apdu); +int ss_uicc_file_ops_cmd_read_record(struct ss_apdu *apdu); +int ss_uicc_file_ops_cmd_update_record(struct ss_apdu *apdu); +int ss_uicc_file_ops_cmd_search_record(struct ss_apdu *apdu); +int ss_uicc_file_ops_cmd_select(struct ss_apdu *apdu); diff --git a/src/softsim/uicc/uicc_ins.h b/src/softsim/uicc/uicc_ins.h new file mode 100644 index 0000000..697d5fa --- /dev/null +++ b/src/softsim/uicc/uicc_ins.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +enum uicc_ins_byte { + + /* ETSI TS 102 221 Section 10.1.2 Table 10.5 */ + TS_102_221_INS_SELECT_FILE = 0xa4, + TS_102_221_INS_STATUS = 0xf2, + TS_102_221_INS_READ_BINARY = 0xb0, + TS_102_221_INS_UPDATE_BINARY = 0xd6, + TS_102_221_INS_READ_RECORD = 0xb2, + TS_102_221_INS_UPDATE_RECORD = 0xdc, + TS_102_221_INS_SEARCH_RECORD = 0xa2, + TS_102_221_INS_INCREASE = 0x32, + TS_102_221_INS_RETRIEVE_DATA = 0xcb, + TS_102_221_INS_SET_DATA = 0xdb, + TS_102_221_INS_VERIFY_PIN = 0x20, + TS_102_221_INS_CHANGE_PIN = 0x24, + TS_102_221_INS_DISABLE_PIN = 0x26, + TS_102_221_INS_ENABLE_PIN = 0x28, + TS_102_221_INS_UNBLOCK_PIN = 0x2c, + TS_102_221_INS_DEACTIVATE_FILE = 0x04, + TS_102_221_INS_ACTIVATE_FILE = 0x44, + TS_102_221_INS_AUTHENTICATE = 0x88, + TS_102_221_INS_GET_CHALLENGE = 0x84, + TS_102_221_INS_TERMINAL_CAPABILITY = 0xaa, + TS_102_221_INS_TERMINAL_PROFILE = 0x10, + TS_102_221_INS_ENVELOPE = 0xc2, + TS_102_221_INS_FETCH = 0x12, + TS_102_221_INS_TERMINAL_RESPONSE = 0x14, + TS_102_221_INS_MANAGE_CHANNEL = 0x70, + TS_102_221_INS_MANAGE_SECURE_CHANNEL = 0x73, + TS_102_221_INS_TRANSACT_DATA = 0x75, + TS_102_221_INS_SUSPEND_UICC = 0x76, + TS_102_221_INS_GET_IDENTITY = 0x78, + TS_102_221_INS_GET_RESPONSE = 0xc0, + TS_102_221_INS_AUTHENTICATE_EVEN = 0x88, + TS_102_221_INS_AUTHENTICATE_ODD = 0x89, + + /* ETSI TS 102 222 Section 6.1 Table 1 */ + TS_102_222_INS_CREATE_FILE = 0xe0, + TS_102_222_INS_DELETE_FILE = 0xe4, + TS_102_222_INS_TERMINATE_DF = 0xe6, + TS_102_222_INS_TERMINATE_EF = 0xe8, + TS_102_222_INS_TERMINATE_CARD_USAGE = 0xfe, + TS_102_222_INS_RESIZE_FILE = 0xd4 +}; diff --git a/src/softsim/uicc/uicc_lchan.c b/src/softsim/uicc/uicc_lchan.c new file mode 100644 index 0000000..8e97a02 --- /dev/null +++ b/src/softsim/uicc/uicc_lchan.c @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + * + * TODO #61: Until now, there is no support planned for multiple channels. Only + * one basic channel shall be supported. This file is a placeholder. + */ + +#include +#include +#include +#include +#include +#include "access.h" +#include "uicc_lchan.h" +#include "fs.h" +#include "fs_utils.h" +#include "context.h" +#include "apdu.h" +#include "sw.h" +#include "fcp.h" + +/*! Dump lchan status information. + * \param[in] lchan lchan to dump. */ +void ss_uicc_lchan_dump(const struct ss_lchan *lchan) +{ + unsigned int i; + struct ss_file *active_adf; + struct ss_buf *active_adf_name = NULL; + + SS_LOGP(SLCHAN, LDEBUG, "lchan %u:\n", lchan->nr); + for (i = 0; i < SS_ARRAY_SIZE(lchan->pin_verfied); i++) { + if (lchan->pin_verfied[i]) + SS_LOGP(SLCHAN, LDEBUG, " pin %u verifed\n", i); + } + + SS_LOGP(SLCHAN, LDEBUG, " selected file: %s\n", + ss_fs_utils_dump_path(&lchan->fs_path)); + active_adf = ss_get_file_from_path(&lchan->adf_path); + if (active_adf) + active_adf_name = ss_fcp_get_df_name(active_adf->fcp_decoded); + SS_LOGP(SLCHAN, LDEBUG, " active ADF: %s - %s\n", + ss_fs_utils_dump_path(&lchan->adf_path), + active_adf_name ? ss_hexdump(active_adf_name->data, + active_adf_name->len) : + "(no AID)"); + SS_LOGP(SLCHAN, LDEBUG, " current record: %u\n", lchan->current_record); +} + +/*! Free all dynamically allocated data inside all lchans. + * \param[inout] ctx softsim context. */ +void ss_uicc_lchan_free(struct ss_context *ctx) +{ + /* Make sure pathes are cleared */ + if (ctx->lchan.fs_path.next != NULL) + ss_path_reset(&ctx->lchan.fs_path); + if (ctx->lchan.adf_path.next != NULL) + ss_path_reset(&ctx->lchan.adf_path); + + /* Get rid of last APDU */ + SS_FREE(ctx->lchan.last_apdu); + + /* Ensure all lchan memory is zeroed out */ + memset(&ctx->lchan, 0, sizeof(ctx->lchan)); +} + +/*! Reset (close) all lchans (called on UICC reset). + * \param[inout] ctx softsim context. */ +void ss_uicc_lchan_reset(struct ss_context *ctx) +{ + /* Get rid of all dynamically allocated data */ + ss_uicc_lchan_free(ctx); + + /* Initialize lchan global pathes */ + ss_fs_init(&ctx->lchan.fs_path); + ss_fs_init(&ctx->lchan.adf_path); + + /* Can be the case during commissioning, before the MF was created */ + if (ss_get_file_from_path(&ctx->lchan.fs_path) != NULL) + ss_access_populate(&ctx->lchan); +} + +/*! Get matching lchan for specified CLA byte. + * \param[inout] ctx softsim context. + * \param[out] cla byte (0x00 for basic channel). + * \returns lchan, NULL if lchan is not found. */ +struct ss_lchan *ss_uicc_lchan_get(struct ss_context *ctx, uint8_t cla) +{ + /* See also: ISO 7816-4, section 5.1.1 */ + uint8_t lchan_nr = cla & 0x03; + if (lchan_nr == 0) + return &ctx->lchan; + else { + SS_LOGP(SLCHAN, LERROR, "lchan %u not found (cla=%02x)\n", + lchan_nr, cla); + return NULL; + } +} + +/*! MANAGE CHANNEL (TS 102 221 Section 11.1.17) */ +int ss_uicc_lchan_cmd_manage_channel(struct ss_apdu *apdu) +{ + /* We only support the basic logical channel. Opening or closing + * logical cannels is not supported. */ + apdu->le = 0; + return SS_SW_ERR_FUNCTION_IN_CLA_NOT_SUPP_LCHAN; +} diff --git a/src/softsim/uicc/uicc_lchan.h b/src/softsim/uicc/uicc_lchan.h new file mode 100644 index 0000000..90c80ee --- /dev/null +++ b/src/softsim/uicc/uicc_lchan.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include +#include +#include "uicc_sms_rx.h" +struct ss_context; +struct ss_apdu; + +/*! Logical channel through which we communicate with an external application */ +struct ss_lchan { + + /* Number of the lchan */ + uint8_t nr; + + /* Note: This flag field must not be read accessed directly. In order to + * check PIN permission, the API functions in module uicc_pin.c must + * be used. It may be written to if a PIN is implicitly provided for an + * lchan, eg. in setup_ctx_from_tar. */ + bool pin_verfied[256]; + + /* Currently selected file (can be an EF, DF or ADF) */ + struct ss_list fs_path; + + /* Current record, in case a record oriented file is currently + * selected. */ + uint8_t current_record; + + /* the last APDU in case a GET RESPONSE follows */ + struct ss_apdu *last_apdu; + bool last_apdu_keep; + + /* Path to currently active (last selected) ADF */ + struct ss_list adf_path; +}; + +void ss_uicc_lchan_dump(const struct ss_lchan *lchan); +void ss_uicc_lchan_free(struct ss_context *ctx); +void ss_uicc_lchan_reset(struct ss_context *ctx); +struct ss_lchan *ss_uicc_lchan_get(struct ss_context *ctx, uint8_t cla); +int ss_uicc_lchan_cmd_manage_channel(struct ss_apdu *apdu); diff --git a/src/softsim/uicc/uicc_pin.c b/src/softsim/uicc/uicc_pin.c new file mode 100644 index 0000000..1fb4966 --- /dev/null +++ b/src/softsim/uicc/uicc_pin.c @@ -0,0 +1,623 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "uicc_lchan.h" +#include "command.h" +#include "uicc_pin.h" +#include "uicc_ins.h" +#include "sw.h" +#include "apdu.h" +#include "fs.h" +#include "fs_utils.h" +#include "fcp.h" + +/* PIN code file in file system */ +#define PIN_FID 0xA003 + +const uint8_t pins_in_psdo[] = { + SS_PIN_1, + SS_PIN_2, + SS_PIN_ADM1, +}; + +/* Record layout of the file is the same as the layout of the pin_context + * struct. */ +struct pin_context { + bool enabled; + uint8_t max_tries; + uint8_t tries; + uint8_t max_unblock_tries; + uint8_t unblock_tries; + uint8_t pin_no; /* Value-compatible with `enum pin`, but u8 to not depend on system integer width and endianness */ + uint8_t pin[8]; + uint8_t puk[8]; +} __attribute__((packed)); + +/* find the PIN context for a given PIN number in PIN code file + * + * @param[out] sw A setatus word containing additional error details + * @param[in] pin_no A key reference (cf. `enum pin`) + * + * @return A pointer to the first pin context matching pin_no. Must be freed + * later by calling @ref pin_context_free. + */ +static struct pin_context *get_pin_context(uint16_t *sw, uint8_t pin_no) +{ + unsigned int i; + int rc; + struct ss_list pin_context_path; + struct ss_buf *pin_context_buf; + struct pin_context *pin_context_ptr; + struct ss_file *pin_context_file; + uint8_t n_pins; + + /* Select pin code file. */ + ss_fs_init(&pin_context_path); + rc = ss_fs_select(&pin_context_path, PIN_FID); + if (rc < 0) { + SS_LOGP(SPIN, LERROR, + "PIN code file not selectable -- cannot load context for PIN No.:%02x!\n", + pin_no); + ss_path_reset(&pin_context_path); + return NULL; + } + pin_context_file = ss_get_file_from_path(&pin_context_path); + if (!pin_context_file) { + SS_LOGP(SPIN, LERROR, + "PIN code file not available -- cannot load context for PIN No.:%02x!\n", + pin_no); + ss_path_reset(&pin_context_path); + return NULL; + } + + /* Read pin context from pin code file */ + SS_LOGP(SPIN, LINFO, "loading pin PIN code file for PIN No.:%02x...\n", + pin_no); + n_pins = pin_context_file->fcp_file_descr->number_of_records; + for (i = 0; i < n_pins; i++) { + pin_context_buf = + ss_fs_read_file_record(&pin_context_path, i + 1); + if (!pin_context_buf) { + SS_LOGP(SPIN, LERROR, + "PIN code file inconsistent -- cannot read record (%u)!\n", + i + 1); + ss_path_reset(&pin_context_path); + return NULL; + } + + pin_context_ptr = (struct pin_context *)pin_context_buf->data; + if (pin_no == pin_context_ptr->pin_no) { + ss_path_reset(&pin_context_path); + /* The pin_context_free function relies on this implementation details of + * ss_buf, as otherwise there is not offsetof available */ + assert((uintptr_t)pin_context_buf + sizeof(struct ss_buf) == (uintptr_t)pin_context_ptr); + /* pin_context_buf is "leaked" here, but reconstructed and freed when + * pin_context_free is called + * + * The alternative designs here would be to copy data around into an own + * buffer (rejected to avoid needless coping, but can be switched to + * API-compatibly) and using caller allocated buffers (which are a bit + * pointless as long as files are read into a dynamically allocated + * buffer already). + * */ + return pin_context_ptr; + } + + ss_buf_free(pin_context_buf); + } + + SS_LOGP(SPIN, LERROR, "invalid PIN (%u) -- abort\n", pin_no); + if (sw) + *sw = SS_SW_ERR_CHECKING_WRONG_P1_P2; + ss_path_reset(&pin_context_path); + return NULL; +} + +static void pin_context_free(struct pin_context *pin) +{ + if (pin == NULL) + return; + + struct ss_buf *pin_context_buf = (struct ss_buf *)(((char*)pin) - sizeof(struct ss_buf)); + ss_buf_free(pin_context_buf); +} + +/* Find PIN context in PIN code file and update it. */ +static int update_pin_context(const struct pin_context *pin) +{ + unsigned int i; + int rc; + struct ss_list pin_context_path; + struct ss_buf *pin_context_buf; + struct pin_context *pin_context_ptr; + struct ss_file *pin_context_file; + uint8_t n_pins; + + /* Select pin code file. */ + ss_fs_init(&pin_context_path); + rc = ss_fs_select(&pin_context_path, PIN_FID); + if (rc < 0) { + SS_LOGP(SPIN, LERROR, + "PIN code file not selectable -- cannot update context for PIN No.:%02x!\n", + pin->pin_no); + ss_path_reset(&pin_context_path); + return -EINVAL; + } + pin_context_file = ss_get_file_from_path(&pin_context_path); + if (!pin_context_file) { + SS_LOGP(SPIN, LERROR, + "PIN code file not available -- cannot update context for PIN No.:%02x!\n", + pin->pin_no); + ss_path_reset(&pin_context_path); + return -EINVAL; + } + + /* Update pin context from pin code file */ + SS_LOGP(SPIN, LINFO, + "updating pin PIN code file for PIN No.:%02x...\n", + pin->pin_no); + n_pins = pin_context_file->fcp_file_descr->number_of_records; + for (i = 0; i < n_pins; i++) { + pin_context_buf = + ss_fs_read_file_record(&pin_context_path, i + 1); + if (!pin_context_buf) { + SS_LOGP(SPIN, LERROR, + "PIN code file inconsistent -- cannot read record (%u)!\n", + i + 1); + ss_path_reset(&pin_context_path); + return -EINVAL; + } + + pin_context_ptr = (struct pin_context *)pin_context_buf->data; + if (pin->pin_no == pin_context_ptr->pin_no) { + + rc = ss_fs_write_file_record(&pin_context_path, i + 1, + (uint8_t *) pin, + sizeof(*pin)); + if (rc < 0) { + SS_LOGP(SPIN, LERROR, + "PIN code file update failed -- cannot write record (%u)!\n", + i + 1); + ss_buf_free(pin_context_buf); + ss_path_reset(&pin_context_path); + return -EINVAL; + } + + ss_buf_free(pin_context_buf); + ss_path_reset(&pin_context_path); + return 0; + } + + ss_buf_free(pin_context_buf); + } + + ss_path_reset(&pin_context_path); + return 0; +} + +/* check the PIN retry counter. If the retry counter has exceeded the maximum + * amount of tries, then return false */ +static bool check_pin_retry_counter(const struct pin_context *pin) +{ + if (pin->tries >= pin->max_tries) { + SS_LOGP(SPIN, LERROR, "PIN (%u) is blocked -- abort\n", + pin->pin_no); + return false; + } + + return true; +} + +/* VERIFY PIN, see also ETSI TS 102 221, section 11.1.9 */ +int ss_uicc_pin_cmd_verify_pin(struct ss_apdu *apdu) +{ + struct pin_context *pin = NULL; + int rc; + int result; + + /* Get PIN context */ + pin = get_pin_context(&apdu->sw, apdu->hdr.p2); + if (!pin) + return SS_SW_ERR_EXEC_MEMORY_PROBLEM; + + /* Return number of remaining tries, see also ETSI TS 102 221, + * section 11.1.9.1.2 */ + if (apdu->lc == 0) { + SS_LOGP(SPIN, LDEBUG, + "no operation, number of remaining tries (%u) requested\n", + (pin->max_tries - pin->tries) & 0x0f); + result = SS_SW_WARN_VERIFICATION_FAILED_X_REMAIN | + ((pin->max_tries - pin->tries) & 0x0f); + goto leave; + } + + /* Check retry counter */ + if (!check_pin_retry_counter(pin)) { + result = SS_SW_ERR_CMD_NOT_ALLOWED_PIN_BLOCKED; + goto leave; + } + + /* PIN must not be disabled */ + if (!pin->enabled) { + SS_LOGP(SPIN, LERROR,"cannot verify, PIN (%u) is disabled -- abort\n", pin->pin_no); + result = SS_SW_ERR_CMD_NOT_ALLOWED_CONDITONS_NOT_SATISFIED; + goto leave; + } + + /* Check length and match PIN code */ + if (apdu->lc != sizeof(pin->pin) + || memcmp(apdu->cmd, pin->pin, sizeof(pin->pin))) { + SS_LOGP(SPIN, LERROR, + "incorrect PIN (%u), VERIFY PIN failed -- abort\n", + pin->pin_no); + pin->tries++; + rc = update_pin_context(pin); + if (rc < 0) + result = SS_SW_ERR_CMD_NOT_ALLOWED_PIN_BLOCKED; + else + result = SS_SW_WARN_VERIFICATION_FAILED_X_REMAIN | + ((pin->max_tries - pin->tries) & 0x0f); + goto leave; + } + + SS_LOGP(SPIN, LINFO, "valid PIN (%u), VERIFY PIN successful\n", + pin->pin_no); + apdu->lchan->pin_verfied[pin->pin_no] = true; + pin->tries = 0; + rc = update_pin_context(pin); + if (rc < 0) + result = SS_SW_ERR_CMD_NOT_ALLOWED_PIN_BLOCKED; + else + result = 0; + +leave: + pin_context_free(pin); + return result; +} + +/* CHANGE PIN, see also ETSI TS 102 221, section 11.1.10 */ +int ss_uicc_pin_cmd_change_pin(struct ss_apdu *apdu) +{ + struct pin_context *pin = NULL; + int rc; + int result; + + /* Get PIN context */ + pin = get_pin_context(&apdu->sw, apdu->hdr.p2); + if (!pin) + return SS_SW_ERR_EXEC_MEMORY_PROBLEM; + + /* PIN must not be blocked, check retry counter */ + if (!check_pin_retry_counter(pin)) { + result = SS_SW_ERR_CMD_NOT_ALLOWED_PIN_BLOCKED; + goto leave; + } + + /* PIN must not be disabled */ + if (!pin->enabled) { + SS_LOGP(SPIN, LERROR,"cannot change, PIN (%u) is disabled -- abort\n", pin->pin_no); + result = SS_SW_ERR_CMD_NOT_ALLOWED_CONDITONS_NOT_SATISFIED; + goto leave; + } + + /* Check length and match old PIN code */ + if (apdu->lc != sizeof(pin->pin) * 2 + || memcmp(apdu->cmd, pin->pin, sizeof(pin->pin))) { + SS_LOGP(SPIN, LERROR, + "incorrect old PIN (%u), CHANGE PIN failed -- abort\n", + pin->pin_no); + pin->tries++; + rc = update_pin_context(pin); + if (rc < 0) + result = SS_SW_ERR_CMD_NOT_ALLOWED_PIN_BLOCKED; + else + result =SS_SW_WARN_VERIFICATION_FAILED_X_REMAIN | + ((pin->max_tries - pin->tries) & 0x0f); + goto leave; + } + + /* Apply new PIN code */ + memcpy(pin->pin, apdu->cmd + sizeof(pin->pin), sizeof(pin->pin)); + rc = update_pin_context(pin); + if (rc < 0) { + result = SS_SW_ERR_CMD_NOT_ALLOWED_PIN_BLOCKED; + goto leave; + } + + SS_LOGP(SPIN, LINFO, "valid PIN (%u), CHANGE PIN successful\n", + pin->pin_no); + + result = 0; + +leave: + pin_context_free(pin); + return result; +} + +/* DISABLE PIN, see also ETSI TS 102 221, section 11.1.11 */ +int ss_uicc_pin_cmd_disable_pin(struct ss_apdu *apdu) +{ + /* Note: This implementation ignores the usage of an "alternative global + * key reference". */ + + struct pin_context *pin = NULL; + int rc; + int result; + + /* Get PIN context */ + pin = get_pin_context(&apdu->sw, apdu->hdr.p2); + if (!pin) + return SS_SW_ERR_EXEC_MEMORY_PROBLEM; + + /* Check retry counter */ + if (!check_pin_retry_counter(pin)) { + result = SS_SW_ERR_CMD_NOT_ALLOWED_PIN_BLOCKED; + goto leave; + } + + /* Check length and match PIN code */ + if (apdu->lc != sizeof(pin->pin) + || memcmp(apdu->cmd, pin->pin, sizeof(pin->pin))) { + SS_LOGP(SPIN, LERROR, + "incorrect PIN (%u), DISABLE PIN failed -- abort\n", + pin->pin_no); + pin->tries++; + rc = update_pin_context(pin); + if (rc < 0) + result = SS_SW_ERR_CMD_NOT_ALLOWED_PIN_BLOCKED; + else + result =SS_SW_WARN_VERIFICATION_FAILED_X_REMAIN | + ((pin->max_tries - pin->tries) & 0x0f); + goto leave; + } + + SS_LOGP(SPIN, LINFO, "valid PIN (%u), DISABLE PIN successful\n", + pin->pin_no); + pin->tries = 0; + pin->enabled = false; + rc = update_pin_context(pin); + if (rc < 0) + result = SS_SW_ERR_CMD_NOT_ALLOWED_PIN_BLOCKED; + else + result = 0; + +leave: + pin_context_free(pin); + return result; +} + +/* ENABLE PIN, see also ETSI TS 102 221, section 11.1.12 */ +int ss_uicc_pin_cmd_enable_pin(struct ss_apdu *apdu) +{ + /* Note: This implementation ignores the usage of an "alternative global + * key reference". */ + + struct pin_context *pin = NULL; + int rc; + int result; + + /* Get PIN context */ + pin = get_pin_context(&apdu->sw, apdu->hdr.p2); + if (!pin) + return SS_SW_ERR_EXEC_MEMORY_PROBLEM; + + /* Check retry counter */ + if (!check_pin_retry_counter(pin)) { + result = SS_SW_ERR_CMD_NOT_ALLOWED_PIN_BLOCKED; + goto leave; + } + + /* Check length and match PIN code */ + if (apdu->lc != sizeof(pin->pin) + || memcmp(apdu->cmd, pin->pin, sizeof(pin->pin))) { + SS_LOGP(SPIN, LERROR, + "incorrect PIN (%u), ENABLE PIN failed -- abort\n", + pin->pin_no); + pin->tries++; + rc = update_pin_context(pin); + if (rc < 0) + result = SS_SW_ERR_CMD_NOT_ALLOWED_PIN_BLOCKED; + else + result = SS_SW_WARN_VERIFICATION_FAILED_X_REMAIN | + ((pin->max_tries - pin->tries) & 0x0f); + goto leave; + } + + SS_LOGP(SPIN, LINFO, "valid PIN (%u), ENABLE PIN successful\n", + pin->pin_no); + pin->tries = 0; + pin->enabled = true; + rc = update_pin_context(pin); + if (rc < 0) + result = SS_SW_ERR_CMD_NOT_ALLOWED_PIN_BLOCKED; + else + result = 0; + +leave: + pin_context_free(pin); + return result; +} + +/* UNBLOCK PIN, see also ETSI TS 102 221, section 11.1.13.1.2 */ +int ss_uicc_pin_cmd_unblock_pin(struct ss_apdu *apdu) +{ + struct pin_context *pin = NULL; + int rc; + int result; + + /* Get PIN context */ + pin = get_pin_context(&apdu->sw, apdu->hdr.p2); + if (!pin) + return SS_SW_ERR_EXEC_MEMORY_PROBLEM; + + /* Return number of remaining tries, see also ETSI TS 102 221, + * section 11.1.13.1.2 */ + if (apdu->lc == 0) { + SS_LOGP(SPIN, LDEBUG, + "no operation, number of remaining tries (%u) requested\n", + (pin->max_unblock_tries - pin->unblock_tries) & 0x0f); + result = SS_SW_WARN_VERIFICATION_FAILED_X_REMAIN | + ((pin->max_unblock_tries - pin->unblock_tries) & 0x0f); + goto leave; + } + + /* PUK must not be blocked, check retry counter */ + if (pin->unblock_tries >= pin->max_unblock_tries) { + SS_LOGP(SPIN, LERROR, "PUK (%u) is blocked -- abort\n", + pin->pin_no); + result = SS_SW_ERR_CMD_NOT_ALLOWED_PIN_BLOCKED; + goto leave; + } + + /* Check length and match PUK code */ + if (apdu->lc != sizeof(pin->puk) + sizeof(pin->pin) + || memcmp(apdu->cmd, pin->puk, sizeof(pin->puk))) { + SS_LOGP(SPIN, LERROR, + "incorrect PUK (%u), UNBLOCK PIN failed -- abort\n", + pin->pin_no); + pin->unblock_tries++; + rc = update_pin_context(pin); + if (rc < 0) + result = SS_SW_ERR_CMD_NOT_ALLOWED_PIN_BLOCKED; + else + result = SS_SW_WARN_VERIFICATION_FAILED_X_REMAIN | + ((pin->max_unblock_tries - pin->unblock_tries) & 0x0f); + goto leave; + } + + /* Apply new PIN code */ + memcpy(pin->pin, apdu->cmd + sizeof(pin->puk), sizeof(pin->pin)); + pin->unblock_tries = 0; + pin->tries = 0; + rc = update_pin_context(pin); + if (rc < 0) { + result = SS_SW_ERR_CMD_NOT_ALLOWED_PIN_BLOCKED; + } else { + SS_LOGP(SPIN, LINFO, "valid PUK (%u), UNBLOCK PIN successful\n", + pin->pin_no); + result = 0; + } + +leave: + pin_context_free(pin); + return result; +} + +/*! Check verfication status of a specified PIN. + * \param[in] in_no number of the PIN to check. + * \param[in] lchan logicl channel (stores the pin verification state). + * \returns verfication status of the specified pin. */ +bool ss_uicc_pin_verified(enum pin pin_no, const struct ss_lchan *lchan) +{ + struct pin_context *pin = NULL; + bool result; + + pin = get_pin_context(NULL, pin_no); + if (!pin) + return false; + + if (pin_no == pin->pin_no) { + if (pin->enabled) + result = lchan->pin_verfied[pin_no]; + else + result = true; + } else { + result = false; + } + + pin_context_free(pin); + return result; +} + +/*! Update PS_DO flag inside a given pin status template. + * + * \param[in] pin_stat_templ pin status template string. + * \returns 0 on success -EINVAL on failure. + * + * This relies on the elements to be constructed precisely as in @ref + * ss_uicc_pin_gen_pst_do. + * */ +int ss_uicc_pin_update_pst_do(struct ss_buf *pin_stat_templ) +{ + if (pin_stat_templ->len < 4) { + SS_LOGP(SPIN, LERROR, "pin status template too short!\n"); + /* We exclusively work with those generated in ss_uicc_pin_gen_pst_do, but + * we can't easily know that precise length here, so the check is only for + * what we actively use to avoid causing a memory error from a programming + * error. (The programming error will result in the PST_DO being + * nonsensical, but we can't check all of it without the effort of + * rebuilding it). */ + return -EINVAL; + } + + uint8_t pins_enabled = 0; + /* There is only 1 byte allocated to store the bits */ + assert(SS_ARRAY_SIZE(pins_in_psdo) <= 7); + for (int i = 0; i < SS_ARRAY_SIZE(pins_in_psdo); ++i) { + struct pin_context *pin = NULL; + pin = get_pin_context(NULL, pins_in_psdo[i]); + if (!pin) + return -EINVAL; + if (pin->enabled) { + pins_enabled |= 1 << (7 - i); + } + pin_context_free(pin); + } + SS_LOGP(SPIN, LDEBUG, "Set of enabled pins is %02x\n", pins_enabled); + pin_stat_templ->data[2] = pins_enabled; + + return 0; +} + +/*! Generate a valid PIN Status Template DO string. + * + * \returns buffer with PIN Status Template DO on success NULL on failure. + * + * The buffer contains the enablement status of and corresponding key + * references to all \ref pins_in_psdo (independent of the selected DF) under + * the implementation's constraint of only using a single set of credentials. + * */ +struct ss_buf *ss_uicc_pin_gen_pst_do(void) +{ + struct ss_buf *pin_stat_templ; + int rc; + + pin_stat_templ = ss_buf_alloc(3 + 3 * SS_ARRAY_SIZE(pins_in_psdo)); + if (!pin_stat_templ) + return pin_stat_templ; + /* PS_DO */ + pin_stat_templ->data[0] = 0x90; /* tag */ + pin_stat_templ->data[1] = 0x01; /* length */ + pin_stat_templ->data[2] = 0x00; /* to be set in ss_uicc_pin_update_pst_do */ + + /* No Usage Qualifier tag: We don't support the Universal Pin, in + * particular don't set the Key reference value to 0x11, and thus don't + * need a Usage Qualifier (ETSI TS 102 221 V16 p94) */ + + for (int i = 0; i < SS_ARRAY_SIZE(pins_in_psdo); ++i) { + /* Key reference */ + pin_stat_templ->data[3 + i * 3 + 0] = 0x83; /* tag */ + pin_stat_templ->data[3 + i * 3 + 1] = 0x01; /* length */ + pin_stat_templ->data[3 + i * 3 + 2] = pins_in_psdo[i]; + } + + rc = ss_uicc_pin_update_pst_do(pin_stat_templ); + if (rc < 0) { + SS_LOGP(SPIN, LERROR, + "pin status template has been generated but it was not possible to update it!\n"); + } + return pin_stat_templ; +} diff --git a/src/softsim/uicc/uicc_pin.h b/src/softsim/uicc/uicc_pin.h new file mode 100644 index 0000000..3210662 --- /dev/null +++ b/src/softsim/uicc/uicc_pin.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +struct ss_lchan; + +/* Indices match the key references of TS 102 221 V15.0.0 table 9.3. */ +enum pin { + SS_PIN_1 = 0x01, + SS_PIN_2 = 0x81, + SS_PIN_ADM1 = 0x0A, +}; + +int ss_uicc_pin_cmd_verify_pin(struct ss_apdu *apdu); +int ss_uicc_pin_cmd_change_pin(struct ss_apdu *apdu); +int ss_uicc_pin_cmd_disable_pin(struct ss_apdu *apdu); +int ss_uicc_pin_cmd_enable_pin(struct ss_apdu *apdu); +int ss_uicc_pin_cmd_unblock_pin(struct ss_apdu *apdu); + +bool ss_uicc_pin_verified(enum pin pin_no, const struct ss_lchan *lchan); +int ss_uicc_pin_update_pst_do(struct ss_buf *pin_stat_templ); +struct ss_buf *ss_uicc_pin_gen_pst_do(void); diff --git a/src/softsim/uicc/uicc_refresh.c b/src/softsim/uicc/uicc_refresh.c new file mode 100644 index 0000000..0add463 --- /dev/null +++ b/src/softsim/uicc/uicc_refresh.c @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include +#include "context.h" +#include "uicc_cat.h" +#include "uicc_refresh.h" +#include "ctlv.h" + +#define MAX_REFRESH_RETRYS 3 + +/* See also ETSI TS 102 223, section 8.6 */ +#define CMD_QUALIFIER 0x01 + +static void term_response_cb(struct ss_context *ctx, uint8_t *resp_data, + uint8_t resp_data_len) +{ + struct ss_uicc_refresh_state *state = &ctx->proactive.refresh_state; + int rc; + + rc = ss_proactive_get_rc(resp_data, resp_data_len, SREFRESH); + + if (rc != 0 && state->retry_counter < MAX_REFRESH_RETRYS) { + SS_LOGP(SREFRESH, LERROR, + "unsccessful REFRESH command, retrying...\n"); + /* Unfortunately there is not much that can be done at this point other + * than trying again. */ + state->state = SS_REFRESH_PENDING; + state->retry_counter++; + } else if (rc != 0) { + SS_LOGP(SREFRESH, LERROR, + "unsccessful REFRESH command, giving up...\n"); + state->state = SS_REFRESH_READY; + state->retry_counter = 0; + } else { + SS_LOGP(SREFRESH, LDEBUG, + "successful REFRESH command, done!\n"); + state->state = SS_REFRESH_READY; + state->retry_counter = 0; + } +} + +/* Generate REFRESH command from the data in the state and send it */ +static int send_refresh(struct ss_context *ctx, + struct ss_uicc_refresh_state *state) +{ + struct ss_list *cmd_list; + struct ss_buf *cmd; + uint8_t cmd_details[] = { 0x01, TS_102_223_TOC_REFRESH, CMD_QUALIFIER }; + uint8_t device_id[] = { 0x81, 0x02 }; + int filelist_len; + int rc; + + /* Generate command */ + filelist_len = ss_fs_chg_len(state->filelist); + if (filelist_len < 0) { + SS_LOGP(SREFRESH, LDEBUG, + "Sending REFRESH command failed -- file list is invalid!\n"); + return -EINVAL; + } + cmd_list = SS_ALLOC(struct ss_list); + ss_list_init(cmd_list); + ss_ctlv_new_ie(cmd_list, TS_101_220_IEI_CMD_DETAILS, true, + sizeof(cmd_details), cmd_details); + ss_ctlv_new_ie(cmd_list, TS_101_220_IEI_DEV_ID, true, sizeof(device_id), + device_id); + ss_ctlv_new_ie(cmd_list, TS_101_220_IEI_FILE_LST_OR_CAT_SERV_LST, true, + filelist_len, state->filelist); + SS_LOGP(SREFRESH, LDEBUG, "resulting message IEs:\n"); + + ss_ctlv_dump(cmd_list, 2, SREFRESH, LDEBUG); + cmd = ss_ctlv_encode_to_ss_buf(cmd_list); + if (!cmd) { + SS_LOGP(SREFRESH, LERROR, + "Sending REFRESH failed -- cannot encode command!:\n"); + ss_ctlv_free(cmd_list); + return -EINVAL; + } + + /* Send command */ + rc = ss_proactive_put(ctx, term_response_cb, cmd->data, cmd->len); + ss_ctlv_free(cmd_list); + ss_buf_free(cmd); + return rc; +} + +/*! Poll proactive task "REFRESH". + * \param[inout] ctx softsim context. */ +void ss_uicc_refresh_poll(struct ss_context *ctx) +{ + struct ss_uicc_refresh_state *state = &ctx->proactive.refresh_state; + int rc; + + /* Check REFRESH support, see also ETSI TS 131 111 section 5.2 */ + if (!ss_proactive_term_prof_bit(ctx, 3, 8)) { + SS_LOGP(SREFRESH, LERROR, + "cannot refresh files, TERMINAL PROFILE does not support REFRESH command!\n"); + rc = -EINVAL; + return; + } + + switch (state->state) { + case SS_REFRESH_READY: + if (ctx->fs_chg_filelist[0] == 0x00) { + SS_LOGP(SREFRESH, LDEBUG, + "no file changes detected, skipping...\n"); + return; + } + SS_LOGP(SREFRESH, LDEBUG, + "following file changes will be refreshed:\n"); + ss_fs_chg_dump(ctx->fs_chg_filelist, 2, SREFRESH, LDEBUG); + memcpy(state->filelist, ctx->fs_chg_filelist, + SS_FS_CHG_BUF_SIZE); + ctx->fs_chg_filelist[0] = 0; + state->state = SS_REFRESH_PENDING; + /* fallthrough */ + case SS_REFRESH_PENDING: + rc = send_refresh(ctx, state); + if (rc == -EBUSY) { + SS_LOGP(SREFRESH, LERROR, + "cannot send REFRESH, another command is busy, retrying...\n"); + return; + } else if (rc < 0) { + /* Note: This should not happen since it would mean + * that the data we give to send_refresh is garbled, + * since we are generating it internally the data + * should always be fine. */ + SS_LOGP(SREFRESH, LERROR, + "cannot send REFRESH, data is not accepted, giving up...\n"); + state->state = SS_REFRESH_READY; + return; + } + SS_LOGP(SREFRESH, LDEBUG, "REFRESH command sent!\n"); + state->state = SS_REFRESH_TRANSIT; + /* fallthrough */ + case SS_REFRESH_TRANSIT: + /* Do nothing, wait for the TERMINAL RESPONSE (see above) */ + SS_LOGP(SREFRESH, LDEBUG, + "waiting until file changes are transmitted...:\n"); + break; + } +} diff --git a/src/softsim/uicc/uicc_refresh.h b/src/softsim/uicc/uicc_refresh.h new file mode 100644 index 0000000..248ae50 --- /dev/null +++ b/src/softsim/uicc/uicc_refresh.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include "fs_chg.h" + +enum ss_uicc_refresh_states { + SS_REFRESH_READY = 0x00, + SS_REFRESH_PENDING = 0x01, + SS_REFRESH_TRANSIT = 0x02, +}; + +struct ss_uicc_refresh_state { + uint8_t filelist[SS_FS_CHG_BUF_SIZE]; + enum ss_uicc_refresh_states state; + unsigned int retry_counter; +}; + +void ss_uicc_refresh_poll(struct ss_context *ctx); diff --git a/src/softsim/uicc/uicc_remote_cmd.c b/src/softsim/uicc/uicc_remote_cmd.c new file mode 100644 index 0000000..3868fc6 --- /dev/null +++ b/src/softsim/uicc/uicc_remote_cmd.c @@ -0,0 +1,1224 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "uicc_remote_cmd.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "context.h" +#include "fcp.h" +#include "fs.h" +#include "sw.h" +#include "uicc_pin.h" +#include "utils.h" +#include "utils_3des.h" +#include "utils_aes.h" +#include "utils_ota.h" + +/* Information element identifier for command packets, as used in TS 23.048 + * V5.9.0 Seciton 6.2 */ +#define IEI_RPI 0x71 + +/* File containing TARs and their keys + * + * Format: (22 bytes per record) + * +----------+--------+---------+---------+---------+---------+ + * | 3 byte | 1 byte | 1 byte | 1 byte | 16 byte | 16 byte | + * | TAR | MSL | KIC ind | KID ind | KIC | KID | + * +----------+--------+---------+---------+---------+---------+ + * + * Records are read iteratively until the MSL could be checked, and KIC and KID + * were popualated as needed. A record matches a KIC / KID indicator if either + * the request's indicator is 0 (indicating that it's implicitly clear, ie. + * taking any record) or they are equal. + * + * If KIC and KIDs are not provided pair-wise, or (which is not recommended) + * unencrypted communication is allowed and no keys are present, then the + * record's indications can say 0xff, which never matches any request's + * indications. + * + * All MSLs associated with a TAR need to be identical (this is not checked in + * the code, and more a matter of defensive style). */ +#define TAR_KEY_FID 0xA004 +#define TAR_CNTR_FID 0xA005 + +#define TAR_LEN 3 +#define CNTR_LEN 5 +#define PCNT_LEN 1 +#define RSC_LEN 1 + +struct tar_record { + uint8_t tar[TAR_LEN]; + uint8_t msl; + uint8_t kic_indication; + uint8_t kid_indication; + uint8_t kic[OTA_KEY_LEN]; + uint8_t kid[OTA_KEY_LEN]; +} __attribute__((packed)); + +struct cntr_record { + uint8_t tar[TAR_LEN]; + uint8_t tar_mask[TAR_LEN]; + uint8_t cntr[CNTR_LEN]; +} __attribute__((packed)); + +/* TS 23.048 V5.9.0 Section 5.2 */ +#define RSC_POR_OK 0x00 +/* TS 131.115 V12.1.0 Section 7 */ +#define RSC_WILL_SMS_SUBMIT 0x0b + +/* Arbitrary limit for response sizes: "The limitation of 256 bytes does not + * apply for the length of the response data." */ +#define SS_UICC_REMOTE_COMMAND_RESPONSE_MAXSIZE 4096 + +/* See also ETSI TS 102 225, section 5.1.1 */ +enum cntr_mgmnt { + /* No counter available + * (the message contains the field, but it is ignored.) */ + CNTR_IGNORE = 0, + + /* Set the start value of the RE counter to the counter value from the + * SE (propritary) */ + CNTR_SET_START = 1, + + /* Check whether the counter value from the SE is greater than the + * counter in the RE, If so, top up the counter in the RE, so that it + * matches the counter value from the SE and increment it by one. */ + CNTR_CHECK_GREATER = 2, + + /* Check whether the counter value from the SE is one higher than the + * counter in the RE. If so, increment RE counter by one. */ + CNTR_CHECK_STRICT = 3, +}; + +/** Properties extracted from the header of a command packet */ +struct command_parameters { + /* Is the integrity protected by a cryptographic checksum? */ + bool in_cc; + /* Is the request integrity protection CC? */ + bool in_ciphering; + size_t in_integrity_len; + + /* Should the response be encrypted? */ + bool out_ciphering; + /* Is the response integrity protection CC? */ + bool out_cc; + size_t out_integrity_len; + + /* Encryption Keys (if any integrity / ciphering is set) */ + uint8_t kic_indication; + uint8_t kid_indication; + + /* Encryption Algorithm (if any integrity / ciphering is set) */ + enum enc_algorithm kic_algorithm; + enum enc_algorithm kid_algorithm; + + /* The TAR (Toolkit Application Reference) */ + uint8_t tar[3]; + + /* Replay detection and Sequence Integrity counter. */ + uint64_t cntr; + enum cntr_mgmnt cntr_mgmnt; + + /* Number of padding octets at the end of the message */ + uint8_t pcntr; + + /* ETSI TS 131 115, section 4.1, b6 of SPI2: + * 0: PoR via SMS-DELIVER-REPORT + * 1: PoR via SMS-SUBMIT */ + bool out_por_via_sms_submit; +}; + +/* Parse the clear text part of the command packet header + * (until and including tar). This function returns the length of the consumed + * header bytes or a suitable SW as error code (negative) */ +static int parse_cmd_hdr_clrtxt(struct command_parameters *param, + size_t cmd_packet_len, + const uint8_t *cmd_packet) +{ + /* CPL, CHL, SPI, KIc, KID, TAR */ + const size_t minimal_length = 2 + 1 + 2 + 1 + 1 + 3; + if (cmd_packet_len <= minimal_length || + (((size_t)cmd_packet[0] << 8) | cmd_packet[1]) != + cmd_packet_len - 2) { + SS_LOGP(SREMOTECMD, LERROR, + "Received comand packet too short\n"); + /* Is there any better guidance? This is only based on general ISO 7816 + * ENVELOPE descriptions. */ + return SS_SW_ERR_CHECKING_WRONG_LENGTH; + } + + SS_LOGP(SREMOTECMD, LDEBUG, + "command packet header data (cleartext): %s\n", + ss_hexdump(cmd_packet, 10)); + + /* Interpreting incoming message */ + uint8_t chl = cmd_packet[2]; + uint8_t spi1 = cmd_packet[3]; + uint8_t spi2 = cmd_packet[4]; + uint8_t kic = cmd_packet[5]; + uint8_t kid = cmd_packet[6]; + + SS_LOGP(SREMOTECMD, LDEBUG, + "Received command with CHL %02x, SPI %02x %02x, KIc %02x, KID %02x\n", + chl, spi1, spi2, kic, kid); + + if (chl < 4) { + /* Checking this after the DEBUG line because the access were all + * memory-valid, just not semantically valid within the command header, but + * the above should still be helpful in fixing that. */ + SS_LOGP(SREMOTECMD, LERROR, "CHL too short\n"); + return -SS_SW_ERR_CHECKING_WRONG_LENGTH; + } + + /* NOTE: for a SPI format see also ETSI TS 102 225, section 5.1.1 */ + + /* Masking to ignore reserved bits and bits that control the counter + * options (see also below) */ + switch (spi1 & 0x07) { + case 0x00: + param->in_cc = false; + param->in_ciphering = false; + param->in_integrity_len = 0; + break; + case 0x06: + param->in_cc = true; + param->in_ciphering = true; + param->in_integrity_len = OTA_INTEGRITY_LEN; + break; + default: + SS_LOGP(SREMOTECMD, LERROR, "Unsupported SPI1\n"); + /* Is that the correct error code? It's what SJA2 returns. */ + return -SS_SW_WARN_NO_INFO_NV_UNCHANGED; + } + + /* Parse counter options (see also enum cntr_mgmnt) */ + param->cntr_mgmnt = (spi1 >> 3) & 0x03; + + if ((spi2 & 0x03) != 0x01) { + SS_LOGP(SREMOTECMD, LERROR, + "SPI2 supported values are limited to \"PoR required\"\n"); + return -SS_SW_WARN_NO_INFO_NV_UNCHANGED; + } + param->out_ciphering = spi2 & 0x10; + switch (spi2 & 0x0c) { + case 0x00: + param->out_cc = false; + param->out_integrity_len = 0; + break; + case 0x08: + param->out_cc = true; + param->out_integrity_len = OTA_INTEGRITY_LEN; + break; + default: + SS_LOGP(SREMOTECMD, LERROR, + "Unsupported SPI2 integrity mode\n"); + return -SS_SW_WARN_NO_INFO_NV_UNCHANGED; + } + + param->out_por_via_sms_submit = (spi2 & 0x20) >> 5; + + if (chl != + 2 /* SPI */ + 1 /* KIc */ + 1 /* KID */ + TAR_LEN + + CNTR_LEN + 1 /* PCNTR */ + param->in_integrity_len) { + SS_LOGP(SREMOTECMD, LERROR, + "CHL does not match expected integrity length\n"); + return -SS_SW_WARN_NO_INFO_NV_UNCHANGED; + } + + /* Parse KIC/KID algorithm and indication */ + param->kic_indication = kic >> 4; + param->kid_indication = kid >> 4; + if (param->in_ciphering || param->out_ciphering) { + switch (kic & 0x0F) { + case 0x05: + param->kic_algorithm = TRIPLE_DES_CBC2; + break; + case 0x02: + param->kic_algorithm = AES_CBC; + break; + default: + SS_LOGP(SREMOTECMD, LERROR, + "Key KIc uses unsupported algorithm / key setup\n"); + return -SS_SW_WARN_NO_INFO_NV_UNCHANGED; + } + switch (kid & 0x0F) { + case 0x05: + param->kid_algorithm = TRIPLE_DES_CBC2; + break; + case 0x02: + param->kid_algorithm = AES_CMAC; + break; + default: + SS_LOGP(SREMOTECMD, LERROR, + "Key KID uses unsupported algorithm / key setup\n"); + return -SS_SW_WARN_NO_INFO_NV_UNCHANGED; + } + } else { + param->kic_algorithm = NONE; + param->kid_algorithm = NONE; + } + + memcpy(param->tar, &cmd_packet[7], TAR_LEN); + + SS_LOGP(SREMOTECMD, LDEBUG, "command parameters (cleartext):\n"); + SS_LOGP(SREMOTECMD, LDEBUG, " cryptographic checksum (in): %s\n", + param->in_cc ? "yes" : "no"); + SS_LOGP(SREMOTECMD, LDEBUG, " cryptographic checksum (out): %s\n", + param->out_cc ? "yes" : "no"); + SS_LOGP(SREMOTECMD, LDEBUG, " chiphering (in): %s\n", + param->in_ciphering ? "yes" : "no"); + SS_LOGP(SREMOTECMD, LDEBUG, " chiphering (out): %s\n", + param->out_ciphering ? "yes" : "no"); + SS_LOGP(SREMOTECMD, LDEBUG, " integrity parameter len (in): %zu\n", + param->in_integrity_len); + SS_LOGP(SREMOTECMD, LDEBUG, " integrity parameter len (out): %zu\n", + param->out_integrity_len); + SS_LOGP(SREMOTECMD, LDEBUG, " KIC indication: %02x\n", + param->kic_indication); + SS_LOGP(SREMOTECMD, LDEBUG, " KID indication: %02x\n", + param->kid_indication); + switch (param->kic_algorithm) { + case NONE: + SS_LOGP(SREMOTECMD, LDEBUG, " KIC algorithm: NONE\n"); + break; + case TRIPLE_DES_CBC2: + SS_LOGP(SREMOTECMD, LDEBUG, " KIC algorithm: 3DES CBC2\n"); + break; + case AES_CBC: + SS_LOGP(SREMOTECMD, LDEBUG, " KIC algorithm: AES CBC\n"); + break; + case AES_CMAC: + SS_LOGP(SREMOTECMD, LDEBUG, " KIC algorithm: AES CMAC\n"); + break; + default: + SS_LOGP(SREMOTECMD, LERROR, " KIC algorithm: invalid\n"); + return -SS_SW_WARN_NO_INFO_NV_UNCHANGED; + } + switch (param->kid_algorithm) { + case NONE: + SS_LOGP(SREMOTECMD, LDEBUG, " KID algorithm: NONE\n"); + break; + case TRIPLE_DES_CBC2: + SS_LOGP(SREMOTECMD, LDEBUG, " KID algorithm: 3DES CBC2\n"); + break; + case AES_CBC: + SS_LOGP(SREMOTECMD, LDEBUG, " KID algorithm: AES CBC\n"); + break; + case AES_CMAC: + SS_LOGP(SREMOTECMD, LDEBUG, " KID algorithm: AES CMAC\n"); + break; + default: + SS_LOGP(SREMOTECMD, LERROR, " KID algorithm: invalid\n"); + return -SS_SW_WARN_NO_INFO_NV_UNCHANGED; + } + SS_LOGP(SREMOTECMD, LDEBUG, " TAR: %s\n", + ss_hexdump(param->tar, sizeof(param->tar))); + switch (param->cntr_mgmnt) { + case CNTR_IGNORE: + SS_LOGP(SREMOTECMD, LDEBUG, " cntr mgmnt: ignore\n"); + break; + case CNTR_SET_START: + SS_LOGP(SREMOTECMD, LDEBUG, " cntr mgmnt: set start value\n"); + break; + case CNTR_CHECK_GREATER: + SS_LOGP(SREMOTECMD, LDEBUG, " cntr mgmnt: match greater\n"); + break; + case CNTR_CHECK_STRICT: + SS_LOGP(SREMOTECMD, LDEBUG, + " cntr mgmnt: match greater one (strict)\n"); + break; + default: + SS_LOGP(SREMOTECMD, LERROR, " cntr mgmnt: invalid\n"); + return -SS_SW_WARN_NO_INFO_NV_UNCHANGED; + } + + return 10; +} + +/* Parse the cipher text part (after decryption) of the command packet header + * This function returns the length of the consumed header bytes or a suitable + * SW as error code (negative). The input data must start at the beginning of + * the CNTR value. */ +static int parse_cmd_hdr_ciphtxt(struct command_parameters *param, + size_t cmd_packet_len, + const uint8_t *cmd_packet) +{ + SS_LOGP(SREMOTECMD, LDEBUG, + "command packet header data (decrypted ciphertext): %s\n", + ss_hexdump(cmd_packet, 6)); + + /* We need at least 6 bytes of data (5 byte CNTR + 1 byte PCNTR) */ + if (cmd_packet_len < 6) { + SS_LOGP(SREMOTECMD, LERROR, "message too short\n"); + return -SS_SW_ERR_CHECKING_WRONG_LENGTH; + } + + param->cntr = ss_uint64_from_array(&cmd_packet[0], CNTR_LEN); + param->pcntr = cmd_packet[5]; + + SS_LOGP(SREMOTECMD, LDEBUG, + "command parameters (decrypted cleartext):\n"); + SS_LOGP(SREMOTECMD, LDEBUG, " CNTR: %lu/%010lx\n", + param->cntr, param->cntr); + SS_LOGP(SREMOTECMD, LDEBUG, " PCNTR: %u/%02x\n", + param->pcntr, param->pcntr); + + return 6; +} + +/* Given a TAR for which credentials have been accepted, configure a context as + * required by that TAR's description. + * + * Reference: TS 102 226 V8.2.0 Section 7.3 and TS 101 220 V8.4.0 Annex D + * + * This typically involves two steps: + * - Selecting the relevant ADF that should be pre-selected before commands are + * executed in this TAR. This should follow the Annex D tabulated TARs to the + * extent implemented. + * - Setting the relevant PINs (CHVs) to be given, so that access control can + * be applied. The precise codes to be set might yet be configured; ADM1 is + * the default permission level associated with TARs per this implementer's + * choice. */ +static void setup_ctx_from_tar(struct ss_context *ctx, uint8_t *tar) +{ + /* Values from TS 101 221 Annex D. When extending, take care not to use any + * TARs that imply extended data format unless that is implemented and + * signalled to the decoder. */ + + if (tar[0] == 0xb0 && tar[1] == 0x00 && (tar[2] & 0xf0) == 0x10) { + /* b0 00 10 to b0 00 1f: SIM File system */ + /* SELECT ADF.USIM */ + ss_fs_select(&ctx->lchan.fs_path, 0x7ff0); + } else { + /* Add else-if chains as more are implemented. For what is currently + * available (UICC Shared File System), the default of keeping the MF + * selected is fine. */ + } + + /* Unconditional default until the need for further customization arises */ + ctx->lchan.pin_verfied[SS_PIN_ADM1] = true; +} + +/* Given a TAR and key IDs, provide key material. + * + * This populates kic and kid as necessitated by param. + * + * It returns true if all requested data has been populated, and the request + * matches the TAR's MSL. + * + * The precise evaluation logic is part of the file description provided with + * @ref TAR_KEY_FID. */ +static bool setup_keys_from_tar(struct command_parameters *param, uint8_t *kic, + uint8_t *kid) +{ + struct ss_list tar_path; + struct ss_buf *tar_buf; + struct ss_file *tar_file = NULL; + size_t n_tars; + bool checked_msl = false; + bool ready_kic = !(param->in_ciphering || param->out_ciphering); + bool ready_kid = !(param->in_cc || param->out_cc); + struct tar_record *record; + int rc; + int i; + + ss_fs_init(&tar_path); + rc = ss_fs_select(&tar_path, TAR_KEY_FID); + if (rc < 0) { + SS_LOGP(SREMOTECMD, LERROR, "TAR file not selectable\n"); + goto exit; + } + tar_file = ss_get_file_from_path(&tar_path); + if (tar_file == NULL) { + SS_LOGP(SREMOTECMD, LERROR, "TAR file not available\n"); + goto exit; + } + n_tars = tar_file->fcp_file_descr->number_of_records; + for (i = 0; i < n_tars && !(checked_msl && ready_kic && ready_kid); i++) { + tar_buf = ss_fs_read_file_record(&tar_path, i + 1); + if (!tar_buf) { + SS_LOGP(SREMOTECMD, LERROR, + "TAR file inconsistent -- cannot read record\n"); + goto exit; + } + if (tar_buf->len != sizeof(struct tar_record)) { + SS_LOGP(SREMOTECMD, LERROR, + "TAR file has wrong record length\n"); + i = n_tars; /* goto exit but leave through cleanup */ + goto continue_; + } + + /* Cast OK: Struct is packed, and none of its fields have alignment + * requirements */ + record = (struct tar_record *)tar_buf->data; + + if (memcmp(&record->tar, param->tar, TAR_LEN) != 0) + goto continue_; + + if (!checked_msl) { + /* TS 102 226 V9.4.0 Section 8.2.1.3.2.4.2 describes that check in more + * detail, but a full implementation is considered too error prone given + * that only two (really, one -- unencrypted is not recommended) values + * make sense */ + switch (record->msl) { + case 0x00: + SS_LOGP(SREMOTECMD, LINFO, + "Accepting SPI1 based on permissive MSL\n"); + checked_msl = true; + break; + case 0x06: + if (!param->in_cc || !param->in_ciphering) { + SS_LOGP(SREMOTECMD, LERROR, + "Request SPI1 does not satisfy MSL\n"); + i = n_tars; /* goto exit but leave through cleanup */ + goto continue_; + } else { + SS_LOGP(SREMOTECMD, LINFO, + "Accepting SPI1 as it is encrypted and cryptographically checksummed\n"); + checked_msl = true; + } + break; + default: + SS_LOGP(SREMOTECMD, LERROR, + "Unsupported MSL, rejecting\n"); + i = n_tars; /* goto exit but leave through cleanup */ + goto continue_; + } + } + + if (!ready_kic && + record->kic_indication != 0xff && + (record->kic_indication == param->kic_indication + || param->kic_indication == 0) + ) { + memcpy(kic, record->kic, OTA_KEY_LEN); + ready_kic = true; + } + + if (!ready_kid && + record->kid_indication != 0xff && + (record->kid_indication == param->kid_indication + || param->kid_indication == 0) + ) { + memcpy(kid, record->kid, OTA_KEY_LEN); + ready_kid = true; + } + +continue_: + ss_memzero(tar_buf->data, tar_buf->len); + ss_buf_free(tar_buf); + } + +exit: + ss_path_reset(&tar_path); + SS_LOGP(SREMOTECMD, LINFO, + "Key selection result: MSL check %d, KIC readiness %d, KID readiness %d\n", + checked_msl, ready_kic, ready_kid); + return checked_msl && ready_kic && ready_kid; +} + +/* Get the current CNTR value for a specified TAR (param). The record number of + * the matching record is also returned to directly update the record later. */ +static int get_cntr_from_tar(uint64_t * cntr, size_t *record_no, + struct command_parameters *param) +{ + struct ss_list cntr_path; + struct ss_buf *cntr_buf; + struct ss_file *cntr_file = NULL; + int rc_select; + size_t n_cntrs; + unsigned int i; + unsigned int k; + bool tar_match = false; + int rc = 0; + + *cntr = 0xffffffffff; + *record_no = 0; + + ss_fs_init(&cntr_path); + rc_select = ss_fs_select(&cntr_path, TAR_CNTR_FID); + if (rc_select < 0) { + SS_LOGP(SREMOTECMD, LERROR, "CNTR file not selectable\n"); + rc = -EINVAL; + goto exit; + } + cntr_file = ss_get_file_from_path(&cntr_path); + if (cntr_file == NULL) { + SS_LOGP(SREMOTECMD, LERROR, "CNTR file not available\n"); + rc = -EINVAL; + goto exit; + } + n_cntrs = cntr_file->fcp_file_descr->number_of_records; + + for (i = 0; i < n_cntrs; i++) { + cntr_buf = ss_fs_read_file_record(&cntr_path, i + 1); + if (!cntr_buf) { + SS_LOGP(SREMOTECMD, LERROR, + "CNTR file inconsistent -- cannot read record\n"); + rc = -EINVAL; + goto exit; + } + if (cntr_buf->len != sizeof(struct cntr_record)) { + SS_LOGP(SREMOTECMD, LERROR, + "CNTR file has wrong record length\n"); + i = n_cntrs; /* goto exit but leave through cleanup */ + ss_buf_free(cntr_buf); + rc = -EINVAL; + goto exit; + } + + /* Cast OK: Struct is packed, and none of its fields have alignment + * requirements */ + struct cntr_record *record = + (struct cntr_record *)cntr_buf->data; + + tar_match = true; + for (k = 0; k < TAR_LEN; k++) { + if ((record->tar[k] & record->tar_mask[k]) != + (param->tar[k] & record->tar_mask[k])) + tar_match = false; + } + + if (tar_match) { + *cntr = ss_uint64_from_array(record->cntr, CNTR_LEN); + *record_no = i + 1; + SS_LOGP(SREMOTECMD, LINFO, + "CNTR selection result: record %zu, TAR %s, TAR mask %s, CNTR %lu/%010lx\n", + *record_no, + ss_hexdump(record->tar, sizeof(record->tar)), + ss_hexdump(record->tar_mask, + sizeof(record->tar_mask)), *cntr, + *cntr); + ss_buf_free(cntr_buf); + break; + } + ss_buf_free(cntr_buf); + } + + if (!tar_match) { + SS_LOGP(SREMOTECMD, LERROR, + "CNTR file does not contain record for TAR %s\n", + ss_hexdump(param->tar, sizeof(param->tar))); + rc = -EINVAL; + } + +exit: + ss_path_reset(&cntr_path); + return rc; +} + +/* Updata a counter value at a specified record, use record number returned + * by get_cntr_from_tar(), which should be called earlier. */ +static int update_cntr(uint64_t cntr, size_t record_no) +{ + struct ss_list cntr_path; + struct ss_buf *cntr_buf = NULL; + struct ss_file *cntr_file = NULL; + struct cntr_record *record; + int rc = 0; + + ss_fs_init(&cntr_path); + rc = ss_fs_select(&cntr_path, TAR_CNTR_FID); + if (rc < 0) { + SS_LOGP(SREMOTECMD, LERROR, "CNTR file not selectable\n"); + rc = -EINVAL; + goto exit; + } + cntr_file = ss_get_file_from_path(&cntr_path); + if (cntr_file == NULL) { + SS_LOGP(SREMOTECMD, LERROR, "CNTR file not available\n"); + rc = -EINVAL; + goto exit; + } + + cntr_buf = ss_fs_read_file_record(&cntr_path, record_no); + if (!cntr_buf) { + SS_LOGP(SREMOTECMD, LERROR, + "CNTR file inconsistent -- cannot read record\n"); + rc = -EINVAL; + goto exit; + } + if (cntr_buf->len != sizeof(struct cntr_record)) { + SS_LOGP(SREMOTECMD, LERROR, + "CNTR file has wrong record length\n"); + rc = -EINVAL; + goto exit; + } + + /* Cast OK: Struct is packed, and none of its fields have alignment + * requirements */ + record = (struct cntr_record *)cntr_buf->data; + ss_array_from_uint64(record->cntr, CNTR_LEN, cntr); + + SS_LOGP(SREMOTECMD, LINFO, + "CNTR update: record %zu, CNTR %lu/%010lx\n", + record_no, cntr, cntr); + + rc = ss_fs_write_file_record(&cntr_path, record_no, cntr_buf->data, + cntr_buf->len); +exit: + ss_buf_free(cntr_buf); + ss_path_reset(&cntr_path); + return rc; +} + +/* Process decrypted commands + * + * @param[in] tar TAR that identifies the application, guides context setup and + * encodes the virtual terminal's authorizations + * @param[in] commands_len Number of bytes in @p commands + * @param[in] commands Commands encoded in Remote APDU format (TS 102 226 V9.4.0 Seciton 5.1) + * @param[in] outbuf_len Usable space inside @p outbuf. + * @param[out] outbuf Buffer into which the response is written. + * + * @return The number of bytes written to the @pb outbuf. + * + * While most of the error handling relevant for this happens internally (if + * anything goes wrong, it'll just return the index of the failed command and + * an unsuccessful SW), allocation errors before comand processing result in a + * return value of 0 (which is otherwise invalid, as the result encoding + * demands at least 1 byte for the command index, and 2 bytes of SW) */ +static size_t process_commands(uint8_t *tar, + size_t commands_len, + uint8_t *commands, + size_t outbuf_len, + uint8_t *outbuf, uint8_t *main_ctx_filelist) +{ + size_t this_command_length; + struct ss_context *ctx = ss_new_reporting_ctx(main_ctx_filelist); + size_t written_length = 0; + + if (ctx == NULL) + return 0; + SS_LOGP(SREMOTECMD, LDEBUG, + "+++++++++++++ command processing on RFM context begins ++++++++++++++\n"); + + ss_reset(ctx); + + setup_ctx_from_tar(ctx, tar); + + /* Number of commands executed within the script */ + outbuf[0] = 0; + + while (commands_len >= 4) { + /* Count number of executed commands */ + outbuf[0]++; + + SS_LOGP(SREMOTECMD, LDEBUG, "Processing command %d: %s\n", + outbuf[0], ss_hexdump(commands, commands_len)); + this_command_length = commands_len; + + /* Note: the compact APDU format used with RFM stores the SW at + * the beginning. But ss_transact() will store the SW at the + * end. We will use an offset in order to be able to put the + * SW at the beginning after the command excution. */ + written_length = + ss_transact(ctx, &outbuf[3], outbuf_len - 3, commands, + &this_command_length); + outbuf[1] = outbuf[1 + written_length]; + outbuf[2] = outbuf[2 + written_length]; + SS_LOGP(SREMOTECMD, LDEBUG, + "Command %d produced %ld bytes of output: %s\n", + outbuf[0], written_length, ss_hexdump(&outbuf[1], + written_length)); + + /* Align to the beginning of the next command. */ + commands_len -= this_command_length; + commands += this_command_length; + + /* Abort in case the command was not executed successfully. */ + if (!ss_sw_is_successful((outbuf[1] << 8) | outbuf[2])) { + SS_LOGP(SREMOTECMD, LINFO, + "Command %d was not successful, not executing any further commands\n", + outbuf[0]); + break; + } + } + if (commands_len != 0) { + SS_LOGP(SREMOTECMD, LERROR, + "%lu bytes left after last remote command; can not express error to remote (and might be OK after unsuccessful command).\n", + commands_len); + } + + SS_LOGP(SREMOTECMD, LDEBUG, + "+++++++++++++ command processing on RFM context ended +++++++++++++++\n"); + ss_free_ctx(ctx); + + return written_length + 1; +} + +/* Decrypt data using a specified algorithm. */ +static int decrypt(uint8_t *data, size_t data_len, uint8_t *key, + size_t key_len, enum enc_algorithm alg) +{ + switch (alg) { + case TRIPLE_DES_CBC2: + assert(key_len == TRIPLE_DES_KEYLEN); + if (data_len % DES_BLOCKSIZE != 0) { + SS_LOGP(SREMOTECMD, LERROR, + "cannot decrypt, ciphertext length (%zu) must be a multiple of %u (padding error)\n", + data_len, DES_BLOCKSIZE); + return -EINVAL; + } + ss_utils_3des_decrypt(data, data_len, key); + break; + case AES_CBC: + if (data_len % AES_BLOCKSIZE != 0) { + SS_LOGP(SREMOTECMD, LERROR, + "cannot decrypt, ciphertext length (%zu) must be a multiple of %u (padding error)\n", + data_len, AES_BLOCKSIZE); + return -EINVAL; + } + ss_utils_aes_decrypt(data, data_len, key, key_len); + break; + default: + SS_LOGP(SREMOTECMD, LERROR, + "unable to decrypt, improper crypto algorithm selected\n"); + return -EINVAL; + } + + return 0; +} + +/* Encrypt data using a specified algorithm. */ +static int encrypt(uint8_t *data, size_t data_len, uint8_t *key, + size_t key_len, enum enc_algorithm alg) +{ + switch (alg) { + case TRIPLE_DES_CBC2: + assert(key_len == TRIPLE_DES_KEYLEN); + if (data_len % DES_BLOCKSIZE != 0) { + SS_LOGP(SREMOTECMD, LERROR, + "cannot encrypt, ciphertext length (%zu) must be a multiple of %u (padding error)\n", + data_len, DES_BLOCKSIZE); + return -EINVAL; + } + ss_utils_3des_encrypt(data, data_len, key); + break; + case AES_CBC: + if (data_len % AES_BLOCKSIZE != 0) { + SS_LOGP(SREMOTECMD, LERROR, + "cannot encrypt, ciphertext length (%zu) must be a multiple of %u (padding error)\n", + data_len, AES_BLOCKSIZE); + return -EINVAL; + } + ss_utils_aes_encrypt(data, data_len, key, key_len); + break; + default: + SS_LOGP(SREMOTECMD, LERROR, + "unable to decrypt, improper crypto algorithm selected\n"); + return -EINVAL; + } + + return 0; +} + +/** + * Build, (usually) encrypt and integrity-protect a whole OTA message whose + * plaintext has already been put in place + * + * The plaintext needs to be placed already at the right offset inside the + * buffer. + * + * @param[inout] outbuf_len Size of @p outbuf. After the function has returned, + * this is lowered to the length of data that is now + * initialized in the buffer, and constitutes the + * message. + * @param[inout] outbuf Output buffer. The parts of it that contain the + * plaintext must already be initialized by the + * caller. + * @param[in] plaintext_len Size of @p plaintext + * @param[in] plaintext Buffer inside @p outbuf where the plaintext has + * been placed by the caller. These argument's + * correctness is assert()ed by this function.. + * @param[in] rsc Response Status Code to place in the encrypted part + * of the header + * @param[in] param Request parameters + * @param[in] kic_key Key KIC (for encryption) + * @param[in] kid_key Key KID (for integrity protection) + * @param[in] cntr Counter value (CNTR) to place in the encrypted part + * of the header + */ +static void build_message(uint8_t *outbuf, size_t *outbuf_len, + uint8_t *plaintext, size_t plaintext_len, + uint8_t rsc, struct command_parameters *param, + uint8_t *kic_key, uint8_t *kid_key, uint8_t *cntr) +{ + uint8_t cc[OTA_INTEGRITY_LEN]; + uint8_t pcnt = 0; + int rc; + size_t outbuf_len_orig = *outbuf_len; + + /* NOTE: when this function is called, *outbuf will already contain + * data that is decorated and encrypted to a valid response packet. */ + + if (plaintext_len != 0) { + /* On gross violations by the caller this would be UB, but it + * should detect the usual things that'd go wrong if one piece + * of the code started implementing variations that the other + * didn't account for */ + assert(&plaintext[plaintext_len] <= &outbuf[*outbuf_len]); + assert(plaintext == &outbuf[16 + param->out_integrity_len]); + } + + /* Calculate PCNT and apply padding at the correct location in the + * output buffer. */ + if (param->out_ciphering) { + /* The padding counter PCNT refers to the padding that is + * required to encrypt the message properly. It does not refer + * to the calculation of the cryptographic checksum (the CC + * calculation applies a suitable padding internally). The + * encrypted part of the message starts at the location of + * the sequence counter (CNTR). */ + pcnt = ss_utils_ota_calc_pcnt(param->kic_algorithm, + CNTR_LEN + PCNT_LEN + RSC_LEN + + param->out_integrity_len + plaintext_len); + }; + if (pcnt > 0) { + switch (param->kic_algorithm) + { + case AES_CBC: + /* NIST Special Publication 800-38A states that the padding for the AES should be 0x80 0x00 ... 0x00 */ + outbuf[16 + param->out_integrity_len + plaintext_len] = 0x80; + memset(&outbuf[16 + param->out_integrity_len + plaintext_len + 1], 0, pcnt - 1); + break; + case TRIPLE_DES_CBC2: + memset(&outbuf[16 + param->out_integrity_len + plaintext_len], 0, pcnt); + default: + break; + } + }; + + /* We don't have a shrinking realloc, but this is still a convenient + * place to store this length */ + *outbuf_len = 16 + param->out_integrity_len + plaintext_len + pcnt; + + /* User Data Header */ + outbuf[0] = 0x02; /* UDHL */ + outbuf[1] = IEI_RPI; /* IEIa: Response Packet Identifier */ + outbuf[2] = 0; /* IEIDLa, length of IEa data */ + /* Length of Response Packet */ + outbuf[3] = (*outbuf_len - 5) >> 8; + outbuf[4] = (*outbuf_len - 5); + /* RHL, Response Header length; TAR, integrity, CNTR, PCNT, response status code */ + outbuf[5] = 10 + param->out_integrity_len; + /* TAR */ + memcpy(&outbuf[6], ¶m->tar, sizeof(param->tar)); + /* CNTR */ + memcpy(&outbuf[9], cntr, CNTR_LEN); + outbuf[14] = pcnt; + /* Response Status Code */ + outbuf[15] = rsc; + + /* outbuf[16 + ...]@plaintext_len was populated already */ + + if (param->out_cc) { + /* "In order to achieve a modulo 8/16 length of the data before the + * RC/CC/DS field in the Response Header, the Length of the Response + * Packet, the Length of the Response Header and the three preceding + * octets (UDHL, IEIa and IEIDLa in the above table) shall be + * included in the calculation of RC/CC/DS if used." */ + rc = ss_utils_ota_calc_cc(cc, param->out_integrity_len, kid_key, OTA_KEY_LEN, + param->kid_algorithm, outbuf, 16, + &outbuf[16 + param->out_integrity_len], + plaintext_len + pcnt); + if (rc < 0) { + /* Clear output buffer before we leave, just to be sure no + * unencrypted data will leak. */ + memset(outbuf, 0, outbuf_len_orig); + *outbuf_len = 0; + return; + } + memcpy(&outbuf[16], cc, param->out_integrity_len); + } + + /* all set up for encryption */ + + if (param->out_ciphering) { + /* Encrypt everything after TAR */ + rc = encrypt(&outbuf[9], *outbuf_len - 9, kic_key, + OTA_KEY_LEN, param->kic_algorithm); + if (rc < 0) { + /* Clear output buffer before we leave, just to be sure no + * unencrypted data will leak. */ + memset(outbuf, 0, outbuf_len_orig); + *outbuf_len = 0; + return; + } + } +} + +/* Process a Command Packet (which is an SMS TPDU) and return a Response Packet + * (also an SMS TPDU) in the response buffer. + * + * @param[in] cmd_packet_len Length of @p cmd_packet + * @param[in] cmd_packet Input command; the UD (without header) of + * an SMS with an IEI_CPI header. + * @param[inout] response_len Length of the @p response buffer. The + * function decreases the pointed value to the + * actually populated length. + * @param[out] response Buffer for the SMS response (a message + * implicitly flagged to contain a UDH) that + * should be returned to the message the + * command packet arrived in. + * @param[out] sms_response Place at which a response message can be + * deposited; that message is returned by the + * caller in a response SMS that the caller is + * responsible to set up as indicating the + * presence of a UDH. + * + * @return the status word with which to respond to the SMS delivery that sent + * the command packet. */ +int ss_uicc_remote_cmd_receive(size_t cmd_packet_len, uint8_t *cmd_packet, + size_t *response_len, uint8_t *response, + struct ss_buf **sms_response, + uint8_t *main_ctx_filelist) +{ + struct command_parameters param; + int ret; + uint8_t kic_key[OTA_KEY_LEN]; + uint8_t kid_key[OTA_KEY_LEN]; + uint8_t *ciphertext; + size_t ciphertext_len; + uint8_t *plaintext; + size_t plaintext_len; + uint8_t *request_cc; + uint8_t compare_cc[OTA_INTEGRITY_LEN]; + struct ss_buf *response_message; + size_t command_output_length; + size_t ciph_hdr_len; + uint64_t cntr; + size_t cntr_rec_no; + int rc; + + /* Decode cleartext part of the command packet header */ + ret = parse_cmd_hdr_clrtxt(¶m, cmd_packet_len, cmd_packet); + if (ret <= 0) + return -ret; + + /* Decrypt the encrypted part of the command packet. This includes + * the remaining encrypted header bytes and the secured payload data */ + if (setup_keys_from_tar(¶m, kic_key, kid_key) == false) + return SS_SW_WARN_NO_INFO_NV_UNCHANGED; + ciphertext = &cmd_packet[ret]; + ciphertext_len = cmd_packet_len - ret; + SS_LOGP(SREMOTECMD, LDEBUG, "Ciphertext command: %s\n", + ss_hexdump(ciphertext, ciphertext_len)); + if (param.in_ciphering) { + rc = decrypt(ciphertext, ciphertext_len, kic_key, + sizeof(kic_key), param.kic_algorithm); + if (rc < 0) { + ret = SS_SW_WARN_NO_INFO_NV_UNCHANGED; + goto clear_out; + } + } + plaintext = ciphertext; + plaintext_len = ciphertext_len; + SS_LOGP(SREMOTECMD, LDEBUG, "Plaintext command: %s\n", + ss_hexdump(plaintext, plaintext_len)); + + ret = parse_cmd_hdr_ciphtxt(¶m, plaintext_len, plaintext); + if (ret <= 0) + return -ret; + ciph_hdr_len = ret; + + /* Guard against invalid length params */ + if (ciph_hdr_len + param.pcntr + param.in_integrity_len > plaintext_len) { + SS_LOGP(SREMOTECMD, LERROR, + "one or more inconsistent length params/fields received.\n"); + ret = SS_SW_WARN_NO_INFO_NV_UNCHANGED; + goto clear_out; + } + + /* The header is now completely in param, and can be split off */ + plaintext += ciph_hdr_len; + plaintext_len -= ciph_hdr_len; + + /* Calculate and verify the cryptographic checksum (if requested) */ + if (param.in_cc) { + request_cc = plaintext; + /* ... and splitting it out: */ + plaintext += param.in_integrity_len; + plaintext_len -= param.in_integrity_len; + + /* At this point, cmd_packet contains precisely what is needed + * to go with a non-buffering checksum: 2 bytes of CPL (not + * that we'd read it in any other place, currently), CHL, 2 + * bytes of SPI, KIC, KID, TAR, CNTR and PCNTR. */ + rc = ss_utils_ota_calc_cc(compare_cc, param.in_integrity_len, kid_key, + OTA_KEY_LEN, param.kid_algorithm, cmd_packet, 16, + plaintext, plaintext_len); + if (rc < 0) { + ret = SS_SW_WARN_NO_INFO_NV_UNCHANGED; + goto clear_out; + } + if (memcmp(request_cc, compare_cc, param.in_integrity_len) != 0) { + SS_LOGP(SREMOTECMD, LERROR, + "CC error, message was signed with: %s, local calculation result is: %s\n", + ss_hexdump(request_cc, param.in_integrity_len), + ss_hexdump(compare_cc, param.in_integrity_len)); + ret = SS_SW_WARN_NO_INFO_NV_UNCHANGED; + goto clear_out; + } + } + + /* Execute counter management logic (see also enum cntr_mgmnt) */ + rc = get_cntr_from_tar(&cntr, &cntr_rec_no, ¶m); + if (rc < 0) { + ret = SS_SW_WARN_NO_INFO_NV_UNCHANGED; + goto clear_out; + } + + switch (param.cntr_mgmnt) { + case CNTR_IGNORE: + SS_LOGP(SREMOTECMD, LDEBUG, + "No counter checks performed, counter ignored\n"); + break; + case CNTR_SET_START: + /* Make sure the counter cannot be overset. */ + if (param.cntr >= 0xffffffffff) + cntr = 0xffffffffff; + else + cntr = param.cntr; + SS_LOGP(SREMOTECMD, LDEBUG, + "No counter checks performed, counter set to: %lu\n", + cntr); + break; + case CNTR_CHECK_GREATER: + /* Detect blocked counter */ + if (cntr >= 0xffffffffff) { + SS_LOGP(SREMOTECMD, LDEBUG, + "Counter has reached its maximum value, blocked!\n"); + ret = SS_SW_WARN_NO_INFO_NV_UNCHANGED; + goto clear_out; + } + + /* Verify counter condition: must be greater than stored + * counter value */ + if (param.cntr <= cntr) { + SS_LOGP(SREMOTECMD, LDEBUG, + "Received counter value %lu not greater than stored counter value %lu\n", + param.cntr, cntr); + ret = SS_SW_WARN_NO_INFO_NV_UNCHANGED; + goto clear_out; + } + cntr = param.cntr; + SS_LOGP(SREMOTECMD, LDEBUG, + "Received counter value %lu greater than stored counter value, counter incremented to: %lu\n", + param.cntr, cntr); + break; + case CNTR_CHECK_STRICT: + /* Detect blocked counter */ + if (cntr >= 0xffffffffff) { + SS_LOGP(SREMOTECMD, LDEBUG, + "Counter has reached its maximum value, blocked!\n"); + ret = SS_SW_WARN_NO_INFO_NV_UNCHANGED; + goto clear_out; + } + + /* Verify counter condition: must be exactly greater one than + * stored counter value */ + if (param.cntr != cntr + 1) { + SS_LOGP(SREMOTECMD, LDEBUG, + "Received counter value %lu not greater by one than stored counter value %lu\n", + param.cntr, cntr); + ret = SS_SW_WARN_NO_INFO_NV_UNCHANGED; + goto clear_out; + } + cntr = param.cntr; + SS_LOGP(SREMOTECMD, LDEBUG, + "Received counter value %lu greater by one than stored counter value, counter incremented to: %lu\n", + param.cntr, cntr); + break; + default: + SS_LOGP(SREMOTECMD, LERROR, + "Invalid counter options requested\n"); + ret = SS_SW_WARN_NO_INFO_NV_UNCHANGED; + goto clear_out; + } + + rc = update_cntr(cntr, cntr_rec_no); + if (rc < 0) { + ret = SS_SW_WARN_NO_INFO_NV_UNCHANGED; + goto clear_out; + } + + /* NOTE: At this point the remote command message is decrypted and + * and verfied (cryptographic checksum). We now begin with the active + * execution of the remote command. */ + + /* Allocate memory for the response. */ + response_message = + ss_buf_alloc(SS_UICC_REMOTE_COMMAND_RESPONSE_MAXSIZE); + if (response_message == NULL) { + SS_LOGP(SREMOTECMD, LERROR, + "No space to allocate response message\n"); + ret = SS_SW_ERR_EXEC_MEMORY_PROBLEM; + goto clear_out; + } + + /* Execute the command string as RFM (remote file management) + * commands. */ + command_output_length = process_commands(param.tar, + plaintext_len - param.pcntr, + plaintext, + response_message->len - (16 + param.out_integrity_len), + &response_message->data[16 + param.out_integrity_len], + main_ctx_filelist); + if (command_output_length == 0) { + SS_LOGP(SREMOTECMD, LERROR, + "Command processing encountered internal allocation error\n"); + ret = SS_SW_ERR_EXEC_MEMORY_PROBLEM; + ss_buf_free(response_message); + goto clear_out; + } + + /* NOTE: the response is encrypted just the same, no matter whether it + * is encrypted to be sent in the confirmation or in a separate SMS, so + * we can encrypt the large part either way, and later decide where it + * goes. (we will either just copy the buffer once more or give the + * caller the ownership.) */ + build_message(response_message->data, &response_message->len, + &response_message->data[16 + param.out_integrity_len], + command_output_length, RSC_POR_OK, ¶m, + kic_key, kid_key, &cmd_packet[10]); + + /* Return response message to the caller */ + if (response_message->len <= *response_len && !param.out_por_via_sms_submit) { + /* The response fits in the GET RESPONSE buffer of the UICC, + * the MS will take care of the SMS sending. */ + memcpy(response, response_message->data, response_message->len); + SS_LOGP(SREMOTECMD, LERROR, "------------ setresponse len %ld\n", response_message->len); + *response_len = response_message->len; + ss_buf_free(response_message); + } else { + /* The response is to large to fit in the GET RESPONSE buffer + * of the UICC. The UICC (caller) will have to generate one + * or more SM himself and send them through STK commands. */ + SS_LOGP(SREMOTECMD, LDEBUG, + "Response too large for reply, will submit SMS instead.\n"); + + *sms_response = response_message; + + build_message(response, response_len, NULL, 0, + RSC_WILL_SMS_SUBMIT, ¶m, kic_key, kid_key, + &cmd_packet[10]); + } + ret = 0; + +clear_out: + ss_memzero(kic_key, sizeof(kic_key)); + ss_memzero(kid_key, sizeof(kid_key)); + return ret; +} diff --git a/src/softsim/uicc/uicc_remote_cmd.h b/src/softsim/uicc/uicc_remote_cmd.h new file mode 100644 index 0000000..2ede835 --- /dev/null +++ b/src/softsim/uicc/uicc_remote_cmd.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include +#include + +int ss_uicc_remote_cmd_receive(size_t cmd_packet_len, uint8_t *cmd_packet, + size_t *response_len, uint8_t *response, + struct ss_buf **sms_response, + uint8_t *main_ctx_filelist); diff --git a/src/softsim/uicc/uicc_sms_rx.c b/src/softsim/uicc/uicc_sms_rx.c new file mode 100644 index 0000000..35be5d4 --- /dev/null +++ b/src/softsim/uicc/uicc_sms_rx.c @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include +#include +#include "sw.h" +#include "command.h" +#include "uicc_cat.h" +#include "uicc_sms_rx.h" +#include "uicc_remote_cmd.h" +#include "uicc_ins.h" +#include "uicc_lchan.h" +#include "apdu.h" +#include "context.h" +#include "btlv.h" +#include "tlv8.h" +#include "sms.h" + +/* Information element identifier for command packets, as used in TS 23.048 + * V5.9.0 Seciton 6.2 */ +#define IEI_CPI 0x70 + +static void clear_state(struct ss_uicc_sms_rx_state *state) +{ + struct ss_uicc_sms_rx_sm *sm; + struct ss_uicc_sms_rx_sm *sm_pre; + + /* When the number of message parts is set to zero, there is no + * multi part SMS reception in progress, wich means we can simply + * wipe of all state with zeros. There will be no SMS message in + * the list that could leak memory. */ + if (state->msg_parts == 0) + goto leave; + + SS_LIST_FOR_EACH_SAVE(&state->sm, sm, sm_pre, + struct ss_uicc_sms_rx_sm, list) { + ss_list_remove(&sm->list); + SS_FREE(sm); + } + +leave: + memset(state, 0, sizeof(*state)); + ss_list_init(&state->sm); +} + +/*! Clear CAT SMS state, needs to be executed once on startup. + * \param[inout] ctx softsim context. */ +void ss_uicc_sms_rx_clear(struct ss_context *ctx) +{ + struct ss_uicc_sms_rx_state *state = &ctx->proactive.sms_rx_state; + clear_state(state); +} + +/* Get an SM part we have received before from the SM list. */ +static struct ss_uicc_sms_rx_sm *get_sm_part(struct ss_uicc_sms_rx_state + *state, uint8_t msg_part_no) +{ + struct ss_uicc_sms_rx_sm *sm; + SS_LIST_FOR_EACH(&state->sm, sm, struct ss_uicc_sms_rx_sm, list) { + if (sm->msg_part_no == msg_part_no) + return sm; + } + + return NULL; +} + +/* Put an SM part into SM list for later processing. */ +static int put_sm_part(struct ss_uicc_sms_rx_state *state, + struct ss_uicc_sms_rx_sm *sm) +{ + struct ss_uicc_sms_rx_sm *sm_i; + + /* Ignore duplicates */ + SS_LIST_FOR_EACH(&state->sm, sm_i, struct ss_uicc_sms_rx_sm, list) { + if (sm_i->msg_part_no == sm->msg_part_no) { + SS_LOGP(SSMS, LERROR, + "ignoring duplicate part %u/%u of message %u\n", + sm->msg_part_no, state->msg_parts, sm->msg_id); + return -EINVAL; + } + } + + ss_list_put(&state->sm, &sm->list); + return 0; +} + +/* Collect and when complete concatenate all SM parts to one large SM */ +static struct ss_buf *concat_sm(struct ss_uicc_sms_rx_state *state, + uint8_t *tp_ud, size_t tp_ud_len, + struct tlv8_ie *concat_sm_desc_ie) +{ + struct ss_uicc_sms_rx_sm *sm; + uint8_t i; + size_t result_len = 0; + struct ss_buf *result = NULL; + uint8_t *result_ptr; + int rc; + uint8_t msg_id = concat_sm_desc_ie->value->data[0]; + uint8_t msg_parts = concat_sm_desc_ie->value->data[1]; + uint8_t msg_part_no = concat_sm_desc_ie->value->data[2]; + + SS_LOGP(SSMS, LERROR, "receiving part %u/%u of message %u: %s\n", + msg_part_no, msg_parts, msg_id, ss_hexdump(tp_ud, tp_ud_len)); + + /* Clear state when a new message is detected */ + if (state->msg_id != msg_id) { + SS_LOGP(SSMS, LERROR, + "message %u is a new message, clearing state.\n", + msg_id); + clear_state(state); + state->msg_id = msg_id; + state->msg_parts = msg_parts; + } + + /* Make sure that each message reports the same number of message + * parts */ + if (msg_parts != state->msg_parts) { + SS_LOGP(SSMS, LERROR, + "message part %u of message %u reports invalid number of message parts expected %u, got %u\n", + msg_part_no, msg_id, state->msg_parts, msg_parts); + clear_state(state); + return NULL; + } + + /* Make sure that the message id cannot be larger than the expected number of + * messages. The message id also mut not be 0 */ + if (msg_part_no > state->msg_parts) { + SS_LOGP(SSMS, LERROR, + "message %u reports invalid message part number %u, expecting id in range 1-%u.\n", + msg_id, msg_part_no, state->msg_parts); + clear_state(state); + return NULL; + } + + /* NOTE: In reality, the message cannot be longer than 140 octets, + * so we won't see the following error message unless there are + * serios software problems elsewhere. */ + if (tp_ud_len > sizeof(sm->tp_ud)) { + SS_LOGP(SSMS, LERROR, + "receiving part %u/%u of message %u exceeds size of a normal SMS, expected < %zu octets, got %zu octets.\n", + msg_part_no, msg_parts, msg_id, sizeof(sm->tp_ud), + tp_ud_len); + return NULL; + } + + /* Store message in list */ + sm = SS_ALLOC(struct ss_uicc_sms_rx_sm); + memcpy(sm->tp_ud, tp_ud, tp_ud_len); + sm->tp_ud_len = tp_ud_len; + sm->msg_part_no = msg_part_no; + sm->msg_id = msg_id; + rc = put_sm_part(state, sm); + if (rc < 0) { + SS_FREE(sm); + return NULL; + } + + /* Check if we got the complete message */ + for (i = 0; i < msg_parts; i++) { + sm = get_sm_part(state, i + 1); + if (!sm) { + SS_LOGP(SSMS, LDEBUG, + "message %u is not complete yet, still waiting for message part %u/%u.\n", + msg_id, msg_parts, i + 1); + return NULL; + } + + result_len += sm->tp_ud_len; + } + + /* Concatenate message */ + result = ss_buf_alloc(result_len); + result_ptr = result->data; + for (i = 0; i < msg_parts; i++) { + sm = get_sm_part(state, i + 1); + if (!sm) + assert(false); + if (sm->msg_id != msg_id) + assert(false); + memcpy(result_ptr, sm->tp_ud, sm->tp_ud_len); + result_ptr += sm->tp_ud_len; + } + + SS_LOGP(SSMS, LDEBUG, "message %u complete: %s\n", msg_id, + ss_hexdump(result->data, result->len)); + clear_state(state); + return result; +} + +/* Process the tp_ud data we have received from either single SM or multiple + * concatenated delivered SMs + * + * The response arguments behave like those of @ref ss_uicc_sms_rx. + * */ +static int handle_sm(struct ss_context *ctx, struct ss_sm_hdr *sm_hdr, + uint8_t *ud_hdr, size_t ud_hdr_len, uint8_t *tp_ud, + size_t tp_ud_len, size_t *response_len, + uint8_t response[*response_len]) +{ + int rc; + + assert(sm_hdr->tp_mti == SMS_MTI_DELIVER); + + /* IEIa -- first information element identifier; typically 0x70 = CPI + * + * Left at 0 if UDHI is unset; that case can be treated like any unknown + * IEIOa */ + uint8_t ieia = 0; + + if (ud_hdr_len >= 2) { + ieia = ud_hdr[0]; + /* Ignoring both IEIDa (data for IE a) and any further IEs, as + * none of them are used in the currently only implemented case */ + } + + switch (ieia) { + case IEI_CPI: ; + struct ss_buf *sms_response = NULL; + rc = ss_uicc_remote_cmd_receive(tp_ud_len, tp_ud, response_len, response, &sms_response, ctx->fs_chg_filelist); + + if (sms_response != NULL) { + struct ss_sm_hdr response_hdr; + memset(&response_hdr, 0, sizeof(response_hdr)); + + response_hdr.tp_mti = SMS_MTI_SUBMIT; + response_hdr.u.sms_submit.tp_da.extension = true; + memcpy(&response_hdr.u.sms_submit.tp_da, &sm_hdr->u.sms_deliver.tp_oa, sizeof(struct ss_sms_addr)); + /* TP-Protocol-Identifier: unsure */ + response_hdr.u.sms_submit.tp_pid = 127; + /* data coding scheme: 8-bit data */ + response_hdr.u.sms_submit.tp_dcs = 246; + /* UDHI gets set automatically when encode_sm gets its hands on it */ + + ss_uicc_sms_tx( + ctx, + &response_hdr, + /* The response is a single blob with both UDH and UD, which makes + * sense there as that's part of what gets integrity protected, but as + * sms_tx needs to fragment it, we're dissecting the message for it */ + &sms_response->data[1], + sms_response->data[0], + &sms_response->data[1 + sms_response->data[0]], + sms_response->len - 1 - sms_response->data[0], + /* Couldn't do anything else than debug logging */ + NULL + ); + SS_LOGP(SSMS, LDEBUG, "Enqueued SMS in response to command\n"); + ss_buf_free(sms_response); + } + break; + default: + SS_LOGP(SSMS, LDEBUG, "received sms TP-UD with unknown IEIa=%02x:%s\n", ieia, ss_hexdump(tp_ud, tp_ud_len)); + rc = -1; + } + + return rc; +} + +/*! Receive an SM. + * \param[inout] ctx softsim context. + * \param[in] data encoded SM. + * \param[inout] response_len Pointer to what is initially the maximum size of + * response; changed to the filled size on 0 (successul) returns. + * \param[out] response Buffer in which a response to the envelope command in + * which the SMS-PP download arrived. + * \returns ISO7816 SW or 0 on success. */ +int ss_uicc_sms_rx(struct ss_context *ctx, struct ss_buf *sms_tpdu, + size_t *response_len, uint8_t response[*response_len]) +{ + struct ss_uicc_sms_rx_state *state = &ctx->proactive.sms_rx_state; + + int rc = 0; + struct ss_sm_hdr sm_hdr; + int sm_hdr_len; + + uint8_t *tp_ud; + size_t tp_ud_len; + + uint8_t *ud_hdr = NULL; + size_t ud_hdr_len = 0; + + struct ss_list *ud_hdr_dec = NULL; + struct tlv8_ie *concat_sm_desc_ie = NULL; + struct ss_buf *concat_sm_buf; + + sm_hdr_len = ss_sms_hdr_decode(&sm_hdr, sms_tpdu->data, sms_tpdu->len); + if (sm_hdr_len < 0) { + SS_LOGP(SSMS, LERROR, "failed to decode SMS TPDU header.\n"); + *response_len = 0; + goto leave; + } + assert(sm_hdr_len <= sms_tpdu->len); + + switch (sm_hdr.tp_mti) { + case SMS_MTI_DELIVER: + tp_ud = sms_tpdu->data + sm_hdr_len; + tp_ud_len = sms_tpdu->len - sm_hdr_len; + if (sm_hdr.u.sms_deliver.tp_udhi) { + if (tp_ud[0] + 1 <= tp_ud_len) { + ud_hdr = tp_ud + 1; + ud_hdr_len = tp_ud[0]; + + SS_LOGP(SSMS, LDEBUG, + "received sms TP-UD header: %s\n", + ss_hexdump(ud_hdr, ud_hdr_len)); + ud_hdr_dec = ss_tlv8_decode(ud_hdr, ud_hdr_len); + if (!ud_hdr_dec) { + SS_LOGP(SSMS, LERROR, + "failed to decode user data header, invalid TLV data\n"); + *response_len = 0; + goto leave; + } + ss_tlv8_dump(ud_hdr_dec, 2, SSMS, LDEBUG); + + /* Advance pointers to actual user data */ + tp_ud_len -= 1 + tp_ud[0]; + tp_ud += 1 + tp_ud[0]; + + /* Part of a concatencated SM received, collect partial messages */ + concat_sm_desc_ie = + ss_tlv8_get_ie_minlen(ud_hdr_dec, + TS_23_040_IEI_CONCAT_SMS, + 3); + if (concat_sm_desc_ie) { + concat_sm_buf = + concat_sm(state, tp_ud, + tp_ud_len, + concat_sm_desc_ie); + if (concat_sm_buf) { + rc = handle_sm(ctx, + &sm_hdr, + ud_hdr, + ud_hdr_len, + concat_sm_buf-> + data, + concat_sm_buf-> + len, + response_len, + response); + ss_buf_free(concat_sm_buf); + if (rc < 0) + *response_len = 0; + } + } + + } else { + SS_LOGP(SSMS, LERROR, + "failed to decode user data header, length field exceeds TP-UD length\n"); + rc = SS_SW_ERR_WRONG_PARAM_INCORRECT_DATA; + *response_len = 0; + goto leave; + } + } + + /* Normal SM received, forward directly */ + if (!concat_sm_desc_ie) { + SS_LOGP(SSMS, LDEBUG, "received sms TP-UD: %s\n", + ss_hexdump(tp_ud, tp_ud_len)); + rc = handle_sm(ctx, &sm_hdr, ud_hdr, ud_hdr_len, + tp_ud, tp_ud_len, response_len, response); + if (rc < 0) + *response_len = 0; + } + break; + default: + SS_LOGP(SSMS, LINFO, + "Unspported SMS message type (%u) received -- ignored!\n", + sm_hdr.tp_mti & 0x03); + *response_len = 0; + break; + } + +leave: + ss_tlv8_free(ud_hdr_dec); + return rc; +} diff --git a/src/softsim/uicc/uicc_sms_rx.h b/src/softsim/uicc/uicc_sms_rx.h new file mode 100644 index 0000000..e118600 --- /dev/null +++ b/src/softsim/uicc/uicc_sms_rx.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include "sms.h" + +struct ss_uicc_sms_rx_sm { + struct ss_list list; + uint8_t msg_id; + uint8_t msg_part_no; + uint8_t tp_ud[SMS_MAX_SIZE]; + size_t tp_ud_len; +}; + +/* Note: It is expected that the contents of ss_uicc_sms_rx_state are set to + * zero before carrying out any operation, including ss_uicc_sms_rx_clear() */ +struct ss_uicc_sms_rx_state { + uint8_t msg_id; + uint8_t msg_parts; + struct ss_list sm; +}; + +struct ss_buf; +struct ss_context; + +void ss_uicc_sms_rx_clear(struct ss_context *ctx); +int ss_uicc_sms_rx(struct ss_context *ctx, struct ss_buf *sms_tpdu, + size_t *response_len, uint8_t response[*response_len]); diff --git a/src/softsim/uicc/uicc_sms_tx.c b/src/softsim/uicc/uicc_sms_tx.c new file mode 100644 index 0000000..11606ec --- /dev/null +++ b/src/softsim/uicc/uicc_sms_tx.c @@ -0,0 +1,510 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include +#include +#include "sw.h" +#include "command.h" +#include "uicc_cat.h" +#include "uicc_sms_tx.h" +#include "uicc_ins.h" +#include "uicc_lchan.h" +#include "apdu.h" +#include "context.h" +#include "btlv.h" +#include "ctlv.h" +#include "tlv8.h" +#include "sms.h" +#include "uicc_sms_tx.h" + +/* Check if the TERMINAL PROFILE supports sending of short messages */ +static bool check_cat_support(struct ss_context *ctx) +{ + /* See also ETSI TS 102 223, section 5.2 + and/or ETSI TS 131 111 section 5.2 */ + + /* SMS-PP data download */ + if (!ss_proactive_term_prof_bit(ctx, 1, 2)) + return false; + + /* Bit = 1 if SMS-PP data download is supported */ + if (!ss_proactive_term_prof_bit(ctx, 1, 5)) + return false; + + /* Proactive UICC: SEND SHORT MESSAGE */ + if (!ss_proactive_term_prof_bit(ctx, 4, 2)) + return false; + + return true; +} + +static void clear_state(struct ss_uicc_sms_tx_state *state) +{ + struct ss_uicc_sms_tx_sm *sm; + struct ss_uicc_sms_tx_sm *sm_pre; + + if (!ss_list_initialized(&state->sm)) + goto leave; + + SS_LIST_FOR_EACH_SAVE(&state->sm, sm, sm_pre, + struct ss_uicc_sms_tx_sm, list) { + ss_list_remove(&sm->list); + SS_FREE(sm); + } + +leave: + memset(state, 0, sizeof(*state)); + ss_list_init(&state->sm); +} + +/*! Clear CAT SMS state, needs to be executed once on startup. + * \param[inout] ctx softsim context. */ +void ss_uicc_sms_tx_clear(struct ss_context *ctx) +{ + struct ss_uicc_sms_tx_state *state = &ctx->proactive.sms_tx_state; + clear_state(state); +} + +/* Encode a single short message TPDU */ +int encode_sm(uint8_t *sm_enc, size_t sm_enc_len, const struct ss_sm_hdr *sm_hdr, + const uint8_t *ud_hdr, size_t ud_hdr_len, const uint8_t *tp_ud, + size_t tp_ud_len, bool recalc_tp_udl) +{ + int rc; + size_t bytes_used = 0; + uint8_t *tp_udl = NULL; + uint8_t tp_dcs; + struct ss_sm_hdr _sm_hdr; + struct ss_sm_hdr *sm_hdr_copy = &_sm_hdr; + + /* Note: the data for ud_hdr must not contain the length field at its + * beginning. The length is prepended automatically by this function. */ + + /* Note: the parameter tp_ud can also be tp_cd in case an SMS-COMMAND + * message is sent. */ + + /* In cases where the tp_udl value in the header struct is not + * populated we will fill in this value automatically with a calculated + * result. This currently works only for messages that use the + * "Data coding/message class" with 8 bit data message coding. + * See also: 3GPP TS 23.038, section 4. */ + + /* The SM header may be subject to change, so we create a local copy to + * avoid side effects. */ + memcpy(sm_hdr_copy, sm_hdr, sizeof(*sm_hdr_copy)); + + switch (sm_hdr_copy->tp_mti) { + case SMS_MTI_DELIVER_REPORT: + tp_udl = &sm_hdr_copy->u.sms_deliver.tp_udl; + tp_dcs = sm_hdr_copy->u.sms_deliver.tp_dcs; + break; + case SMS_MTI_SUBMIT: + tp_udl = &sm_hdr_copy->u.sms_submit.tp_udl; + tp_dcs = sm_hdr_copy->u.sms_submit.tp_dcs; + break; + case SMS_MTI_COMMAND: + tp_udl = &sm_hdr_copy->u.sms_command.tp_cdl; + tp_dcs = 0xff; /* not applicable */ + break; + default: + SS_LOGP(SSMS, LERROR, + "failed to encode incompatible SMS-TPDU (tp-mti=%02x)\n", + sm_hdr_copy->tp_mti & 3); + return -EINVAL; + } + if ((tp_ud_len || ud_hdr_len) && (*tp_udl == 0 || recalc_tp_udl)) { + if ((tp_dcs & 0xF4) != 0xF4) { + SS_LOGP(SSMS, LERROR, + "failed to encode message with incompatible data coding scheme (tp-dcs=%02x)\n", + tp_dcs); + return -EINVAL; + } + /* A user data header needs one byte length field + the actual + * header data. */ + if (ud_hdr && ud_hdr_len) + *tp_udl = (uint8_t) (1 + ud_hdr_len); + + /* Add the length of the actual user data (this only works for + * 8 bit encoding, see error message above) */ + if (tp_ud && tp_ud_len) + *tp_udl += (uint8_t) tp_ud_len; + + SS_LOGP(SSMS, LINFO, + "using calculated value for tp_udl=%u (8 bit encoding)\n", *tp_udl); + } + + /* Ensure that the User Data Header Indicator is set in case a user + * data header is present */ + if (ud_hdr && ud_hdr_len > 0) { + switch (sm_hdr_copy->tp_mti) { + case SMS_MTI_DELIVER_REPORT: + sm_hdr_copy->u.sms_deliver.tp_udhi = true; + break; + case SMS_MTI_SUBMIT: + sm_hdr_copy->u.sms_submit.tp_udhi = true; + break; + case SMS_MTI_COMMAND: + sm_hdr_copy->u.sms_command.tp_udhi = true; + break; + default: + SS_LOGP(SSMS, LERROR, + "failed to encode incompatible SMS-TPDU (tp-mti=%02x)\n", + sm_hdr_copy->tp_mti & 3); + return -EINVAL; + } + } + + /* Encode header */ + rc = ss_sms_hdr_encode(sm_enc, sm_enc_len, sm_hdr_copy); + if (rc < 0) { + SS_LOGP(SSMS, LERROR, "failed to encode SMS-TPDU header.\n"); + return -EINVAL; + } + sm_enc_len -= rc; + sm_enc += rc; + bytes_used += rc; + + /* Copy user data header (if present) */ + if (ud_hdr && ud_hdr_len > 0) { + if (sm_enc_len < ud_hdr_len + 1) { + SS_LOGP(SSMS, LERROR, + "failed to encode SMS-TPDU, no space to fit user data header\n"); + return -EINVAL; + } + + if (ud_hdr_len > 254) { + SS_LOGP(SSMS, LERROR, + "failed to encode SMS-TPDU, data header too large\n"); + return -EINVAL; + } + + /* Prepend user data header length */ + sm_enc[0] = (uint8_t) ud_hdr_len; + sm_enc_len -= 1; + sm_enc += 1; + bytes_used += 1; + + /* Copy user data header data */ + memcpy(sm_enc, ud_hdr, ud_hdr_len); + sm_enc_len -= ud_hdr_len; + sm_enc += ud_hdr_len; + bytes_used += ud_hdr_len; + } + + /* Copy user data (if present) */ + if (tp_ud && tp_ud_len > 0) { + if (sm_enc_len < tp_ud_len) { + SS_LOGP(SSMS, LERROR, + "failed to encode SMS-TPDU, no space to fit user data\n"); + return -EINVAL; + } + memcpy(sm_enc, tp_ud, tp_ud_len); + sm_enc_len -= tp_ud_len; + sm_enc += tp_ud_len; + bytes_used += tp_ud_len; + } + + return bytes_used; +} + +static int sms_tx_single(struct ss_uicc_sms_tx_state *state, + const struct ss_sm_hdr *sm_hdr, + const uint8_t *ud_hdr, size_t ud_hdr_len, + const uint8_t *tp_ud, size_t tp_ud_len, + sms_result_cb sms_result_cb, bool last_msg, + uint8_t msg_id, bool recalc_tp_udl) +{ + struct ss_uicc_sms_tx_sm *sm; + int rc; + + /* Initialize queue in case it hasn't been initialized yet */ + if (!ss_list_initialized(&state->sm)) + clear_state(state); + + /* Encode and Enqueue SMS TPDU */ + sm = SS_ALLOC(struct ss_uicc_sms_tx_sm); + rc = encode_sm(sm->msg, sizeof(sm->msg), sm_hdr, ud_hdr, ud_hdr_len, + tp_ud, tp_ud_len, recalc_tp_udl); + if (rc < 0) { + SS_FREE(sm); + SS_LOGP(SSMS, LERROR, "error encoding SMS-TPDU - tossed!\n"); + return -EINVAL; + } + sm->msg_len = rc; + sm->sms_result_cb = sms_result_cb; + sm->last_msg = last_msg; + sm->msg_id = msg_id; + SS_LOGP(SSMS, LINFO, "enqueueing SMS-TPDU: %s\n", + ss_hexdump(sm->msg, sm->msg_len)); + ss_list_put(&state->sm, &sm->list); + + return 0; +} + +/* Calculate the number of message parts that will be needed */ +static uint8_t calc_message_parts(size_t ud_hdr_len, size_t tp_ud_len) +{ + size_t total_len; + size_t result; + + total_len = ud_hdr_len + tp_ud_len; + + /* 5 byte (concat_sm_descr IE) + 1 byte user data header length field + * will be subtracted from the overall useful bytes of the SM + * (SMS_MAX_SIZE) */ + + result = total_len / (SMS_MAX_SIZE - 6); + if (total_len % (SMS_MAX_SIZE - 6)) + result++; + + if (result > 0xff) { + SS_LOGP(SSMS, LERROR, "message too large!\n"); + return 0; + } + + return (uint8_t) result; +} + +/* Remove all messages with a specified message id from the queue. This is + * usually done to remove already scheduled parts of a concatenated SM from + * the queue in case there is an error while generating and enqueing the + * partial messages. */ +void cancel_sm(struct ss_uicc_sms_tx_state *state, uint8_t msg_id) +{ + struct ss_uicc_sms_tx_sm *sm; + struct ss_uicc_sms_tx_sm *sm_pre; + + if (!ss_list_initialized(&state->sm)) + return; + + SS_LIST_FOR_EACH_SAVE(&state->sm, sm, sm_pre, + struct ss_uicc_sms_tx_sm, list) { + if (sm->msg_id == msg_id) { + SS_LOGP(SSMS, LINFO, + "canceling pending SMS-TPDU: %s\n", + ss_hexdump(sm->msg, sm->msg_len)); + ss_list_remove(&sm->list); + SS_FREE(sm); + } + } +} + +/*! Send an SM. + * \param[inout] ctx softsim context. + * \param[inout] sm_hdr user provided memory with SMS TPDU header struct. + * \param[in] ud_hdr user provided memory with user data header (encoded). + * \param[in] ud_hdr_len user data header length. + * \param[in] tp_ud user provided memory with user data. + * \param[in] tp_ud_len user data length. + * \param[in] ms_rsult_cb callback to inform caller about the outcome. + * \returns ISO7816 SW or 0 on success. */ +int ss_uicc_sms_tx(struct ss_context *ctx, + struct ss_sm_hdr *sm_hdr, + uint8_t *ud_hdr, size_t ud_hdr_len, + uint8_t *tp_ud, size_t tp_ud_len, + sms_result_cb sms_result_cb) +{ + struct ss_uicc_sms_tx_state *state = &ctx->proactive.sms_tx_state; + + uint8_t concat_sm_descr[5]; + uint8_t msg_parts; + uint8_t i; + uint8_t ud_hdr_buf[SMS_MAX_SIZE]; + size_t ud_hdr_buf_len; + int rc; + size_t tp_ud_len_window; + uint8_t *tp_ud_ptr; + + state->msg_id++; + + /* Check if user data and user data header will fit in a single SM. */ + if (ud_hdr_len + 1 + tp_ud_len <= SMS_MAX_SIZE) { + rc = sms_tx_single(state, sm_hdr, ud_hdr, ud_hdr_len, tp_ud, + tp_ud_len, sms_result_cb, true, + state->msg_id, false); + + /* Give the SMS a chance to go out immediately */ + ss_uicc_sms_tx_poll(ctx); + return rc; + } + + /* Split up, encode and enqueue the message */ + msg_parts = calc_message_parts(ud_hdr_len, tp_ud_len); + concat_sm_descr[0] = TS_23_040_IEI_CONCAT_SMS; + concat_sm_descr[1] = 0x03; + concat_sm_descr[2] = state->msg_id; + concat_sm_descr[3] = msg_parts; + tp_ud_ptr = tp_ud; + SS_LOGP(SSMS, LINFO, + "user data too large for a single SM, splitting into %u separate SMs, message id is: %u\n", + msg_parts, state->msg_id); + for (i = 1; i <= msg_parts; i++) { + concat_sm_descr[4] = i; + + /* Copy the user data header with concat SM descriptor IE */ + memcpy(ud_hdr_buf, concat_sm_descr, sizeof(concat_sm_descr)); + ud_hdr_buf_len = sizeof(concat_sm_descr); + + /* Copy the user data header part that the caller has + * specified, but only in the first message. */ + if (ud_hdr && ud_hdr_len > 0) { + memcpy(ud_hdr_buf + ud_hdr_buf_len, ud_hdr, ud_hdr_len); + ud_hdr_buf_len += ud_hdr_len; + ud_hdr_len = 0; + ud_hdr = NULL; + } + + /* Calculate window size. This will be the user data header + * length (including its length byte) minus the maximum useful + * byte size of an SM in all cases ecept for the last message, + * where the remainder of the message is sent. */ + if (i == msg_parts) + tp_ud_len_window = tp_ud_len - (tp_ud_ptr - tp_ud); + else + tp_ud_len_window = SMS_MAX_SIZE - ud_hdr_buf_len - 1; + + /* Enqueue message and point tp_ud_ptr to the beginning of the + * user data window that is transmitted with the next turn. */ + rc = sms_tx_single(state, sm_hdr, ud_hdr_buf, ud_hdr_buf_len, + tp_ud_ptr, tp_ud_len_window, sms_result_cb, + (i == msg_parts), state->msg_id, true); + if (rc < 0) { + SS_LOGP(SSMS, LERROR, + "unable to send part %u/%u of message %u!\n", i, + msg_parts, state->msg_id); + cancel_sm(state, state->msg_id); + return -EINVAL; + } + + tp_ud_ptr += tp_ud_len_window; + } + + /* Give the SMS a chance to go out immediately */ + ss_uicc_sms_tx_poll(ctx); + + return 0; +} + +/* Handle terminal response */ +static void term_response_cb(struct ss_context *ctx, uint8_t *resp_data, + uint8_t resp_data_len) +{ + int rc; + rc = ss_proactive_get_rc(resp_data, resp_data_len, SSMS); + + if (ctx->proactive.sms_tx_state.sms_result_cb) { + /* Note: When sending was successful we only report the success + * back to the caller when the last message is done. Contrary + * to that, errors are reported back immediately. */ + if (rc == 0 && ctx->proactive.sms_tx_state.last_msg) + ctx->proactive.sms_tx_state.sms_result_cb(ctx, 0); + else if (rc != 0) + ctx->proactive.sms_tx_state.sms_result_cb(ctx, -EINVAL); + } + + ctx->proactive.sms_tx_state.sms_result_cb = NULL; + ctx->proactive.sms_tx_state.pending = false; + ctx->proactive.sms_tx_state.last_msg = false; + + /* Give any additional queued SMSs a chance to go out */ + ss_uicc_sms_tx_poll(ctx); +} + +/* Pick pending SM from queue */ +static struct ss_uicc_sms_tx_sm *pick_sm(struct ss_uicc_sms_tx_state *state) +{ + struct ss_uicc_sms_tx_sm *sm; + + if (!ss_list_initialized(&state->sm)) + return NULL; + if (ss_list_empty(&state->sm)) + return NULL; + + sm = SS_LIST_GET(state->sm.next, struct ss_uicc_sms_tx_sm, list); + SS_LOGP(SSMS, LINFO, "dequeueing SMS-TPDU: %s\n", + ss_hexdump(sm->msg, sm->msg_len)); + ss_list_remove(&sm->list); + + return sm; +} + +void ss_uicc_sms_tx_poll(struct ss_context *ctx) +{ + struct ss_uicc_sms_tx_sm *sm; + uint8_t cmd_sms[] = { + 0x80 | TS_101_220_IEI_CMD_DETAILS, 0x03, 0x01, + TS_102_223_TOC_SEND_SHORT_MESSAGE, 0x00, + /* Device identities: From UICC to network is the only combination allowed + * for Send Short Message per TS 102 223 V4.4.0 Section 10 */ + 0x80 | TS_101_220_IEI_DEV_ID, 0x02, 0x81, 0x83, + 0x80 | TS_101_220_IEI_SMS_TPDU + }; + /* 2 for length; at most 2 bytes as SMS are limited in length */ + uint8_t cmd[sizeof(cmd_sms) + 2 + sizeof(sm->msg)]; + uint8_t *cmd_ptr = cmd; + int rc; + + if (ctx->proactive.sms_tx_state.pending) { + SS_LOGP(SSMS, LINFO, + "pending SM not yet sent, skipping...\n"); + return; + } + + if (!ss_proactive_rts(ctx)) { + SS_LOGP(SSMS, LINFO, + "another proactive command is still pending, skipping...\n"); + return; + } + + sm = pick_sm(&ctx->proactive.sms_tx_state); + if (sm) { + memcpy(cmd_ptr, cmd_sms, sizeof(cmd_sms)); + cmd_ptr += sizeof(cmd_sms); + assert(sm->msg_len <= 255); + if (sm->msg_len > 127) { + *cmd_ptr = 0x81; + cmd_ptr++; + *cmd_ptr = sm->msg_len; + cmd_ptr++; + } else { + *cmd_ptr = (uint8_t) sm->msg_len; + cmd_ptr++; + } + memcpy(cmd_ptr, sm->msg, sm->msg_len); + cmd_ptr += sm->msg_len; + + SS_LOGP(SSMS, LINFO, + "sending CAT command with SMS-TPDU: %s\n", + ss_hexdump(cmd, cmd_ptr - cmd)); + + if (check_cat_support(ctx)) { + rc = ss_proactive_put(ctx, term_response_cb, cmd, + cmd_ptr - cmd); + } else { + SS_LOGP(SSMS, LERROR, + "cannot send, TERMINAL PROFILE does not support sending of short messages!\n"); + rc = -EINVAL; + } + + if (rc < 0) { + SS_LOGP(SSMS, LERROR, + "error sending CAT command - SMS-TPDU tossed!\n"); + if (sm->sms_result_cb) + sm->sms_result_cb(ctx, -EINVAL); + ctx->proactive.sms_tx_state.sms_result_cb = NULL; + } else { + ctx->proactive.sms_tx_state.sms_result_cb = + sm->sms_result_cb; + ctx->proactive.sms_tx_state.pending = true; + ctx->proactive.sms_tx_state.last_msg = sm->last_msg; + } + SS_FREE(sm); + } +} diff --git a/src/softsim/uicc/uicc_sms_tx.h b/src/softsim/uicc/uicc_sms_tx.h new file mode 100644 index 0000000..264baef --- /dev/null +++ b/src/softsim/uicc/uicc_sms_tx.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include "sms.h" + +struct ss_context; +typedef void (*sms_result_cb)(struct ss_context * ctx, int rc); + +struct ss_uicc_sms_tx_sm { + struct ss_list list; + uint8_t msg[SMS_HDR_MAX_SIZE + SMS_MAX_SIZE]; + size_t msg_len; + sms_result_cb sms_result_cb; + bool last_msg; + uint8_t msg_id; +}; + +/* Note: It is expected that the contents of ss_uicc_sms_rx_state are set to + * zero before carrying out any operation, including ss_uicc_sms_tx_clear() */ +struct ss_uicc_sms_tx_state { + struct ss_list sm; + sms_result_cb sms_result_cb; + bool pending; + bool last_msg; + uint8_t msg_id; +}; + +void ss_uicc_sms_tx_clear(struct ss_context *ctx); +int ss_uicc_sms_tx(struct ss_context *ctx, + struct ss_sm_hdr *sm_hdr, + uint8_t *ud_hdr, size_t ud_hdr_len, + uint8_t *tp_ud, size_t tp_ud_len, + sms_result_cb sms_result_cb); +void ss_uicc_sms_tx_poll(struct ss_context *ctx); diff --git a/src/softsim/uicc/utils.c b/src/softsim/uicc/utils.c new file mode 100644 index 0000000..961136a --- /dev/null +++ b/src/softsim/uicc/utils.c @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include + +/*! Generate a hexdump string from the input data. + * \param[in] data pointer to binary data. + * \param[in] len length of binary data. + * \returns pointer to generated human readable string. */ +#define SS_HEXDUMP_MAX 4 +#define SS_HEXDUMP_BUFSIZE 1024 +char *ss_hexdump(const uint8_t *data, size_t len) +{ + static char out[SS_HEXDUMP_MAX][SS_HEXDUMP_BUFSIZE]; + static uint8_t idx = 0; + char *out_ptr; + size_t i; + + idx++; + idx = idx % SS_HEXDUMP_MAX; + out_ptr = out[idx]; + + if (!data) + return ("(null)"); + + for (i = 0; i < len; i++) { + sprintf(out_ptr, "%02x", data[i]); + out_ptr += 2; + + /* put three dots and exit early in case we are running out of + * space */ + if (i > SS_HEXDUMP_BUFSIZE / 2 - 4) { + sprintf(out_ptr, "..."); + return out[idx]; + } + } + + *out_ptr = '\0'; + return out[idx]; +} + +static bool is_hex(char hex_digit) +{ + switch (tolower(hex_digit)) { + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '0': + return true; + } + + return false; +} + +/*! Convert a human readable hex string to its binary representation. + * \param[in] binary pointer to binary data. + * \param[in] binary_len length of binary data. + * \param[in] hexstr string with human readable representation. + * \returns number resulting bytes. */ +size_t ss_binary_from_hexstr(uint8_t *binary, size_t binary_len, const char *hexstr) +{ + unsigned int i; + size_t hexstr_len; + char hex_digit[3]; + unsigned int hex_digit_bin; + size_t binary_count = 0; + int rc; + + hexstr_len = strlen(hexstr); + + memset(binary, 0, binary_len); + + for (i = 0; i < hexstr_len / 2; i++) { + hex_digit[0] = hexstr[0]; + hex_digit[1] = hexstr[1]; + hex_digit[2] = '\0'; + hexstr += 2; + + if (!is_hex(hex_digit[0]) || !is_hex(hex_digit[1])) + hex_digit_bin = 0xff; + else { + rc = sscanf(hex_digit, "%02x", &hex_digit_bin); + if (rc != 1) + hex_digit_bin = 0xff; + } + + binary[binary_count] = (uint8_t) hex_digit_bin & 0xff; + binary_count++; + + if (binary_count >= binary_len) + break; + } + + return binary_count; +} + +/*! Allocate a new ss_buf and fill it with data from given hexstring. + * \param[in] hexstr pointer to human readable hexstring. + * \returns pointer to newly allocated ss_buf object. */ +struct ss_buf *ss_buf_from_hexstr(const char *hexstr) +{ + int bin_len = strlen(hexstr)/2; + struct ss_buf *sb = ss_buf_alloc(bin_len); + + ss_binary_from_hexstr(sb->data, sb->len, hexstr); + + return sb; +} + +/*! Convert an array of up to 4 bytes to an uint32_t. + * \param[in] array user provided memory with bytes to convert. + * \param[in] len length of the array. + * \returns converted value as uint32_t. */ +uint32_t ss_uint32_from_array(const uint8_t *array, size_t len) +{ + uint32_t rc = 0; + size_t i; + uint32_t byte; + + if (len > 4) + len = 4; + + for(i = 0; i < len; i++) { + byte = array[len-i-1]; + rc |= (byte << i * 8); + } + + return rc; +} + +/*! Convert an array of up to 8 bytes to an uint64_t. + * \param[in] array user provided memory with bytes to convert. + * \param[in] len length of the array. + * \returns converted value as uint64_t. */ +uint64_t ss_uint64_from_array(const uint8_t *array, size_t len) +{ + uint64_t rc = 0; + size_t i; + uint64_t byte; + + if (len > 8) + len = 8; + + for(i = 0; i < len; i++) { + byte = array[len-i-1]; + rc |= (byte << i * 8); + } + + return rc; +} + +/*! Convert an uint32_t value into an array of up to 4 bytes. + * \param[in] array user provided memory to store the result. + * \param[in] len length of the array (1-4). + * \param[in] in uint32_t value to convert. */ +void ss_array_from_uint32(uint8_t *array, size_t len, uint32_t in) +{ + size_t i; + + if (len > 4) + len = 4; + + for (i = 0; i < len; i++) + array[len - i - 1] = (in >> i * 8) & 0xff; +} + +/*! Convert an uint64_t value into an array of up to 8 bytes. + * \param[in] array user provided memory to store the result. + * \param[in] len length of the array (1-8). + * \param[in] in uint32_t value to convert. */ +void ss_array_from_uint64(uint8_t *array, size_t len, uint64_t in) +{ + size_t i; + + if (len > 8) + len = 8; + + for (i = 0; i < len; i++) + array[len - i - 1] = (in >> i * 8) & 0xff; +} + +/*! Find the optimal array length to store an uint32_t. + * \returns number of bytes required. */ +size_t ss_optimal_len_for_uint32(uint32_t in) +{ + if (in > 0xffffff) + return 4; + if (in > 0xffff) + return 3; + if (in > 0xff) + return 2; + return 1; +} + +/* If we had htobe64 etc. around, we could copy data into a union type and let + * type punning and htobe64 do the work. As we don't have functionas around + * that give data in reversed endianness, we do everything in a fully in a + * platform independent way rather than by looking at the endianness, trusting + * that the compiler will recognize that a byte swap or plain copy will do as + * well. */ + +/*! Load an uint64_t value from a storage location (8 bytes, BE). + * \param[in] storage user provided memory where the the uint64_t shall be loaded from. + * \returns converted value as uint64_t. */ +uint64_t ss_uint64_load_from_be(const uint8_t *storage) +{ + return ((uint64_t)storage[0] << (7 * 8)) | + ((uint64_t)storage[1] << (6 * 8)) | + ((uint64_t)storage[2] << (5 * 8)) | + ((uint64_t)storage[3] << (4 * 8)) | + ((uint64_t)storage[4] << (3 * 8)) | + ((uint64_t)storage[5] << (2 * 8)) | + ((uint64_t)storage[6] << (1 * 8)) | + ((uint64_t)storage[7] << (0 * 8)); +} + +/*! Store an uint64_t value to a storage location (8 bytes, BE). + * \param[out] storage user provided memory where the the uint64_t shall be stored to. + * \param[in] number uint64_t value to store. */ +void ss_uint64_store_to_be(uint8_t *storage, uint64_t number) +{ + storage[0] = number >> (7 * 8); + storage[1] = number >> (6 * 8); + storage[2] = number >> (5 * 8); + storage[3] = number >> (4 * 8); + storage[4] = number >> (3 * 8); + storage[5] = number >> (2 * 8); + storage[6] = number >> (1 * 8); + storage[7] = number >> (0 * 8); +} + +/*! An alternative to strnlen(), which is not present in c99. + * \param[in] s string to evaluate. + * \param[in] maxlen maximum length of the buffer that contains the string. */ +size_t ss_strnlen(const char *s, size_t maxlen) +{ + size_t i; + bool digits_str_valid = false; + + if (maxlen <= 1) + return 0; + + /* Make sure that the string is valid */ + if (s[maxlen - 1] != '\0') { + for (i = 0; i < maxlen; i++) { + if (s[i] == '\0') + digits_str_valid = true; + } + } else + digits_str_valid = true; + + if (!digits_str_valid) + return 0; + + return strlen(s); +} + +/** Like memzero, but go through a volatile pointer to ensure + * that zeroing memory on the stack before a function return + * does not get optimized away. + * + * This serves the same purpose as memset_s, explizit_bzero or + * SecureZeroMemory as available on different platforms */ +void ss_memzero(void *ptr, size_t len) +{ + volatile char *clear_this = (volatile char *)ptr; + for (int i = 0; i < len; i++) + clear_this[i] = 0; +} diff --git a/src/softsim/uicc/utils.h b/src/softsim/uicc/utils.h new file mode 100644 index 0000000..58bb26f --- /dev/null +++ b/src/softsim/uicc/utils.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include + +void ss_memzero(void *ptr, size_t len); diff --git a/src/softsim/uicc/utils_3des.c b/src/softsim/uicc/utils_3des.c new file mode 100644 index 0000000..9b3aa1d --- /dev/null +++ b/src/softsim/uicc/utils_3des.c @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include "utils.h" +#include "utils_3des.h" +#include "crypto/des_i.h" + +void setup_key(const uint8_t *src, struct des3_key_s *dest) +{ + /* The library wants keys in 24byte form, but we have the 16byte form */ + /* If the compiler is smart, that doesn't really happen (but things re + * inlined down to the deskey() calls instead) */ + uint8_t key_copied[24]; + memcpy(&key_copied[0], src, TRIPLE_DES_KEYLEN); + memcpy(&key_copied[16], src, DES_BLOCKSIZE); + des3_key_setup(key_copied, dest); + + ss_memzero(key_copied, sizeof(key_copied)); +} + +/*! Perform an in-place 3DES decryption with the common settings of OTA + * (CBC mode, 16-byte key, zero IV). + * \param[inout] buffer user provided memory with plaintext to decrypt. + * \param[in] buffer_len length of the plaintext data to decrypt (multiple of 8). + * \param[in] key 16 byte DES key. */ +void ss_utils_3des_decrypt(uint8_t *buffer, size_t buffer_len, + const uint8_t *key) +{ + struct des3_key_s configured_key; + setup_key(key, &configured_key); + uint8_t cbc[DES_BLOCKSIZE] = { 0 }; /* IV, all zero */ + + /* Adjusted from hostap's crypto_internal-cipher.c */ + for (int i = 0; i < buffer_len / DES_BLOCKSIZE; i++) { + /* Some optimization would be possibly by double-buffering CBC, + * but that'd reduce readability. */ + uint8_t next_cbc[DES_BLOCKSIZE]; + memcpy(next_cbc, buffer, DES_BLOCKSIZE); + des3_decrypt(buffer, &configured_key, buffer); + for (int j = 0; j < DES_BLOCKSIZE; j++) + buffer[j] ^= cbc[j]; + memcpy(cbc, next_cbc, DES_BLOCKSIZE); + buffer += DES_BLOCKSIZE; + } + + ss_memzero(&configured_key, sizeof(configured_key)); +} + +/*! Perform an in-place 3DES encryption with the common settings of OTA + * (CBC mode, 16-byte key, zero IV). + * \param[inout] buffer user provided memory with plaintext to encrypt. + * \param[in] buffer_len length of the plaintext data to encrypt (multiple of 8). + * \param[in] key 16 byte DES key. */ +void ss_utils_3des_encrypt(uint8_t *buffer, size_t buffer_len, + const uint8_t *key) +{ + struct des3_key_s configured_key; + setup_key(key, &configured_key); + + uint8_t cbc[DES_BLOCKSIZE] = { 0 }; /* The IV */ + + /* Adjusted from hostap's crypto_internal-cipher.c */ + for (int i = 0; i < buffer_len / DES_BLOCKSIZE; i++) { + for (int j = 0; j < DES_BLOCKSIZE; j++) + cbc[j] ^= buffer[j]; + des3_encrypt(cbc, &configured_key, cbc); + memcpy(buffer, cbc, DES_BLOCKSIZE); + buffer += DES_BLOCKSIZE; + } + + ss_memzero(&configured_key, sizeof(configured_key)); +} + +/*! Setup a context for cryptographic checksum calculation. + * \param[out] cc user provided memory with checksum context. + * \param[in] key 16 byte DES key. */ +void ss_utils_3des_cc_setup(struct utils_3des_cc_ctx *cc, const uint8_t *key) +{ + memset(cc, 0, sizeof(*cc)); + cc->key = SS_ALLOC(struct des3_key_s); + setup_key(key, cc->key); + memset(cc->cbc, 0, SS_ARRAY_SIZE(cc->cbc)); +} + +/*! Feed data slice into cryptographic checksum calculation. + * \param[inout] cc user provided memory with checksum context. + * \param[in] data_len length of data slice (must be multiple of 8). + * \param[in] data user provided memory with data slice. */ +void ss_utils_3des_cc_feed(struct utils_3des_cc_ctx *cc, const uint8_t *data, + size_t data_len) +{ + /*! Calculation of the cryptographic checksum (CC) using 3DES. + * + * The cryptographic checksum calculated here is the last 8 bytes of + * the encryption output (ciphertext) of the data fed in; unlike the + * utils_3des_encrypt function this works on immutable input and allows + * input to be fed in in slices (to avoid an extra copying step in case + * the data is not in contiguous memory). + * + * The data slices fed must have a length that is a multiple of 8. The + * last slice fed may be of arbitrary length, and is automatically + * padded with zeros. (The input length are not checked, feeding data + * unaligned will produce an incorrect hash). + * + * The calculation result is avalable to the caller in cc->cbc. + * + * Note: Earlier versions of this implementation kept an internal buffer + * to allow byte-wise feeding; look at the history if this does turn + * out to be needed again. However, It turned out that data to be + * checksummed is always available in suitable chunks. */ + + int j; + + while (data_len != 0) { + /* Single chunk of the "encrypt" step, see utils_3des_encrypt */ + for (j = 0; j < DES_BLOCKSIZE; j++) + cc->cbc[j] ^= j < data_len ? data[j] : 0; + des3_encrypt(cc->cbc, cc->key, cc->cbc); + + if (data_len <= DES_BLOCKSIZE) + return; + + data += DES_BLOCKSIZE; + data_len -= DES_BLOCKSIZE; + } +} + +/*! Cleanup cryptographic checksum calculation context. + * \param[in] cc user provided memory with checksum context. */ +void ss_utils_3des_cc_cleanup(struct utils_3des_cc_ctx *cc) +{ + /*! This function removes the key from the context data in a secure. + * way. The cryptographic checksum result will be retained. */ + ss_memzero(cc->key, sizeof(cc->key)); + SS_FREE(cc->key); +} diff --git a/src/softsim/uicc/utils_3des.h b/src/softsim/uicc/utils_3des.h new file mode 100644 index 0000000..586e3dd --- /dev/null +++ b/src/softsim/uicc/utils_3des.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#define DES_BLOCKSIZE 8 +#define TRIPLE_DES_KEYLEN 16 + +void ss_utils_3des_decrypt(uint8_t *buffer, size_t buffer_len, + const uint8_t *key); +void ss_utils_3des_encrypt(uint8_t *buffer, size_t buffer_len, + const uint8_t *key); + +struct des3_key_s; +struct utils_3des_cc_ctx { + struct des3_key_s *key; + /* This contains the last block's ciphertext, which also represents the + * checksum (and the overall state of the algorithm). */ + uint8_t cbc[DES_BLOCKSIZE]; +}; + +void ss_utils_3des_cc_setup(struct utils_3des_cc_ctx *cc, const uint8_t *key); +void ss_utils_3des_cc_feed(struct utils_3des_cc_ctx *cc, const uint8_t *data, + size_t data_len); +void ss_utils_3des_cc_cleanup(struct utils_3des_cc_ctx *cc); diff --git a/src/softsim/uicc/utils_aes.c b/src/softsim/uicc/utils_aes.c new file mode 100644 index 0000000..c15cb37 --- /dev/null +++ b/src/softsim/uicc/utils_aes.c @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include "utils.h" +#include "utils_aes.h" +#include "crypto/common.h" + +#include "crypto/aes.h" + +/*! Perform an in-place AES decryption with the common settings of OTA + * (CBC mode, zero IV). + * \param[inout] buffer user provided memory with plaintext to decrypt. + * \param[in] buffer_len length of the plaintext data to decrypt (multiple of 16). + * \param[in] key AES key. + * \param[in] key_len length of the AES key. */ +void ss_utils_aes_decrypt(uint8_t *buffer, size_t buffer_len, + const uint8_t *key, size_t key_len) +{ + void *aes_ctx; + uint8_t cbc[AES_BLOCKSIZE] = { 0 }; /* IV, all zero */ + uint8_t next_cbc[AES_BLOCKSIZE]; + int i; + int j; + + aes_ctx = aes_decrypt_init(key, key_len); + + /* Adjusted from hostap's crypto_internal-cipher.c */ + for (i = 0; i < buffer_len / AES_BLOCKSIZE; i++) { + /* Some optimization would be possibly by double-buffering CBC, but that'd reduce readability. */ + memcpy(next_cbc, buffer, AES_BLOCKSIZE); + aes_decrypt(aes_ctx, buffer, buffer); + for (j = 0; j < AES_BLOCKSIZE; j++) + buffer[j] ^= cbc[j]; + memcpy(cbc, next_cbc, AES_BLOCKSIZE); + buffer += AES_BLOCKSIZE; + } + + aes_decrypt_deinit(aes_ctx); +} + +/*! Perform an in-place AES encryption with the common settings of OTA + * (CBC mode, zero IV). + * \param[inout] buffer user provided memory with plaintext to encrypt. + * \param[in] buffer_len length of the plaintext data to encrypt (multiple of 16). + * \param[in] key 16 byte AES key. + * \param[in] key_len length of the AES key. */ +void ss_utils_aes_encrypt(uint8_t *buffer, size_t buffer_len, + const uint8_t *key, size_t key_len) +{ + void *aes_ctx; + uint8_t cbc[AES_BLOCKSIZE] = { 0 }; /* IV, all zero */ + int i; + int j; + + aes_ctx = aes_encrypt_init(key, key_len); + + /* Adjusted from hostap's crypto_internal-cipher.c */ + for (i = 0; i < buffer_len / AES_BLOCKSIZE; i++) { + for (j = 0; j < AES_BLOCKSIZE; j++) + cbc[j] ^= buffer[j]; + aes_encrypt(aes_ctx, cbc, cbc); + memcpy(buffer, cbc, AES_BLOCKSIZE); + buffer += AES_BLOCKSIZE; + } + + aes_encrypt_deinit(aes_ctx); +} + +/* Shift a whole block to the left by one bit position. + * (See also RFC 4493, appendix A) */ +static void leftshift_onebit(uint8_t *input, uint8_t *output) +{ + unsigned int i; + uint8_t carry = 0; + + for (i = AES_BLOCKSIZE; i > 0; i--) { + output[i-1] = input[i-1] << 1; + output[i-1] |= carry; + carry = (input[i-1] & 0x80) ? 1 : 0; + } + return; +} + +/* XOR two blocks bitwise (See also RFC 4493, appendix A) */ +static void xor_128(uint8_t *a, uint8_t *b, uint8_t *out) +{ + unsigned int i; + for (i = 0; i < AES_BLOCKSIZE; i++) + out[i] = a[i] ^ b[i]; +} + +/*! Setup a context for cryptographic checksum calculation. + * \param[out] cc user provided memory with checksum context. + * \param[in] key AES key. + * \param[in] key_len length of the AES key. + * \param[in] inert_padding apply padding without chaning crypto process. */ +void ss_utils_aes_cc_setup(struct utils_aes_cc_ctx *cc, const uint8_t *key, + size_t key_len, bool inert_padding) +{ + /*! Note: The AES-CMAC algorithm specifies a separate path in case the + * input data has to be padded. The option "inert_padding" will handle + * the data as if its length would be a multiple of 16 byte. A zero + * padding will be applied automatically but it will have no effect on + * the cryptographic process itsself. */ + + const uint8_t const_zero[AES_BLOCKSIZE] = { 0 }; + uint8_t L[AES_BLOCKSIZE] = { 0 }; + uint8_t tmp[AES_BLOCKSIZE]; + + uint8_t const_Rb[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87 + }; + + memset(cc, 0, sizeof(*cc)); + cc->aes_ctx = aes_encrypt_init(key, key_len); + cc->inert_padding = inert_padding; + + /* Generate subkeys K1 and K2 (See also RFC 4493, appendix A) */ + aes_encrypt(cc->aes_ctx, const_zero, L); + if ((L[0] & 0x80) == 0) { + leftshift_onebit(L, cc->K1); + } else { + leftshift_onebit(L, tmp); + xor_128(tmp, const_Rb, cc->K1); + } + if ((cc->K1[0] & 0x80) == 0) { + leftshift_onebit(cc->K1, cc->K2); + } else { + leftshift_onebit(cc->K1, tmp); + xor_128(tmp, const_Rb, cc->K2); + } +} + +/*! Feed data slice into cryptographic checksum calculation. + * \param[inout] cc user provided memory with checksum context. + * \param[in] data_len length of data slice (must be multiple of 16). + * \param[in] data user provided memory with data slice. + * \param[in] last set to true when feeding the last block. */ +void ss_utils_aes_cc_feed(struct utils_aes_cc_ctx *cc, const uint8_t *data, + size_t data_len, bool last) +{ + /*! Calculation of the cryptographic checksum (CC) using AES. + * (See also see also RFC 4493, section 2.2 and utils_3des.c */ + + int j; + uint8_t padd; + bool padded = false; + + if (cc->inert_padding) + /* When we do an "inert padding" we will pad with zeros only. */ + padd = 0x00; + else + /* Set the first padding bit to 1 and padd the remaining data + * with zeros, see also NIST Special Publication 800-38A, + * appendix A and RFC 4493, section 2.4. */ + padd = 0x80; + + do { + /* XOR the input data with the encrypted data of the last turn, + * apply padding if necessary. */ + for (j = 0; j < AES_BLOCKSIZE; j++) { + if (j < data_len) + cc->cbc[j] ^= data[j]; + else { + cc->cbc[j] ^= padd; + padd = 0x00; + padded = true; + } + } + + /* The last block needs special treatment using a subkey + * K1 or K2, depending on if padding was applied or not. */ + if (last && data_len <= AES_BLOCKSIZE) { + /* In case of "inert padding" we will apply K1 as if + * we didn't pad anything. */ + if (padded && cc->inert_padding == false) + xor_128(cc->cbc, cc->K2, cc->cbc); + else + xor_128(cc->cbc, cc->K1, cc->cbc); + } + + aes_encrypt(cc->aes_ctx, cc->cbc, cc->cbc); + + if (data_len <= AES_BLOCKSIZE) + return; + + data += AES_BLOCKSIZE; + data_len -= AES_BLOCKSIZE; + } while (data_len != 0); +} + +/*! Cleanup cryptographic checksum calculation context. + * \param[in] cc user provided memory with checksum context. */ +void ss_utils_aes_cc_cleanup(struct utils_aes_cc_ctx *cc) +{ + aes_encrypt_deinit(cc->aes_ctx); + ss_memzero(&cc->K1, sizeof(cc->K1)); + ss_memzero(&cc->K2, sizeof(cc->K2)); +} diff --git a/src/softsim/uicc/utils_aes.h b/src/softsim/uicc/utils_aes.h new file mode 100644 index 0000000..3a3dab7 --- /dev/null +++ b/src/softsim/uicc/utils_aes.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#define AES_BLOCKSIZE 16 + +void ss_utils_aes_decrypt(uint8_t *buffer, size_t buffer_len, + const uint8_t *key, size_t key_len); +void ss_utils_aes_encrypt(uint8_t *buffer, size_t buffer_len, + const uint8_t *key, size_t key_len); + +struct utils_aes_cc_ctx { + void *aes_ctx; + + uint8_t K1[AES_BLOCKSIZE]; + uint8_t K2[AES_BLOCKSIZE]; + + /* This contains the last block's ciphertext, where the last 8 byte will + * represent the actual checksum. (The last block also represents the + * overall state of the algorithm). */ + uint8_t cbc[AES_BLOCKSIZE]; + + /* When set to true, the input data will be padded as if the length + * would already be a multiple of 16 bytes. Eventually the padding will + * not alter the cryptograhic process like it normally would. */ + bool inert_padding; +}; + +void ss_utils_aes_cc_setup(struct utils_aes_cc_ctx *cc, const uint8_t *key, + size_t key_len, bool inert_padding); +void ss_utils_aes_cc_feed(struct utils_aes_cc_ctx *cc, const uint8_t *data, + size_t data_len, bool last); +void ss_utils_aes_cc_cleanup(struct utils_aes_cc_ctx *cc); diff --git a/src/softsim/uicc/utils_ota.c b/src/softsim/uicc/utils_ota.c new file mode 100644 index 0000000..9380632 --- /dev/null +++ b/src/softsim/uicc/utils_ota.c @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include "utils.h" +#include "utils_3des.h" +#include "utils_aes.h" +#include "utils_ota.h" +#include + +/*! Calculate the number of padding bytes (pcnt) for a given length. + * \param[in] algorithm algorithm to use. + * \param[in] data_len length of the data. + * \returns number of padding bytes. */ +uint8_t ss_utils_ota_calc_pcnt(enum enc_algorithm algorithm, size_t data_len) +{ + uint8_t blocksize = 0; + + switch (algorithm) { + case TRIPLE_DES_CBC2: + blocksize = DES_BLOCKSIZE; + break; + case AES_CBC: + blocksize = AES_BLOCKSIZE; + break; + default: + blocksize = 1; /* No padding */ + } + + return (-(blocksize + data_len)) % blocksize; +} + +/*! Calculate cryptographic checksum (CC) using a specified algorithm. + * \param[out] cc user provided memory for resulting cryptpgraphic checksum. + * \param[out] cc_len length of user provided memory for resulting cryptpgraphic checksum. + * \param[in] key cryptpgraphic key. + * \param[in] key_len cryptpgraphic key length. + * \param[in] data1 user buffer containing part 1 of the data. + * \param[in] data1_len length of data part 1 (must be multiple of blocksize) + * \param[in] data2 user buffer containing part 2 of the data. + * \param[in] data2_len length of data part 2 (unpadded). + * \returns 0 on success, -EINVAL on error. */ +int ss_utils_ota_calc_cc(uint8_t *cc, size_t cc_len, + uint8_t *key, size_t key_len, enum enc_algorithm alg, + uint8_t *data1, size_t data1_len, + uint8_t *data2, size_t data2_len) +{ + struct utils_3des_cc_ctx cc_des; + struct utils_aes_cc_ctx cc_aes; + + /* NOTE: This function accepts two separate buffers (data1 and data2). + * The reason for this is that the data we are going to sign is in two + * separate buffers and to avoid copying the two buffers into a single + * buffer we apply the checksum calculation on the two buffers + * separately. This works as long as the first buffer does not require + * to be padded (AES-CMAC and 3DES-CBC2 are block ciphers). The caller + * must ensure that the length of the buffer (data1) is a multiple of 8 + * for 3DES-CBC2 and a multiple of 16 for AES-CMAC. Usually we have + * multiple of 16 in both situations. The length of the second buffer + * (data2) may be of arbitrary length. Both implementation (3DES-CBC2 + * and AES-CMAC) will apply an appropriate padding internally. */ + + switch (alg) { + case TRIPLE_DES_CBC2: + assert(data1_len % DES_BLOCKSIZE == 0); + assert(key_len == TRIPLE_DES_KEYLEN); + assert(cc_len <= sizeof(cc_des.cbc)); + ss_utils_3des_cc_setup(&cc_des, key); + ss_utils_3des_cc_feed(&cc_des, data1, data1_len); + ss_utils_3des_cc_feed(&cc_des, data2, data2_len); + ss_utils_3des_cc_cleanup(&cc_des); + memcpy(cc, cc_des.cbc, cc_len); + return 0; + case AES_CMAC: + assert(data1_len % AES_BLOCKSIZE == 0); + assert(cc_len <= sizeof(cc_aes.cbc)); + ss_utils_aes_cc_setup(&cc_aes, key, key_len, false); + ss_utils_aes_cc_feed(&cc_aes, data1, data1_len, false); + ss_utils_aes_cc_feed(&cc_aes, data2, data2_len, true); + ss_utils_aes_cc_cleanup(&cc_aes); + memcpy(cc, cc_aes.cbc, cc_len); + return 0; + default: + SS_LOGP(SREMOTECMD, LERROR, + "unable to calculate cc, improper crypto algorithm selected\n"); + return -EINVAL; + } +} diff --git a/src/softsim/uicc/utils_ota.h b/src/softsim/uicc/utils_ota.h new file mode 100644 index 0000000..e1615bc --- /dev/null +++ b/src/softsim/uicc/utils_ota.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +/* See also ETSI TS 102 225, section 5.1.1 and 5.1.2 */ +enum enc_algorithm { + NONE, + TRIPLE_DES_CBC2, + AES_CBC, + AES_CMAC, +}; + +#define OTA_KEY_LEN 16 +#define OTA_INTEGRITY_LEN 8 + +uint8_t ss_utils_ota_calc_pcnt(enum enc_algorithm algorithm, size_t data_len); +int ss_utils_ota_calc_cc(uint8_t *cc, size_t cc_len, + uint8_t *key, size_t key_len, enum enc_algorithm alg, + uint8_t *data1, size_t data1_len, + uint8_t *data2, size_t data2_len); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..53f0650 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +# Enable testing for current directory and below. +enable_testing() + +add_subdirectory(aes) +add_subdirectory(btlv) +add_subdirectory(ctlv) +add_subdirectory(des) +add_subdirectory(fcp) +add_subdirectory(list) +add_subdirectory(ota) +add_subdirectory(sms) +add_subdirectory(tlv8) +add_subdirectory(utils) diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..0057f66 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,63 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +SUBDIRS = \ + list \ + btlv \ + ctlv \ + tlv8 \ + utils \ + fcp \ + sms \ + aes \ + des \ + ota \ + $(NULL) + +# The `:;' works around a Bash 3.2 bug when the output is not writeable. +$(srcdir)/package.m4: $(top_srcdir)/configure.ac + :;{ \ + echo '# Signature of the current package.' && \ + echo 'm4_define([AT_PACKAGE_NAME],' && \ + echo ' [$(PACKAGE_NAME)])' && \ + echo 'm4_define([AT_PACKAGE_TARNAME],' && \ + echo ' [$(PACKAGE_TARNAME)])' && \ + echo 'm4_define([AT_PACKAGE_VERSION],' && \ + echo ' [$(PACKAGE_VERSION)])' && \ + echo 'm4_define([AT_PACKAGE_STRING],' && \ + echo ' [$(PACKAGE_STRING)])' && \ + echo 'm4_define([AT_PACKAGE_BUGREPORT],' && \ + echo ' [$(PACKAGE_BUGREPORT)])'; \ + echo 'm4_define([AT_PACKAGE_URL],' && \ + echo ' [$(PACKAGE_URL)])'; \ + } >'$(srcdir)/package.m4' + +EXTRA_DIST = \ + testsuite.at \ + $(srcdir)/package.m4 \ + $(TESTSUITE) \ + $(NULL) + +TESTSUITE = $(srcdir)/testsuite + +DISTCLEANFILES = \ + atconfig \ + $(NULL) + +check-local: atconfig $(TESTSUITE) + $(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS) + $(MAKE) $(AM_MAKEFLAGS) + +installcheck-local: atconfig $(TESTSUITE) + $(SHELL) '$(TESTSUITE)' AUTOTEST_PATH='$(bindir)' \ + $(TESTSUITEFLAGS) + +clean-local: + test ! -f '$(TESTSUITE)' || \ + $(SHELL) '$(TESTSUITE)' --clean + +AUTOM4TE = $(SHELL) $(top_srcdir)/missing --run autom4te +AUTOTEST = $(AUTOM4TE) --language=autotest +$(TESTSUITE): $(srcdir)/testsuite.at $(srcdir)/package.m4 + $(AUTOTEST) -I '$(srcdir)' -o $@.tmp $@.at + mv $@.tmp $@ diff --git a/tests/aes/CMakeLists.txt b/tests/aes/CMakeLists.txt new file mode 100644 index 0000000..5a6bad1 --- /dev/null +++ b/tests/aes/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +add_executable(aes_test aes_test.c) + +include_directories(${PROJECT_SOURCE_DIR}) +include_directories(${PROJECT_SOURCE_DIR}/include) + +target_link_libraries(aes_test uicc crypto) + +add_test(NAME aes_test COMMAND sh -c "$ > aes_test.out") + +add_test(NAME aes_test_compare + COMMAND ${CMAKE_COMMAND} -E compare_files --ignore-eol + ${PROJECT_SOURCE_DIR}/build/tests/aes/aes_test.out + ${CMAKE_CURRENT_SOURCE_DIR}/aes_test.ok +) diff --git a/tests/aes/Makefile.am b/tests/aes/Makefile.am new file mode 100644 index 0000000..99710b5 --- /dev/null +++ b/tests/aes/Makefile.am @@ -0,0 +1,29 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(NULL) + +EXTRA_DIST = \ + aes_test.ok \ + $(NULL) + +noinst_PROGRAMS = \ + aes_test \ + $(NULL) + +aes_test_SOURCES = \ + aes_test.c \ + $(NULL) + +aes_test_LDADD = \ + $(top_srcdir)/src/softsim/uicc/libuicc.a \ + $(top_srcdir)/src/softsim/crypto/libcrypto.a \ + $(NULL) diff --git a/tests/aes/aes_test.c b/tests/aes/aes_test.c new file mode 100644 index 0000000..7600e40 --- /dev/null +++ b/tests/aes/aes_test.c @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include "src/softsim/uicc/utils_aes.h" + +/* See also RFC 4493, section 4, Example 1 */ +void aes_cmac_test_0(void) +{ + struct utils_aes_cc_ctx cc_aes; + + uint8_t key[] = { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c, + }; + + ss_utils_aes_cc_setup(&cc_aes, key, sizeof(key), false); + printf("AES-CMAC feed m: (nothing, NULL)\n"); + ss_utils_aes_cc_feed(&cc_aes, NULL, 0, true); + ss_utils_aes_cc_cleanup(&cc_aes); + printf("AES-CMAC checksum: %s\n", + ss_hexdump(cc_aes.cbc, sizeof(cc_aes.cbc))); + +} + +/* See also RFC 4493, section 4, Example 2 */ +void aes_cmac_test_16(void) +{ + struct utils_aes_cc_ctx cc_aes; + + uint8_t key[] = { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c, + }; + + uint8_t m[] = { + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + }; + + ss_utils_aes_cc_setup(&cc_aes, key, sizeof(key), false); + printf("AES-CMAC feed m: %s\n", ss_hexdump(m, sizeof(m))); + ss_utils_aes_cc_feed(&cc_aes, m, sizeof(m), true); + ss_utils_aes_cc_cleanup(&cc_aes); + printf("AES-CMAC checksum: %s\n", + ss_hexdump(cc_aes.cbc, sizeof(cc_aes.cbc))); + +} + +/* See also RFC 4493, section 4, Example 3 */ +void aes_cmac_test_40(void) +{ + struct utils_aes_cc_ctx cc_aes; + + uint8_t key[] = { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c, + }; + + uint8_t m[] = { + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, + 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, + 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, + }; + + ss_utils_aes_cc_setup(&cc_aes, key, sizeof(key), false); + printf("AES-CMAC feed m: %s\n", ss_hexdump(m, sizeof(m))); + ss_utils_aes_cc_feed(&cc_aes, m, sizeof(m), true); + ss_utils_aes_cc_cleanup(&cc_aes); + printf("AES-CMAC checksum: %s\n", + ss_hexdump(cc_aes.cbc, sizeof(cc_aes.cbc))); + +} + +/* See also RFC 4493, section 4, Example 4 */ +void aes_cmac_test_64(void) +{ + struct utils_aes_cc_ctx cc_aes; + + uint8_t key[] = { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c, + }; + + uint8_t m[] = { + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, + 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, + 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, + 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, + 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, + 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10, + }; + + ss_utils_aes_cc_setup(&cc_aes, key, sizeof(key), false); + printf("AES-CMAC feed m: %s\n", ss_hexdump(m, sizeof(m))); + ss_utils_aes_cc_feed(&cc_aes, m, sizeof(m), true); + ss_utils_aes_cc_cleanup(&cc_aes); + printf("AES-CMAC checksum: %s\n", + ss_hexdump(cc_aes.cbc, sizeof(cc_aes.cbc))); +} + +void aes_cmac_test_one_block(void) +{ + struct utils_aes_cc_ctx cc_aes; + + uint8_t block[] = { + 0x00, 0x28, 0x15, 0x1e, 0x19, 0x32, 0x32, 0xb0, + 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, + 0x00, 0xa4, 0x00, 0x04, 0x02, 0x3f, 0x00, 0x00, + 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 + }; + + uint8_t key[] = { + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + 0x01, 0x23, 0x45, 0x67, 0x01, 0x23, 0x45, 0x67, + }; + + ss_utils_aes_cc_setup(&cc_aes, key, sizeof(key), true); + printf("AES-CMAC feed block: %s\n", ss_hexdump(block, sizeof(block))); + ss_utils_aes_cc_feed(&cc_aes, block, sizeof(block), true); + ss_utils_aes_cc_cleanup(&cc_aes); + printf("AES-CMAC checksum: %s\n", + ss_hexdump(cc_aes.cbc, sizeof(cc_aes.cbc))); +} + +void aes_cmac_test_one_block_padded(void) +{ + struct utils_aes_cc_ctx cc_aes; + + /* This is the same data as in aes_cmac_test_one_block(), but it is + * already padded with zeros already before it is fed into the checksum + * algorithm */ + uint8_t block[] = { + 0x00, 0x28, 0x15, 0x1e, 0x19, 0x32, 0x32, 0xb0, + 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, + 0x00, 0xa4, 0x00, 0x04, 0x02, 0x3f, 0x00, 0x00, + 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + uint8_t key[] = { + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + 0x01, 0x23, 0x45, 0x67, 0x01, 0x23, 0x45, 0x67, + }; + + ss_utils_aes_cc_setup(&cc_aes, key, sizeof(key), true); + printf("AES-CMAC feed block: %s\n", ss_hexdump(block, sizeof(block))); + ss_utils_aes_cc_feed(&cc_aes, block, sizeof(block), true); + ss_utils_aes_cc_cleanup(&cc_aes); + printf("AES-CMAC checksum: %s\n", + ss_hexdump(cc_aes.cbc, sizeof(cc_aes.cbc))); +} + +void aes_cmac_test_two_blocks(void) +{ + struct utils_aes_cc_ctx cc_aes; + + uint8_t block_1[] = { + 0x00, 0x28, 0x15, 0x1e, 0x19, 0x32, 0x32, 0xb0, + 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, + }; + + uint8_t block_2[] = { + 0x00, 0xa4, 0x00, 0x04, 0x02, 0x3f, 0x00, 0x00, + 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 + }; + + uint8_t key[] = { + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + 0x01, 0x23, 0x45, 0x67, 0x01, 0x23, 0x45, 0x67, + }; + + ss_utils_aes_cc_setup(&cc_aes, key, sizeof(key), true); + printf("AES-CMAC feed block 1: %s\n", + ss_hexdump(block_1, sizeof(block_1))); + ss_utils_aes_cc_feed(&cc_aes, block_1, sizeof(block_1), false); + printf("AES-CMAC feed block 2: %s\n", + ss_hexdump(block_2, sizeof(block_2))); + ss_utils_aes_cc_feed(&cc_aes, block_2, sizeof(block_2), true); + ss_utils_aes_cc_cleanup(&cc_aes); + printf("AES-CMAC checksum: %s\n", + ss_hexdump(cc_aes.cbc, sizeof(cc_aes.cbc))); +} + +int main(int argc, char **argv) +{ + aes_cmac_test_0(); + aes_cmac_test_16(); + aes_cmac_test_40(); + aes_cmac_test_64(); + aes_cmac_test_one_block(); + aes_cmac_test_one_block_padded(); + aes_cmac_test_two_blocks(); + return 0; +} diff --git a/tests/aes/aes_test.ok b/tests/aes/aes_test.ok new file mode 100644 index 0000000..848babd --- /dev/null +++ b/tests/aes/aes_test.ok @@ -0,0 +1,15 @@ +AES-CMAC feed m: (nothing, NULL) +AES-CMAC checksum: bb1d6929e95937287fa37d129b756746 +AES-CMAC feed m: 6bc1bee22e409f96e93d7e117393172a +AES-CMAC checksum: 070a16b46b4d4144f79bdd9dd04a287c +AES-CMAC feed m: 6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411 +AES-CMAC checksum: dfa66747de9ae63030ca32611497c827 +AES-CMAC feed m: 6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710 +AES-CMAC checksum: 51f0bebf7e3b9d92fc49741779363cfe +AES-CMAC feed block: 0028151e193232b0001100000000010600a40004023f0000c0000000000000000000 +AES-CMAC checksum: ea531788b3de542beebe4eb34f5a1086 +AES-CMAC feed block: 0028151e193232b0001100000000010600a40004023f0000c00000000000000000000000000000000000000000000000 +AES-CMAC checksum: ea531788b3de542beebe4eb34f5a1086 +AES-CMAC feed block 1: 0028151e193232b00011000000000106 +AES-CMAC feed block 2: 00a40004023f0000c0000000000000000000 +AES-CMAC checksum: ea531788b3de542beebe4eb34f5a1086 diff --git a/tests/atlocal.in b/tests/atlocal.in new file mode 100644 index 0000000..e69de29 diff --git a/tests/btlv/CMakeLists.txt b/tests/btlv/CMakeLists.txt new file mode 100644 index 0000000..b16f089 --- /dev/null +++ b/tests/btlv/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +add_executable(btlv_test btlv_test.c) + +include_directories(${PROJECT_SOURCE_DIR}) +include_directories(${PROJECT_SOURCE_DIR}/include) + +target_link_libraries(btlv_test uicc) + +add_test(NAME btlv_test COMMAND sh -c "$ > btlv_test.out") diff --git a/tests/btlv/Makefile.am b/tests/btlv/Makefile.am new file mode 100644 index 0000000..54c4090 --- /dev/null +++ b/tests/btlv/Makefile.am @@ -0,0 +1,28 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(NULL) + +EXTRA_DIST = \ + btlv_test.ok \ + $(NULL) + +noinst_PROGRAMS = \ + btlv_test \ + $(NULL) + +btlv_test_SOURCES = \ + btlv_test.c \ + $(NULL) + +btlv_test_LDADD = \ + $(top_srcdir)/src/softsim/uicc/libuicc.a \ + $(NULL) diff --git a/tests/btlv/btlv_test.c b/tests/btlv/btlv_test.c new file mode 100644 index 0000000..3452679 --- /dev/null +++ b/tests/btlv/btlv_test.c @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include "src/softsim/uicc/btlv.h" +#include "src/softsim/uicc/fcp.h" + +const struct ber_tlv_desc bertlv_tree_descr[] = { + { + .id = 1, + .id_parent = 0, + .title = "envelope", + .tag_encoded = TS_102_221_IEI_FCP_TMPL, + }, + { + .id = 2, + .id_parent = 1, + .title = "one", + .tag_encoded = 0x82, + }, + { + .id = 3, + .id_parent = 1, + .title = "two", + .tag_encoded = 0x84, + }, + { + .id = 4, + .id_parent = 1, + .title = "nested-envelope", + .tag_encoded = 0xa5, + }, + { + .id = 5, + .id_parent = 4, + .title = "three", + .tag_encoded = 0x80, + }, + { + .id = 6, + .id_parent = 4, + .title = "four", + .tag_encoded = 0x83, + }, + { + .id = 7, + .id_parent = 1, + .title = "five", + .tag_encoded = 0x8a, + }, + { + .id = 8, + .id_parent = 1, + .title = "six", + .tag_encoded = 0x8c, + }, + { + .id = 9, + .id_parent = 1, + .title = "eight", + .tag_encoded = 0xc6, + }, + { + .id = 0, + }, +}; + +const struct ber_tlv_desc bertlv_tree_descr_misfit[] = { + { + .id = 1, + .id_parent = 0, + .title = "envelope", + .tag_encoded = TS_102_221_IEI_FCP_TMPL, + }, + { + .id = 2, + .id_parent = 1, + .title = "one", + .tag_encoded = 0x82, + }, + { + .id = 3, + .id_parent = 1, + .title = "two", + .tag_encoded = 0x84, + }, + { + .id = 4, + .id_parent = 1, + .title = "nested-envelope", + .tag_encoded = 0xa5, + }, + { + .id = 5, + .id_parent = 4, + .title = "three", + .tag_encoded = 0x80, + }, + { + .id = 6, + .id_parent = 1, + .title = "five", + .tag_encoded = 0x8a, + }, + { + .id = 7, + .id_parent = 1, + .title = "six", + .tag_encoded = 0x8c, + }, + { + .id = 8, + .id_parent = 1, + .title = "nested_envelope2", + .tag_encoded = 0xa5, + }, + { + .id = 9, + .id_parent = 8, + .title = "twentyone", + .tag_encoded = 0xaa, + }, + { + .id = 0, + }, +}; + +/* Decode a select response that has been sampled from a real usim-card */ +static void ss_btlv_decode_test_realistic(void) +{ + uint8_t encoded[] = { 0x62, 0x38, 0x82, 0x02, 0x78, 0x21, 0x84, 0x10, + 0xa0, 0x00, 0x00, 0x00, 0x87, 0x10, 0x02, 0xff, + 0xff, 0xff, 0xff, 0x89, 0x07, 0x09, 0x00, 0x00, + 0xa5, 0x09, 0x80, 0x01, 0x71, 0x83, 0x04, 0x00, + 0x01, 0x8d, 0x08, 0x8a, 0x01, 0x05, 0x8c, 0x01, + 0x00, 0xc6, 0x0f, 0x90, 0x01, 0x70, 0x83, 0x01, + 0x01, 0x83, 0x01, 0x81, 0x83, 0x01, 0x0a, 0x83, + 0x01, 0x0b + }; + struct ss_list *decoded; + + fprintf(stderr, "\nTEST: decode a BER-TLV encoded string\n"); + fprintf(stderr, "encoded input: %s\n", ss_hexdump(encoded, sizeof(encoded))); + + /* Without description */ + fprintf(stderr, "decoded without description:\n"); + decoded = ss_btlv_decode(encoded, sizeof(encoded), NULL); + ss_btlv_dump(decoded, 0, SBTLV, LDEBUG); + ss_btlv_free(decoded); + + /* With complete description */ + fprintf(stderr, "decoded with complete description:\n"); + decoded = ss_btlv_decode(encoded, sizeof(encoded), bertlv_tree_descr); + ss_btlv_dump(decoded, 0, SBTLV, LDEBUG); + ss_btlv_free(decoded); + + /* With not quite fitting description */ + fprintf(stderr, "decoded with non fitting description:\n"); + decoded = ss_btlv_decode(encoded, sizeof(encoded), bertlv_tree_descr_misfit); + ss_btlv_dump(decoded, 0, SBTLV, LDEBUG); + ss_btlv_free(decoded); +} + +/* Build up a BER-TLV linked list tree and encode it. The encoded result must + * match a sample that has been taken from a real usim-card. */ +static void ss_btlv_encode_test_realistic(void) +{ + struct ss_list *decoded; + struct ber_tlv_ie *ie_envelope; + struct ber_tlv_ie *ie_nested_envelope; + uint8_t encoded[1024]; + int rc; + + uint8_t encoded_expected[] = + { 0x62, 0x38, 0x82, 0x02, 0x78, 0x21, 0x84, 0x10, + 0xa0, 0x00, 0x00, 0x00, 0x87, 0x10, 0x02, 0xff, + 0xff, 0xff, 0xff, 0x89, 0x07, 0x09, 0x00, 0x00, + 0xa5, 0x09, 0x80, 0x01, 0x71, 0x83, 0x04, 0x00, + 0x01, 0x8d, 0x08, 0x8a, 0x01, 0x05, 0x8c, 0x01, + 0x00, 0xc6, 0x0f, 0x90, 0x01, 0x70, 0x83, 0x01, + 0x01, 0x83, 0x01, 0x81, 0x83, 0x01, 0x0a, 0x83, + 0x01, 0x0b + }; + + /* Set up BER-TLV tree */ + decoded = SS_ALLOC(struct ss_list); + ss_list_init(decoded); + ie_envelope = ss_btlv_new_ie_constr(decoded, "envelope", TS_102_221_IEI_FCP_TMPL); + ss_btlv_new_ie(ie_envelope->nested, "one", 0x82, 2, + (uint8_t *) "\x78\x21"); + ss_btlv_new_ie(ie_envelope->nested, "two", 0x84, 16, (uint8_t *) + "\xa0\x00\x00\x00\x87\x10\x02\xff\xff\xff\xff\x89\x07\x09\x00\x00"); + ie_nested_envelope = + ss_btlv_new_ie_constr(ie_envelope->nested, "nested-envelope", 0xa5); + ss_btlv_new_ie(ie_nested_envelope->nested, "three", 0x80, 1, + (uint8_t *) "\x71"); + ss_btlv_new_ie(ie_nested_envelope->nested, "four", 0x83, 4, + (uint8_t *) "\x00\x01\x8d\x08"); + ss_btlv_new_ie(ie_envelope->nested, "five", 0x8a, 1, + (uint8_t *) "\x05"); + ss_btlv_new_ie(ie_envelope->nested, "six", 0x8c, 1, + (uint8_t *) "\x00"); + ss_btlv_new_ie(ie_envelope->nested, "eight", 0xc6, 15, (uint8_t *) + "\x90\x01\x70\x83\x01\x01\x83\x01\x81\x83\x01\x0a\x83\x01\x0b"); + + fprintf(stderr, "\nTEST: encode a binary BER-TLV encoded string from decoded list\n"); + fprintf(stderr, "BER-TLV data to be encoded:\n"); + ss_btlv_dump(decoded, 2, SBTLV, LDEBUG); + rc = ss_btlv_encode(encoded, sizeof(encoded), decoded); + fprintf(stderr, "expected result: %s\n", ss_hexdump(encoded_expected, sizeof(encoded_expected))); + fprintf(stderr, "encoded result: %s\n", ss_hexdump(encoded, rc)); + ss_btlv_free(decoded); +} + +const struct ber_tlv_desc decode_encode_test_descr[] = { + { + .id = 1, + .id_parent = 0, + .title = "single-byte-tag", + .tag_encoded = 0x0a, + }, + { + .id = 2, + .id_parent = 0, + .title = "two-byte-tag", + .tag_encoded = 0xdf55, + }, + { + .id = 3, + .id_parent = 0, + .title = "three-byte-tag", + .tag_encoded = 0xdfaaaa, + }, + { + .id = 4, + .id_parent = 0, + .title = "one-byte-len", + .tag_encoded = 0x01, + }, + { + .id = 5, + .id_parent = 0, + .title = "two-byte-len", + .tag_encoded = 0x02, + }, + { + .id = 6, + .id_parent = 0, + .title = "three-byte-len", + .tag_encoded = 0x03, + }, + { + .id = 0, + } +}; + +/* BER-TLV offers flexible header and tag length. This tests different header + * and tag length formats against its own implementation. */ +static void ss_btlv_encode_decode_test(void) +{ + struct ss_list *decoded; + struct ss_list *decoded_from_encoded; + uint8_t encoded[80000]; + size_t bytes_encoded; + + uint8_t buf_one_byte_len[126]; + uint8_t buf_two_byte_len[255]; + uint8_t buf_three_byte_len[65535]; + + memset(buf_one_byte_len, 0xaa, sizeof(buf_one_byte_len)); + memset(buf_two_byte_len, 0xbb, sizeof(buf_two_byte_len)); + memset(buf_three_byte_len, 0xcc, sizeof(buf_three_byte_len)); + + /* Set up BER-TLV tree */ + decoded = SS_ALLOC(struct ss_list); + ss_list_init(decoded); + + /* Try all possible TAG lengths */ + ss_btlv_new_ie(decoded, "single-byte-tag", 0x0a, 1, + (uint8_t *) "\xff"); + ss_btlv_new_ie(decoded, "two-byte-tag", 0xdf55, 1, (uint8_t *) "\xff"); + ss_btlv_new_ie(decoded, "three-byte-tag", 0xdfaaaa, 1, + (uint8_t *) "\xff"); + + /* Try up to three byte length field length */ + ss_btlv_new_ie(decoded, "one-byte-len", 0x01, + sizeof(buf_one_byte_len), buf_one_byte_len); + ss_btlv_new_ie(decoded, "two-byte-len", 0x02, + sizeof(buf_two_byte_len), buf_two_byte_len); + ss_btlv_new_ie(decoded, "three-byte-len", 0x03, + sizeof(buf_three_byte_len), buf_three_byte_len); + + fprintf(stderr, "\nTEST: encode a binary BER-TLV encoded string with multi byte header fields\n"); + fprintf(stderr, "BER-TLV data to be encoded: (encoder test)\n"); + ss_btlv_dump(decoded, 2, SBTLV, LDEBUG); + bytes_encoded = ss_btlv_encode(encoded, sizeof(encoded), decoded); + fprintf(stderr, "bytes encoded: %lu\n", bytes_encoded); + fprintf(stderr, "encoded result: %s\n", ss_hexdump(encoded, bytes_encoded)); + + fprintf(stderr, "decoded encoded result: (decoder test)\n"); + decoded_from_encoded = ss_btlv_decode(encoded, bytes_encoded, decode_encode_test_descr); + ss_btlv_dump(decoded_from_encoded, 2, SBTLV, LDEBUG); + + ss_btlv_free(decoded); + ss_btlv_free(decoded_from_encoded); +} + +/* Test what happens when some invalid data is fed into the decoder. This is + * not supposed to crash. */ +static void ss_btlv_decode_noise_test(void) +{ + struct ss_list *decoded; + uint8_t encoded[] = { + 0x63, 0x06, 0x20, 0x3a, 0x7c, 0x12, 0xc5, 0xb9, + 0x6b, 0x7e, 0xa6, 0x96, 0x61, 0x4f, 0xe0, 0xa6, + 0x12, 0xfa, 0x37, 0x03, 0x97, 0x26, 0xc6, 0x0d, + 0xd0, 0x9f, 0xed, 0xa6, 0x10, 0x00, 0x2e, 0x00, + 0x4c, 0xd4, 0xdb, 0x07, 0x21, 0x5e, 0xb1, 0x38, + 0xa1, 0xdb, 0x63, 0x0c, 0x3c, 0xf8, 0x22, 0xf5, + 0x20, 0x98, 0x43, 0x58, 0x59, 0x0b, 0xc7, 0x51, + 0xea, 0x01, 0x70, 0xb1, 0x16, 0xef, 0x83, 0x1c, + 0xb8, 0x57, 0xdc, 0x9b, 0xf1, 0x49, 0x6e, 0x97, + }; + fprintf(stderr, "\nTEST: decode a BER-TLV encoded string\n"); + fprintf(stderr, "encoded input: %s\n", ss_hexdump(encoded, sizeof(encoded))); + decoded = ss_btlv_decode(encoded, sizeof(encoded), NULL); + ss_btlv_free(decoded); +} + +int main(int argc, char **argv) +{ + ss_btlv_decode_test_realistic(); + ss_btlv_encode_test_realistic(); + ss_btlv_encode_decode_test(); + ss_btlv_decode_noise_test(); + return 0; +} diff --git a/tests/btlv/btlv_test.err b/tests/btlv/btlv_test.err new file mode 100644 index 0000000..e1788a4 --- /dev/null +++ b/tests/btlv/btlv_test.err @@ -0,0 +1,72 @@ + +TEST: decode a BER-TLV encoded string +encoded input: 6238820278218410a0000000871002ffffffff8907090000a509800171830400018d088a01058c0100c60f90017083010183018183010a83010b +decoded without description: + BTLV DEBUG unknown IE(tag=0x62(0x02), cls=1, constr=true, len=56): 820278218410a0000000871002ffffffff8907090000a509800171830400018d088a01058c0100c60f90017083010183018183010a83010b + BTLV DEBUG unknown IE(tag=0x82(0x02), cls=2, constr=false, len=2): 7821 + BTLV DEBUG unknown IE(tag=0x84(0x04), cls=2, constr=false, len=16): a0000000871002ffffffff8907090000 + BTLV DEBUG unknown IE(tag=0xa5(0x05), cls=2, constr=true, len=9): 800171830400018d08 + BTLV DEBUG unknown IE(tag=0x80(0x00), cls=2, constr=false, len=1): 71 + BTLV DEBUG unknown IE(tag=0x83(0x03), cls=2, constr=false, len=4): 00018d08 + BTLV DEBUG unknown IE(tag=0x8a(0x0a), cls=2, constr=false, len=1): 05 + BTLV DEBUG unknown IE(tag=0x8c(0x0c), cls=2, constr=false, len=1): 00 + BTLV DEBUG unknown IE(tag=0xc6(0x06), cls=3, constr=false, len=15): 90017083010183018183010a83010b +decoded with complete description: + BTLV DEBUG envelope(tag=0x62(0x02), cls=1, constr=true, len=56): 820278218410a0000000871002ffffffff8907090000a509800171830400018d088a01058c0100c60f90017083010183018183010a83010b + BTLV DEBUG one(tag=0x82(0x02), cls=2, constr=false, len=2): 7821 + BTLV DEBUG two(tag=0x84(0x04), cls=2, constr=false, len=16): a0000000871002ffffffff8907090000 + BTLV DEBUG nested-envelope(tag=0xa5(0x05), cls=2, constr=true, len=9): 800171830400018d08 + BTLV DEBUG three(tag=0x80(0x00), cls=2, constr=false, len=1): 71 + BTLV DEBUG four(tag=0x83(0x03), cls=2, constr=false, len=4): 00018d08 + BTLV DEBUG five(tag=0x8a(0x0a), cls=2, constr=false, len=1): 05 + BTLV DEBUG six(tag=0x8c(0x0c), cls=2, constr=false, len=1): 00 + BTLV DEBUG eight(tag=0xc6(0x06), cls=3, constr=false, len=15): 90017083010183018183010a83010b +decoded with non fitting description: + BTLV DEBUG envelope(tag=0x62(0x02), cls=1, constr=true, len=56): 820278218410a0000000871002ffffffff8907090000a509800171830400018d088a01058c0100c60f90017083010183018183010a83010b + BTLV DEBUG one(tag=0x82(0x02), cls=2, constr=false, len=2): 7821 + BTLV DEBUG two(tag=0x84(0x04), cls=2, constr=false, len=16): a0000000871002ffffffff8907090000 + BTLV DEBUG nested-envelope(tag=0xa5(0x05), cls=2, constr=true, len=9): 800171830400018d08 + BTLV DEBUG three(tag=0x80(0x00), cls=2, constr=false, len=1): 71 + BTLV DEBUG unknown IE(tag=0x83(0x03), cls=2, constr=false, len=4): 00018d08 + BTLV DEBUG five(tag=0x8a(0x0a), cls=2, constr=false, len=1): 05 + BTLV DEBUG six(tag=0x8c(0x0c), cls=2, constr=false, len=1): 00 + BTLV DEBUG unknown IE(tag=0xc6(0x06), cls=3, constr=false, len=15): 90017083010183018183010a83010b + +TEST: encode a binary BER-TLV encoded string from decoded list +BER-TLV data to be encoded: + BTLV DEBUG envelope(tag=0x62(0x02), cls=1, constr=true, len=0) + BTLV DEBUG one(tag=0x82(0x02), cls=2, constr=false, len=2): 7821 + BTLV DEBUG two(tag=0x84(0x04), cls=2, constr=false, len=16): a0000000871002ffffffff8907090000 + BTLV DEBUG nested-envelope(tag=0xa5(0x05), cls=2, constr=true, len=0) + BTLV DEBUG three(tag=0x80(0x00), cls=2, constr=false, len=1): 71 + BTLV DEBUG four(tag=0x83(0x03), cls=2, constr=false, len=4): 00018d08 + BTLV DEBUG five(tag=0x8a(0x0a), cls=2, constr=false, len=1): 05 + BTLV DEBUG six(tag=0x8c(0x0c), cls=2, constr=false, len=1): 00 + BTLV DEBUG eight(tag=0xc6(0x06), cls=3, constr=false, len=15): 90017083010183018183010a83010b +expected result: 6238820278218410a0000000871002ffffffff8907090000a509800171830400018d088a01058c0100c60f90017083010183018183010a83010b +encoded result: 6238820278218410a0000000871002ffffffff8907090000a509800171830400018d088a01058c0100c60f90017083010183018183010a83010b + +TEST: encode a binary BER-TLV encoded string with multi byte header fields +BER-TLV data to be encoded: (encoder test) + BTLV DEBUG single-byte-tag(tag=0x0a(0x0a), cls=0, constr=false, len=1): ff + BTLV DEBUG two-byte-tag(tag=0xdf55(0x55), cls=3, constr=false, len=1): ff + BTLV DEBUG three-byte-tag(tag=0xdfaaaa(0x2aaa), cls=3, constr=false, len=1): ff + BTLV DEBUG one-byte-len(tag=0x01(0x01), cls=0, constr=false, len=126): aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + BTLV DEBUG two-byte-len(tag=0x02(0x02), cls=0, constr=false, len=255): bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + BTLV DEBUG three-byte-len(tag=0x03(0x03), cls=0, constr=false, len=65535): cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc... +bytes encoded: 65937 +encoded result: 0a01ffdf5501ffdfaaaa01ff017eaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0281ffbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0382ffffcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc... +decoded encoded result: (decoder test) + BTLV DEBUG single-byte-tag(tag=0x0a(0x0a), cls=0, constr=false, len=1): ff + BTLV DEBUG two-byte-tag(tag=0xdf55(0x55), cls=3, constr=false, len=1): ff + BTLV DEBUG three-byte-tag(tag=0xdfaaaa(0x2aaa), cls=3, constr=false, len=1): ff + BTLV DEBUG one-byte-len(tag=0x01(0x01), cls=0, constr=false, len=126): aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + BTLV DEBUG two-byte-len(tag=0x02(0x02), cls=0, constr=false, len=255): bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + BTLV DEBUG three-byte-len(tag=0x03(0x03), cls=0, constr=false, len=65535): cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc... + +TEST: decode a BER-TLV encoded string +encoded input: 6306203a7c12c5b96b7ea696614fe0a612fa37039726c60dd09feda610002e004cd4db07215eb138a1db630c3cf822f520984358590bc751ea0170b116ef831cb857dc9bf1496e97 + BTLV DEBUG exceeding buffer bounds: len=6, inc=1, bytes_ahead=58, cannot decode IE + BTLV ERROR Error decoding BTLV (203a7c12c5b9). + BTLV DEBUG exceeding buffer bounds: len=64, inc=1, bytes_ahead=126, cannot decode IE + BTLV ERROR Error decoding BTLV (6b7ea696614fe0a612fa37039726c60dd09feda610002e004cd4db07215eb138a1db630c3cf822f520984358590bc751ea0170b116ef831cb857dc9bf1496e97). diff --git a/tests/ctlv/CMakeLists.txt b/tests/ctlv/CMakeLists.txt new file mode 100644 index 0000000..cc69b2a --- /dev/null +++ b/tests/ctlv/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +add_executable(ctlv_test ctlv_test.c) + +include_directories(${PROJECT_SOURCE_DIR}) +include_directories(${PROJECT_SOURCE_DIR}/include) + +target_link_libraries(ctlv_test uicc) + +add_test(NAME ctlv_test COMMAND sh -c "$ > ctlv_test.out") diff --git a/tests/ctlv/Makefile.am b/tests/ctlv/Makefile.am new file mode 100644 index 0000000..2e6a43e --- /dev/null +++ b/tests/ctlv/Makefile.am @@ -0,0 +1,28 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(NULL) + +EXTRA_DIST = \ + ctlv_test.ok \ + $(NULL) + +noinst_PROGRAMS = \ + ctlv_test \ + $(NULL) + +ctlv_test_SOURCES = \ + ctlv_test.c \ + $(NULL) + +ctlv_test_LDADD = \ + $(top_srcdir)/src/softsim/uicc/libuicc.a \ + $(NULL) diff --git a/tests/ctlv/ctlv_test.c b/tests/ctlv/ctlv_test.c new file mode 100644 index 0000000..471b169 --- /dev/null +++ b/tests/ctlv/ctlv_test.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include "src/softsim/uicc/ctlv.h" + +static void ss_ctlv_decode_test(void) +{ + uint8_t encoded[] = + { 0x81, 0x03, 0x01, 0x21, 0x00, 0x82, 0x02, 0x81, 0x02, + 0x8d, 0x0c, 0x04, 'H', 'E', 'L', 'L', 'O', ',', 'W', 'O', 'R', + 'L', 'D', + 0xc8, 0x04, 0x01, 0x02, 0x03, 0x04 + }; + + struct ss_list *decoded; + + fprintf(stderr, "\nTEST: decode a COMPREHENSION-TLV encoded string\n"); + fprintf(stderr, "encoded input: %s\n", + ss_hexdump(encoded, sizeof(encoded))); + + fprintf(stderr, "decoded output:\n"); + decoded = ss_ctlv_decode(encoded, sizeof(encoded)); + ss_ctlv_dump(decoded, 0, SCTLV, LDEBUG); + ss_ctlv_free(decoded); +} + +static void ss_ctlv_encode_test(void) +{ + struct ss_list *decoded; + uint8_t encoded[1024]; + int rc; + + uint8_t encoded_expected[] = + { 0x81, 0x03, 0x01, 0x21, 0x00, 0x82, 0x02, 0x81, 0x02, + 0x8d, 0x0c, 0x04, 'H', 'E', 'L', 'L', 'O', ',', 'W', 'O', 'R', + 'L', 'D', + 0xc8, 0x04, 0x01, 0x02, 0x03, 0x04 + }; + + /* Set up BER-TLV tree */ + decoded = SS_ALLOC(struct ss_list); + ss_list_init(decoded); + + ss_ctlv_new_ie(decoded, 0x01, true, 3, (uint8_t *) "\x01\x21\x00"); + ss_ctlv_new_ie(decoded, 0x02, true, 2, (uint8_t *) "\x81\x02"); + ss_ctlv_new_ie(decoded, 0x0d, true, 12, + (uint8_t *) + "\x04\x48\x45\x4c\x4c\x4f\x2c\x57\x4f\x52\x4c\x44"); + ss_ctlv_new_ie(decoded, 0x48, true, 4, (uint8_t *) "\x01\x02\x03\x04"); + + fprintf(stderr, + "\nTEST: encode a binary COMPREHENSION-TLV encoded string from decoded list\n"); + fprintf(stderr, "COMPREHENSION-TLV data to be encoded:\n"); + ss_ctlv_dump(decoded, 2, SCTLV, LDEBUG); + rc = ss_ctlv_encode(encoded, sizeof(encoded), decoded); + fprintf(stderr, "expected result: %s\n", + ss_hexdump(encoded_expected, sizeof(encoded_expected))); + fprintf(stderr, "encoded result: %s\n", ss_hexdump(encoded, rc)); + ss_ctlv_free(decoded); +} + +int main(int argc, char **argv) +{ + ss_ctlv_decode_test(); + ss_ctlv_encode_test(); + return 0; +} diff --git a/tests/ctlv/ctlv_test.err b/tests/ctlv/ctlv_test.err new file mode 100644 index 0000000..fc3da48 --- /dev/null +++ b/tests/ctlv/ctlv_test.err @@ -0,0 +1,17 @@ + +TEST: decode a COMPREHENSION-TLV encoded string +encoded input: 8103012100820281028d0c0448454c4c4f2c574f524c44c80401020304 +decoded output: + CTLV DEBUG (tag=0x81(0x01), cr=true, len=3): 012100 + CTLV DEBUG (tag=0x82(0x02), cr=true, len=2): 8102 + CTLV DEBUG (tag=0x8d(0x0d), cr=true, len=12): 0448454c4c4f2c574f524c44 + CTLV DEBUG (tag=0xc8(0x48), cr=true, len=4): 01020304 + +TEST: encode a binary COMPREHENSION-TLV encoded string from decoded list +COMPREHENSION-TLV data to be encoded: + CTLV DEBUG (tag=0x81(0x01), cr=true, len=3): 012100 + CTLV DEBUG (tag=0x82(0x02), cr=true, len=2): 8102 + CTLV DEBUG (tag=0x8d(0x0d), cr=true, len=12): 0448454c4c4f2c574f524c44 + CTLV DEBUG (tag=0xc8(0x48), cr=true, len=4): 01020304 +expected result: 8103012100820281028d0c0448454c4c4f2c574f524c44c80401020304 +encoded result: 8103012100820281028d0c0448454c4c4f2c574f524c44c80401020304 diff --git a/tests/des/CMakeLists.txt b/tests/des/CMakeLists.txt new file mode 100644 index 0000000..efc2288 --- /dev/null +++ b/tests/des/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +add_executable(des_test des_test.c) + +include_directories(${PROJECT_SOURCE_DIR}) +include_directories(${PROJECT_SOURCE_DIR}/include) + +target_link_libraries(des_test uicc crypto) + +add_test(NAME des_test COMMAND sh -c "$ > des_test.out") + +add_test(NAME des_test_compare + COMMAND ${CMAKE_COMMAND} -E compare_files --ignore-eol + ${PROJECT_SOURCE_DIR}/build/tests/des/des_test.out + ${CMAKE_CURRENT_SOURCE_DIR}/des_test.ok +) diff --git a/tests/des/Makefile.am b/tests/des/Makefile.am new file mode 100644 index 0000000..49298f7 --- /dev/null +++ b/tests/des/Makefile.am @@ -0,0 +1,29 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(NULL) + +EXTRA_DIST = \ + des_test.ok \ + $(NULL) + +noinst_PROGRAMS = \ + des_test \ + $(NULL) + +des_test_SOURCES = \ + des_test.c \ + $(NULL) + +des_test_LDADD = \ + $(top_srcdir)/src/softsim/uicc/libuicc.a \ + $(top_srcdir)/src/softsim/crypto/libcrypto.a \ + $(NULL) diff --git a/tests/des/des_test.c b/tests/des/des_test.c new file mode 100644 index 0000000..ad4a316 --- /dev/null +++ b/tests/des/des_test.c @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include "src/softsim/uicc/utils_3des.h" + +void des_cmac_test_one_block(void) +{ + struct utils_3des_cc_ctx cc_3des; + + uint8_t block[] = { + 0x00, 0x28, 0x15, 0x1e, 0x19, 0x32, 0x32, 0xb0, + 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, + 0x00, 0xa4, 0x00, 0x04, 0x02, 0x3f, 0x00, 0x00, + 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 + }; + + uint8_t key[] = { + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + 0x01, 0x23, 0x45, 0x67, 0x01, 0x23, 0x45, 0x67, + }; + + ss_utils_3des_cc_setup(&cc_3des, key); + printf("3DES-CMACCMAC feed block: %s\n", ss_hexdump(block, sizeof(block))); + ss_utils_3des_cc_feed(&cc_3des, block, sizeof(block)); + ss_utils_3des_cc_cleanup(&cc_3des); + printf("3DES-CMACCMAC checksum: %s\n", + ss_hexdump(cc_3des.cbc, sizeof(cc_3des.cbc))); +} + +void des_cmac_test_one_block_padded(void) +{ + struct utils_3des_cc_ctx cc_3des; + + /* This is the same data as in des_cmac_test_one_block(), but it is + * already padded with zeros already before it is fed into the checksum + * algorithm */ + uint8_t block[] = { + 0x00, 0x28, 0x15, 0x1e, 0x19, 0x32, 0x32, 0xb0, + 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, + 0x00, 0xa4, 0x00, 0x04, 0x02, 0x3f, 0x00, 0x00, + 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + uint8_t key[] = { + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + 0x01, 0x23, 0x45, 0x67, 0x01, 0x23, 0x45, 0x67, + }; + + ss_utils_3des_cc_setup(&cc_3des, key); + printf("3DES-CMACCMAC feed block: %s\n", ss_hexdump(block, sizeof(block))); + ss_utils_3des_cc_feed(&cc_3des, block, sizeof(block)); + ss_utils_3des_cc_cleanup(&cc_3des); + printf("3DES-CMACCMAC checksum: %s\n", + ss_hexdump(cc_3des.cbc, sizeof(cc_3des.cbc))); +} + +void des_cmac_test_two_blocks(void) +{ + struct utils_3des_cc_ctx cc_3des; + + uint8_t block_1[] = { + 0x00, 0x28, 0x15, 0x1e, 0x19, 0x32, 0x32, 0xb0, + 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, + }; + + uint8_t block_2[] = { + 0x00, 0xa4, 0x00, 0x04, 0x02, 0x3f, 0x00, 0x00, + 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 + }; + + uint8_t key[] = { + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + 0x01, 0x23, 0x45, 0x67, 0x01, 0x23, 0x45, 0x67, + }; + + ss_utils_3des_cc_setup(&cc_3des, key); + printf("3DES-CMAC feed block 1: %s\n", + ss_hexdump(block_1, sizeof(block_1))); + ss_utils_3des_cc_feed(&cc_3des, block_1, sizeof(block_1)); + printf("3DES-CMAC feed block 2: %s\n", + ss_hexdump(block_2, sizeof(block_2))); + ss_utils_3des_cc_feed(&cc_3des, block_2, sizeof(block_2)); + ss_utils_3des_cc_cleanup(&cc_3des); + printf("3DES-CMAC checksum: %s\n", + ss_hexdump(cc_3des.cbc, sizeof(cc_3des.cbc))); +} + +int main(int argc, char **argv) +{ + des_cmac_test_one_block(); + des_cmac_test_one_block_padded(); + des_cmac_test_two_blocks(); + return 0; +} diff --git a/tests/des/des_test.ok b/tests/des/des_test.ok new file mode 100644 index 0000000..fd2e9ef --- /dev/null +++ b/tests/des/des_test.ok @@ -0,0 +1,7 @@ +3DES-CMACCMAC feed block: 0028151e193232b0001100000000010600a40004023f0000c0000000000000000000 +3DES-CMACCMAC checksum: 178bed31c93885cf +3DES-CMACCMAC feed block: 0028151e193232b0001100000000010600a40004023f0000c0000000000000000000000000000000 +3DES-CMACCMAC checksum: 178bed31c93885cf +3DES-CMAC feed block 1: 0028151e193232b00011000000000106 +3DES-CMAC feed block 2: 00a40004023f0000c0000000000000000000 +3DES-CMAC checksum: 178bed31c93885cf diff --git a/tests/fcp/CMakeLists.txt b/tests/fcp/CMakeLists.txt new file mode 100644 index 0000000..886d44b --- /dev/null +++ b/tests/fcp/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +add_executable(fcp_test fcp_test.c) + +include_directories(${PROJECT_SOURCE_DIR}) +include_directories(${PROJECT_SOURCE_DIR}/include) + +target_link_libraries(fcp_test uicc) + +add_test(NAME fcp_test COMMAND sh -c "$ > fcp_test.out") + +add_test(NAME fcp_test_compare + COMMAND ${CMAKE_COMMAND} -E compare_files --ignore-eol + ${PROJECT_SOURCE_DIR}/build/tests/fcp/fcp_test.out + ${CMAKE_CURRENT_SOURCE_DIR}/fcp_test.ok +) diff --git a/tests/fcp/Makefile.am b/tests/fcp/Makefile.am new file mode 100644 index 0000000..b264863 --- /dev/null +++ b/tests/fcp/Makefile.am @@ -0,0 +1,28 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(NULL) + +EXTRA_DIST = \ + fcp_test.ok \ + $(NULL) + +noinst_PROGRAMS = \ + fcp_test \ + $(NULL) + +fcp_test_SOURCES = \ + fcp_test.c \ + $(NULL) + +fcp_test_LDADD = \ + $(top_srcdir)/src/softsim/uicc/libuicc.a \ + $(NULL) diff --git a/tests/fcp/fcp_test.c b/tests/fcp/fcp_test.c new file mode 100644 index 0000000..3bcdfd4 --- /dev/null +++ b/tests/fcp/fcp_test.c @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include "src/softsim/uicc/btlv.h" +#include "src/softsim/uicc/fcp.h" + +static void dump_fd(struct ss_fcp_file_descr *fd) +{ + printf("fd.shareable = %u\n", fd->shareable); + printf("fd.type = %u\n", fd->type); + printf("fd.structure = %u\n", fd->structure); + if (fd->structure == SS_FCP_LINEAR_FIXED || fd->structure == SS_FCP_CYCLIC) { + printf("fd.record_len = %u\n", fd->record_len); + printf("fd.number_of_records = %u\n", fd->number_of_records); + } +} + +struct ss_buf *get_fd_from_hexstr(char *hexstr) +{ + struct ss_buf *fcp_bin; + struct ss_list *fcp_decoded; + struct ber_tlv_ie *fcp_decoded_envelope; + struct ber_tlv_ie *fcp_decoded_file_descr; + struct ss_buf *encoded_fd_buf; + + fcp_bin = ss_buf_from_hexstr(hexstr); + fcp_decoded = ss_fcp_decode(fcp_bin); + fcp_decoded_envelope = ss_btlv_get_ie(fcp_decoded, TS_102_221_IEI_FCP_TMPL); + fcp_decoded_file_descr = ss_btlv_get_ie(fcp_decoded_envelope->nested, 0x82); + encoded_fd_buf = ss_buf_dup(fcp_decoded_file_descr->value); + + ss_buf_free(fcp_bin); + ss_btlv_free(fcp_decoded); + + return encoded_fd_buf; +} + +void ss_fcp_get_file_descr_test(void) +{ + char fcp_mf[] = + "622d8202782183023f00a509800171830400018d088a01058c04261a0000c60f90017083010183018183010a83010b"; + char fcp_ef_iccid[] = + "621f8202412183022fe2a506d00120d201058a01058b032f06028002000a880110"; + char fcp_adf_usim[] = + "6238820278218410a0000000871002ffffffff8907090000a509800171830400018d088a01058c0100c60f90017083010183018183010a83010b"; + char fcp_ef_dir[] = + "622282054221002b0883022f00a506d00120d2010b8a01058b032f0604800201588801f0"; + struct ss_fcp_file_descr fd; + struct ss_buf *encoded_fd_buf; + int rc; + + encoded_fd_buf = get_fd_from_hexstr(fcp_mf); + printf("FCP MF: %s\n", fcp_mf); + rc = ss_fcp_dec_file_descr(&fd, encoded_fd_buf); + printf("rc=%i\n", rc); + dump_fd(&fd); + ss_buf_free(encoded_fd_buf); + + encoded_fd_buf = get_fd_from_hexstr(fcp_ef_iccid); + printf("FCP EF.ICCID: %s\n", fcp_ef_iccid); + rc = ss_fcp_dec_file_descr(&fd, encoded_fd_buf); + printf("rc=%i\n", rc); + dump_fd(&fd); + ss_buf_free(encoded_fd_buf); + + encoded_fd_buf = get_fd_from_hexstr(fcp_adf_usim); + printf("FCP ADF.USIM: %s\n", fcp_adf_usim); + rc = ss_fcp_dec_file_descr(&fd, encoded_fd_buf); + printf("rc=%i\n", rc); + dump_fd(&fd); + ss_buf_free(encoded_fd_buf); + + encoded_fd_buf = get_fd_from_hexstr(fcp_ef_dir); + printf("FCP EF.DIR: %s\n", fcp_ef_dir); + rc = ss_fcp_dec_file_descr(&fd, encoded_fd_buf); + printf("rc=%i\n", rc); + dump_fd(&fd); + ss_buf_free(encoded_fd_buf); +} + +void ss_fcp_gen_file_descr_test(void) +{ + struct ss_fcp_file_descr fd; + struct ss_buf *buf; + + /* record oriented file */ + fd.shareable = true; + fd.type = SS_FCP_WORKING_EF; + fd.structure = SS_FCP_LINEAR_FIXED; + fd.record_len = 8; + fd.number_of_records = 5; + buf = ss_fcp_gen_file_descr(&fd); + printf("generated FD for a record oriented file: %s\n", ss_hexdump(buf->data, buf->len)); + ss_buf_free(buf); + + /* transparent file */ + fd.shareable = true; + fd.type = SS_FCP_WORKING_EF; + fd.structure = SS_FCP_TRANSPARENT; + buf = ss_fcp_gen_file_descr(&fd); + printf("generated FD for a transparent file: %s\n", ss_hexdump(buf->data, buf->len)); + ss_buf_free(buf); +} + +void ss_fcp_gen_test(void) +{ + struct ss_fcp_file_descr fd; + struct ss_buf *fcp; + + /* record oriented file */ + fd.shareable = true; + fd.type = SS_FCP_WORKING_EF; + fd.structure = SS_FCP_LINEAR_FIXED; + fd.record_len = 8; + fd.number_of_records = 5; + fcp = ss_fcp_gen(&fd, 0x1aaaa, 0); + printf("generated FCP for a record oriented file: %s\n", + ss_hexdump(fcp->data, fcp->len)); + ss_buf_free(fcp); + + /* transparent file */ + fd.shareable = true; + fd.type = SS_FCP_WORKING_EF; + fd.structure = SS_FCP_TRANSPARENT; + + fcp = ss_fcp_gen(&fd, 0x1cccc, 255); + printf("generated FCP for a transparent file: %s\n", + ss_hexdump(fcp->data, fcp->len)); + ss_buf_free(fcp); +} + +int main(int argc, char **argv) +{ + ss_fcp_get_file_descr_test(); + ss_fcp_gen_file_descr_test(); + ss_fcp_gen_test(); + return 0; +} diff --git a/tests/fcp/fcp_test.ok b/tests/fcp/fcp_test.ok new file mode 100644 index 0000000..227740b --- /dev/null +++ b/tests/fcp/fcp_test.ok @@ -0,0 +1,26 @@ +FCP MF: 622d8202782183023f00a509800171830400018d088a01058c04261a0000c60f90017083010183018183010a83010b +rc=0 +fd.shareable = 0 +fd.type = 7 +fd.structure = 0 +FCP EF.ICCID: 621f8202412183022fe2a506d00120d201058a01058b032f06028002000a880110 +rc=0 +fd.shareable = 0 +fd.type = 0 +fd.structure = 1 +FCP ADF.USIM: 6238820278218410a0000000871002ffffffff8907090000a509800171830400018d088a01058c0100c60f90017083010183018183010a83010b +rc=0 +fd.shareable = 0 +fd.type = 7 +fd.structure = 0 +FCP EF.DIR: 622282054221002b0883022f00a506d00120d2010b8a01058b032f0604800201588801f0 +rc=0 +fd.shareable = 0 +fd.type = 0 +fd.structure = 2 +fd.record_len = 43 +fd.number_of_records = 8 +generated FD for a record oriented file: 4221000805 +generated FD for a transparent file: 4121 +generated FCP for a record oriented file: 62108205422100080583040001aaaa800128 +generated FCP for a transparent file: 620d8202412183040001cccc8001ff diff --git a/tests/list/CMakeLists.txt b/tests/list/CMakeLists.txt new file mode 100644 index 0000000..914c300 --- /dev/null +++ b/tests/list/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +add_executable(list_test list_test.c) + +include_directories(${PROJECT_SOURCE_DIR}) +include_directories(${PROJECT_SOURCE_DIR}/include) + +add_test(NAME list_test COMMAND sh -c "$ > list_test.out") + +add_test(NAME list_test_compare + COMMAND ${CMAKE_COMMAND} -E compare_files --ignore-eol + ${PROJECT_SOURCE_DIR}/build/tests/list/list_test.out + ${CMAKE_CURRENT_SOURCE_DIR}/list_test.ok +) diff --git a/tests/list/Makefile.am b/tests/list/Makefile.am new file mode 100644 index 0000000..abb40c1 --- /dev/null +++ b/tests/list/Makefile.am @@ -0,0 +1,27 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(NULL) + +EXTRA_DIST = \ + list_test.ok \ + $(NULL) + +noinst_PROGRAMS = \ + list_test \ + $(NULL) + +list_test_SOURCES = \ + list_test.c \ + $(NULL) + +list_test_LDADD = \ + $(NULL) diff --git a/tests/list/list_test.c b/tests/list/list_test.c new file mode 100644 index 0000000..13fc014 --- /dev/null +++ b/tests/list/list_test.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include + +/* A sample struct that can be organzied using a list */ +struct list_test { + struct ss_list list; + int user_data; +}; + +static void add_iterate_and_remove_test(void) +{ + struct ss_list list; + struct list_test a; + struct list_test b; + struct list_test c; + struct list_test d; + struct list_test e; + struct list_test f; + struct list_test *cursor; + struct list_test *precursor; + + printf + ("TEST: Initialize a list, add some elements, iterate the list\n"); + + a.user_data = 11; + b.user_data = 22; + c.user_data = 33; + d.user_data = 1; + e.user_data = 2; + f.user_data = 3; + + /* Before we can do anything with the list, we must initialize it */ + ss_list_init(&list); + + /* Add some elements to the list */ + ss_list_put(&list, &a.list); + ss_list_put(&list, &b.list); + ss_list_put(&list, &c.list); + ss_list_push(&list, &d.list); + ss_list_push(&list, &e.list); + ss_list_push(&list, &f.list); + + /* Iterate over the list */ + SS_LIST_FOR_EACH(&list, cursor, struct list_test, list) { + printf(" user_data=%d\n", cursor->user_data); + } + + /* Iterate over the list and remove elements from it */ + SS_LIST_FOR_EACH_SAVE(&list, cursor, precursor, struct list_test, list) { + printf(" user_data=%d\n", cursor->user_data); + + /* Remove element from the list, this must not disturb the + * list iteration */ + ss_list_remove(&cursor->list); + cursor = NULL; + } + + /* Prove that the list is emty, we should see no output */ + SS_LIST_FOR_EACH(&list, cursor, struct list_test, list) { + printf(" user_data=%d (this shouldn't be here!)\n", + cursor->user_data); + } +} + +int main(int argc, char **argv) +{ + add_iterate_and_remove_test(); + return 0; +} diff --git a/tests/list/list_test.ok b/tests/list/list_test.ok new file mode 100644 index 0000000..917cac9 --- /dev/null +++ b/tests/list/list_test.ok @@ -0,0 +1,13 @@ +TEST: Initialize a list, add some elements, iterate the list + user_data=3 + user_data=2 + user_data=1 + user_data=11 + user_data=22 + user_data=33 + user_data=3 + user_data=2 + user_data=1 + user_data=11 + user_data=22 + user_data=33 diff --git a/tests/ota/CMakeLists.txt b/tests/ota/CMakeLists.txt new file mode 100644 index 0000000..f65fcc9 --- /dev/null +++ b/tests/ota/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +add_executable(ota_test ota_test.c) + +include_directories(${PROJECT_SOURCE_DIR}) +include_directories(${PROJECT_SOURCE_DIR}/include) + +target_link_libraries(ota_test uicc crypto) + +add_test(NAME ota_test COMMAND sh -c "$ > ota_test.out") + +add_test(NAME ota_test_compare + COMMAND ${CMAKE_COMMAND} -E compare_files --ignore-eol + ${PROJECT_SOURCE_DIR}/build/tests/ota/ota_test.out + ${CMAKE_CURRENT_SOURCE_DIR}/ota_test.ok +) diff --git a/tests/ota/Makefile.am b/tests/ota/Makefile.am new file mode 100644 index 0000000..c7aeb83 --- /dev/null +++ b/tests/ota/Makefile.am @@ -0,0 +1,29 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(NULL) + +EXTRA_DIST = \ + ota_test.ok \ + $(NULL) + +noinst_PROGRAMS = \ + ota_test \ + $(NULL) + +ota_test_SOURCES = \ + ota_test.c \ + $(NULL) + +ota_test_LDADD = \ + $(top_srcdir)/src/softsim/uicc/libuicc.a \ + $(top_srcdir)/src/softsim/crypto/libcrypto.a \ + $(NULL) diff --git a/tests/ota/ota_test.c b/tests/ota/ota_test.c new file mode 100644 index 0000000..4bb6344 --- /dev/null +++ b/tests/ota/ota_test.c @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include "src/softsim/uicc/utils_3des.h" +#include "src/softsim/uicc/utils_ota.h" + +static void calc_and_print_pcnt(enum enc_algorithm algorithm, size_t data_len, uint8_t pcnt_expected) +{ + char *algorithm_str; + uint8_t pcnt; + + switch (algorithm) { + case TRIPLE_DES_CBC2: + algorithm_str = "DES_CBC2"; + break; + case AES_CBC: + algorithm_str = "AES"; + break; + case NONE: + algorithm_str = "NONE"; + break; + default: + algorithm_str = "(INVALID)"; + } + + pcnt = ss_utils_ota_calc_pcnt(algorithm, data_len); + + printf("algorithm=%s data_len=%zu, pcnt=%u, pcnt_expected=%u\n", + algorithm_str, data_len, ss_utils_ota_calc_pcnt(algorithm, data_len), pcnt_expected); + + assert(pcnt == pcnt_expected); + +} + +static void ss_utils_ota_calc_pcnt_test(void) +{ + /* DES Aligned on blocksize */ + calc_and_print_pcnt(TRIPLE_DES_CBC2, 0, 0); + calc_and_print_pcnt(TRIPLE_DES_CBC2, 8, 0); + calc_and_print_pcnt(TRIPLE_DES_CBC2, 16, 0); + calc_and_print_pcnt(TRIPLE_DES_CBC2, 24, 0); + + /* DES Off by +1 */ + calc_and_print_pcnt(TRIPLE_DES_CBC2, 1, 7); + calc_and_print_pcnt(TRIPLE_DES_CBC2, 9, 7); + calc_and_print_pcnt(TRIPLE_DES_CBC2, 17, 7); + calc_and_print_pcnt(TRIPLE_DES_CBC2, 25, 7); + + /* DES Off by -1 */ + calc_and_print_pcnt(TRIPLE_DES_CBC2, 7, 1); + calc_and_print_pcnt(TRIPLE_DES_CBC2, 15, 1); + calc_and_print_pcnt(TRIPLE_DES_CBC2, 23, 1); + + /* AES Aligned on blocksize */ + calc_and_print_pcnt(AES_CBC, 0, 0); + calc_and_print_pcnt(AES_CBC, 16, 0); + calc_and_print_pcnt(AES_CBC, 32, 0); + calc_and_print_pcnt(AES_CBC, 48, 0); + + /* AES Off by +1 */ + calc_and_print_pcnt(AES_CBC, 1, 15); + calc_and_print_pcnt(AES_CBC, 17, 15); + calc_and_print_pcnt(AES_CBC, 33, 15); + calc_and_print_pcnt(AES_CBC, 49, 15); + + /* AES Off by -1 */ + calc_and_print_pcnt(AES_CBC, 15, 1); + calc_and_print_pcnt(AES_CBC, 31, 1); + calc_and_print_pcnt(AES_CBC, 47, 1); + + /* No algorithm should always produce zero padding */ + calc_and_print_pcnt(NONE, 0, 0); + calc_and_print_pcnt(NONE, 1, 0); + calc_and_print_pcnt(NONE, 123, 0); + calc_and_print_pcnt(NONE, 456, 0); +} + +static void ss_utils_ota_calc_cc_test(void) +{ + int rc; + uint8_t cc[OTA_INTEGRITY_LEN]; + + uint8_t key[OTA_KEY_LEN] = + { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22 }; + + uint8_t data1[16] = { + 0x00, 0x28, 0x15, 0x1e, 0x19, 0x32, 0x32, 0xb0, + 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, + }; + + uint8_t data2_A[16] = { + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + 0x01, 0x23, 0x45, 0x67, 0x01, 0x23, 0x45, 0x67, + }; + + uint8_t data2_B[17] = { + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + 0x01, 0x23, 0x45, 0x67, 0x01, 0x23, 0x45, 0x67, + 0xAA, + }; + + uint8_t data2_C[15] = { + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, + 0x01, 0x23, 0x45, 0x67, 0x01, 0x23, 0x45, + }; + + rc = ss_utils_ota_calc_cc(cc, sizeof(cc), key, sizeof(key), TRIPLE_DES_CBC2, + data1, sizeof(data1), data2_A, sizeof(data2_A)); + printf("3DES-CMAC data1=%s, data2=%s, rc=%d, checksum=%s\n", ss_hexdump(data1, sizeof(data1)), + ss_hexdump(data2_A, sizeof(data2_A)), rc, ss_hexdump(cc, sizeof(cc))); + + rc = ss_utils_ota_calc_cc(cc, sizeof(cc), key, sizeof(key), TRIPLE_DES_CBC2, + data1, sizeof(data1), data2_B, sizeof(data2_B)); + printf("3DES-CMAC data1=%s, data2=%s, rc=%d, checksum=%s\n", ss_hexdump(data1, sizeof(data1)), + ss_hexdump(data2_B, sizeof(data2_B)), rc, ss_hexdump(cc, sizeof(cc))); + + rc = ss_utils_ota_calc_cc(cc, sizeof(cc), key, sizeof(key), TRIPLE_DES_CBC2, + data1, sizeof(data1), data2_C, sizeof(data2_C)); + printf("3DES-CMAC data1=%s, data2=%s, rc=%d, checksum=%s\n", ss_hexdump(data1, sizeof(data1)), + ss_hexdump(data2_C, sizeof(data2_C)), rc, ss_hexdump(cc, sizeof(cc))); + + rc = ss_utils_ota_calc_cc(cc, sizeof(cc), key, sizeof(key), AES_CMAC, + data1, sizeof(data1), data2_A, sizeof(data2_A)); + printf("AES-CMAC data1=%s, data2=%s, rc=%d, checksum=%s\n", ss_hexdump(data1, sizeof(data1)), + ss_hexdump(data2_A, sizeof(data2_A)), rc, ss_hexdump(cc, sizeof(cc))); + + rc = ss_utils_ota_calc_cc(cc, sizeof(cc), key, sizeof(key), AES_CMAC, + data1, sizeof(data1), data2_B, sizeof(data2_B)); + printf("AES-CMAC data1=%s, data2=%s, rc=%d, checksum=%s\n", ss_hexdump(data1, sizeof(data1)), + ss_hexdump(data2_B, sizeof(data2_B)), rc, ss_hexdump(cc, sizeof(cc))); + + rc = ss_utils_ota_calc_cc(cc, sizeof(cc), key, sizeof(key), AES_CMAC, + data1, sizeof(data1), data2_C, sizeof(data2_C)); + printf("AES-CMAC data1=%s, data2=%s, rc=%d, checksum=%s\n", ss_hexdump(data1, sizeof(data1)), + ss_hexdump(data2_C, sizeof(data2_C)), rc, ss_hexdump(cc, sizeof(cc))); +} + +int main(int argc, char **argv) +{ + ss_utils_ota_calc_pcnt_test(); + ss_utils_ota_calc_cc_test(); + return 0; +} diff --git a/tests/ota/ota_test.ok b/tests/ota/ota_test.ok new file mode 100644 index 0000000..424e30b --- /dev/null +++ b/tests/ota/ota_test.ok @@ -0,0 +1,32 @@ +algorithm=DES_CBC2 data_len=0, pcnt=0, pcnt_expected=0 +algorithm=DES_CBC2 data_len=8, pcnt=0, pcnt_expected=0 +algorithm=DES_CBC2 data_len=16, pcnt=0, pcnt_expected=0 +algorithm=DES_CBC2 data_len=24, pcnt=0, pcnt_expected=0 +algorithm=DES_CBC2 data_len=1, pcnt=7, pcnt_expected=7 +algorithm=DES_CBC2 data_len=9, pcnt=7, pcnt_expected=7 +algorithm=DES_CBC2 data_len=17, pcnt=7, pcnt_expected=7 +algorithm=DES_CBC2 data_len=25, pcnt=7, pcnt_expected=7 +algorithm=DES_CBC2 data_len=7, pcnt=1, pcnt_expected=1 +algorithm=DES_CBC2 data_len=15, pcnt=1, pcnt_expected=1 +algorithm=DES_CBC2 data_len=23, pcnt=1, pcnt_expected=1 +algorithm=AES data_len=0, pcnt=0, pcnt_expected=0 +algorithm=AES data_len=16, pcnt=0, pcnt_expected=0 +algorithm=AES data_len=32, pcnt=0, pcnt_expected=0 +algorithm=AES data_len=48, pcnt=0, pcnt_expected=0 +algorithm=AES data_len=1, pcnt=15, pcnt_expected=15 +algorithm=AES data_len=17, pcnt=15, pcnt_expected=15 +algorithm=AES data_len=33, pcnt=15, pcnt_expected=15 +algorithm=AES data_len=49, pcnt=15, pcnt_expected=15 +algorithm=AES data_len=15, pcnt=1, pcnt_expected=1 +algorithm=AES data_len=31, pcnt=1, pcnt_expected=1 +algorithm=AES data_len=47, pcnt=1, pcnt_expected=1 +algorithm=NONE data_len=0, pcnt=0, pcnt_expected=0 +algorithm=NONE data_len=1, pcnt=0, pcnt_expected=0 +algorithm=NONE data_len=123, pcnt=0, pcnt_expected=0 +algorithm=NONE data_len=456, pcnt=0, pcnt_expected=0 +3DES-CMAC data1=0028151e193232b00011000000000106, data2=0123456789abcdef0123456701234567, rc=0, checksum=c0995c616fa90e18 +3DES-CMAC data1=0028151e193232b00011000000000106, data2=0123456789abcdef0123456701234567aa, rc=0, checksum=1b3a9c868f1283d3 +3DES-CMAC data1=0028151e193232b00011000000000106, data2=0123456789abcdef01234567012345, rc=0, checksum=d6bfd6ec7ef02a0e +AES-CMAC data1=0028151e193232b00011000000000106, data2=0123456789abcdef0123456701234567, rc=0, checksum=baddda0ee401d63d +AES-CMAC data1=0028151e193232b00011000000000106, data2=0123456789abcdef0123456701234567aa, rc=0, checksum=f090fe9b251d5520 +AES-CMAC data1=0028151e193232b00011000000000106, data2=0123456789abcdef01234567012345, rc=0, checksum=de89e247cdaf128a diff --git a/tests/sms/CMakeLists.txt b/tests/sms/CMakeLists.txt new file mode 100644 index 0000000..20b2de1 --- /dev/null +++ b/tests/sms/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +add_executable(sms_test sms_test.c) + +include_directories(${PROJECT_SOURCE_DIR}) +include_directories(${PROJECT_SOURCE_DIR}/include) + +target_link_libraries(sms_test uicc) + +add_test(NAME sms_test COMMAND sh -c "$ > sms_test.out") diff --git a/tests/sms/Makefile.am b/tests/sms/Makefile.am new file mode 100644 index 0000000..e5ccacb --- /dev/null +++ b/tests/sms/Makefile.am @@ -0,0 +1,28 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(NULL) + +EXTRA_DIST = \ + sms_test.ok \ + $(NULL) + +noinst_PROGRAMS = \ + sms_test \ + $(NULL) + +sms_test_SOURCES = \ + sms_test.c \ + $(NULL) + +sms_test_LDADD = \ + $(top_srcdir)/src/softsim/uicc/libuicc.a \ + $(NULL) diff --git a/tests/sms/sms_test.c b/tests/sms/sms_test.c new file mode 100644 index 0000000..c3506f2 --- /dev/null +++ b/tests/sms/sms_test.c @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include +#include +#include +#include +#include "src/softsim/uicc/sms.h" +#include "src/softsim/uicc/uicc_sms_tx.h" +#include "src/softsim/uicc/context.h" + +/* Clear a conetxt, clear its TX state, and set all the bits required for proactive SMS */ +static void ready_ctx(struct ss_context *ctx) +{ + memset(ctx, 0, sizeof(*ctx)); + ss_uicc_sms_tx_clear(ctx); + /* Simulate context that set all the relevant bits */ + ctx->proactive.term_profile[0] = 0xff; + ctx->proactive.term_profile[1] = 0xff; + ctx->proactive.term_profile[3] = 0xff; + ctx->proactive.term_profile[4] = 0xff; + ctx->proactive.term_profile[5] = 0xff; +} + +void ss_sms_hdr_decode_sms_deliver_test(void) +{ + uint8_t sms_tpdu[] = + { 0x40, 0x08, 0x81, 0x55, 0x66, 0x77, 0x88, 0x7f, 0xf6, 0x00, 0x11, + 0x29, 0x12, 0x00, 0x00, 0x04, 0x3d, 0x02, 0x70, + 0x00, 0x00, 0x38, 0x15, 0x06, 0x01, 0x25, 0x25, 0xb0, 0x00, + 0x10, 0x80, 0x76, 0x6f, 0x57, 0xf0, 0xf8, 0x9b, 0xbd, + 0xbc, 0x09, 0xaf, 0x97, 0xb8, 0xb7, 0xef, 0x7e, 0xdc, 0x6c, + 0x8b, 0xd2, 0xa3, 0x5a, 0x57, 0x14, 0x70, 0x37, 0x49, + 0x75, 0x00, 0x3b, 0xfd, 0x77, 0xac, 0x39, 0x53, 0x1c, 0xc4, + 0x82, 0x71, 0x4e, 0x75, 0x47, 0xa3, 0xf8, 0x5c, 0xc5, + 0xdc, 0x10 + }; + struct ss_sm_hdr sm_hdr; + int rc; + + printf("receive SMS-DELIVER tpu\n"); + + rc = ss_sms_hdr_decode(&sm_hdr, sms_tpdu, sizeof(sms_tpdu)); + + printf(" rc=%i\n", rc); + + printf(" tp_mti=%02x\n", sm_hdr.tp_mti); + printf(" tp_mms=%u\n", sm_hdr.u.sms_deliver.tp_mms); + printf(" tp_rp=%u\n", sm_hdr.u.sms_deliver.tp_rp); + printf(" tp_udhi=%u\n", sm_hdr.u.sms_deliver.tp_udhi); + printf(" tp_sri=%u\n", sm_hdr.u.sms_deliver.tp_sri); + printf(" tp_oa.extension=%u\n", sm_hdr.u.sms_deliver.tp_oa.extension); + printf(" tp_oa.type_of_number=%u\n", + sm_hdr.u.sms_deliver.tp_oa.type_of_number); + printf(" tp_oa.numbering_plan=%u\n", + sm_hdr.u.sms_deliver.tp_oa.numbering_plan); + printf(" tp_oa.digits=%s\n", sm_hdr.u.sms_deliver.tp_oa.digits); + printf(" tp_pid=%02x\n", sm_hdr.u.sms_deliver.tp_pid); + printf(" tp_dcs=%02x\n", sm_hdr.u.sms_deliver.tp_dcs); + printf(" tp_scts=%s\n", + ss_hexdump(sm_hdr.u.sms_deliver.tp_scts, + sizeof(sm_hdr.u.sms_deliver.tp_scts))); + printf(" tp_udl=%u\n", sm_hdr.u.sms_deliver.tp_udl); + + printf(" user data: %s\n", + ss_hexdump(sms_tpdu + rc, sm_hdr.u.sms_deliver.tp_udl)); + + printf("\n"); +} + +void ss_sms_hdr_decode_sms_status_report(void) +{ + uint8_t sms_tpdu[] = + { 0x02, 0x23, 0x08, 0x81, 0x55, 0x66, 0x77, 0x88, 0x00, 0x11, 0x29, + 0x12, 0x00, 0x00, 0x04, 0x00, 0x11, 0x29, 0x12, 0x00, 0x00, + 0x04, 0x42 + }; + struct ss_sm_hdr sm_hdr; + int rc; + + printf("receive SMS-STATUS-REPORT tpu\n"); + + rc = ss_sms_hdr_decode(&sm_hdr, sms_tpdu, sizeof(sms_tpdu)); + + printf(" rc=%i\n", rc); + + printf(" tp_mti=%02x\n", sm_hdr.tp_mti); + printf(" tp_mr=%02x\n", sm_hdr.u.sms_status_report.tp_mr); + printf(" tp_mms=%u\n", sm_hdr.u.sms_status_report.tp_mms); + printf(" tp_ra.extension=%u\n", + sm_hdr.u.sms_status_report.tp_ra.extension); + printf(" tp_ra.type_of_number=%u\n", + sm_hdr.u.sms_status_report.tp_ra.type_of_number); + printf(" tp_ra.numbering_plan=%u\n", + sm_hdr.u.sms_status_report.tp_ra.numbering_plan); + printf(" tp_ra.digits=%s\n", sm_hdr.u.sms_status_report.tp_ra.digits); + printf(" tp_scts=%s\n", + ss_hexdump(sm_hdr.u.sms_status_report.tp_scts, + sizeof(sm_hdr.u.sms_status_report.tp_scts))); + printf(" tp_dt=%s\n", + ss_hexdump(sm_hdr.u.sms_status_report.tp_dt, + sizeof(sm_hdr.u.sms_status_report.tp_dt))); + printf(" tp_st=%02x\n", sm_hdr.u.sms_status_report.tp_st); + + printf("\n"); +} + +void ss_sms_hdr_decode_sms_submit_report(void) +{ + uint8_t sms_tpdu[] = { 0x01, 0x42 }; + struct ss_sm_hdr sm_hdr; + int rc; + + printf("receive SMS-DELIVER tpu\n"); + + rc = ss_sms_hdr_decode(&sm_hdr, sms_tpdu, sizeof(sms_tpdu)); + + printf(" rc=%i\n", rc); + + printf(" tp_mti=%02x\n", sm_hdr.tp_mti); + printf(" tp_fcs=%02x\n", sm_hdr.u.sms_submit_report.tp_fcs); + + printf("\n"); +} + +void ss_sms_hdr_encode_test_sms_deliver_report(void) +{ + int rc; + uint8_t result[256]; + struct ss_sm_hdr sm_hdr; + + printf("send SMS-DELIVER-REPORT tpu\n"); + + memset(&sm_hdr, 0, sizeof(sm_hdr)); + + sm_hdr.tp_mti = SMS_MTI_DELIVER_REPORT; + sm_hdr.u.sms_deliver_report.tp_udhi = true; + sm_hdr.u.sms_deliver_report.tp_fcs = 0x42; + sm_hdr.u.sms_deliver_report.tp_pid_present = true; + sm_hdr.u.sms_deliver_report.tp_pid = 0x23; + sm_hdr.u.sms_deliver_report.tp_dcs_present = true; + sm_hdr.u.sms_deliver_report.tp_dcs = 0x24; + sm_hdr.u.sms_deliver_report.tp_udl_present = true; + sm_hdr.u.sms_deliver_report.tp_udl = 0x25; + + rc = ss_sms_hdr_encode(result, sizeof(result), &sm_hdr); + printf(" rc=%i\n", rc); + printf(" result=%s\n", ss_hexdump(result, rc)); + printf("\n"); +} + +void ss_sms_hdr_encode_test_sms_command(void) +{ + int rc; + uint8_t result[256]; + struct ss_sm_hdr sm_hdr; + + printf("send SMS-COMMAND tpu\n"); + + memset(&sm_hdr, 0, sizeof(sm_hdr)); + + sm_hdr.tp_mti = SMS_MTI_COMMAND; + sm_hdr.u.sms_command.tp_udhi = true; + sm_hdr.u.sms_command.tp_srr = true; + sm_hdr.u.sms_command.tp_mr = 0x23; + sm_hdr.u.sms_command.tp_pid = 0x42; + sm_hdr.u.sms_command.tp_ct = 0x03; + sm_hdr.u.sms_command.tp_mn = 0x11; + sm_hdr.u.sms_command.tp_da.extension = true; + sm_hdr.u.sms_command.tp_da.type_of_number = 0; + sm_hdr.u.sms_command.tp_da.numbering_plan = 1; + strcpy(sm_hdr.u.sms_command.tp_da.digits, "1234567"); + sm_hdr.u.sms_command.tp_cdl = 0x99; + + rc = ss_sms_hdr_encode(result, sizeof(result), &sm_hdr); + printf(" rc=%i\n", rc); + if (rc >= 0) + printf(" result=%s\n", ss_hexdump(result, rc)); + printf("\n"); +} + +void ss_sms_hdr_encode_test_sms_submit(void) +{ + int rc; + uint8_t result[256]; + struct ss_sm_hdr sm_hdr; + + printf("send SMS-SUBMIT tpu\n"); + + memset(&sm_hdr, 0, sizeof(sm_hdr)); + sm_hdr.tp_mti = SMS_MTI_SUBMIT; + sm_hdr.u.sms_submit.tp_rd = true; + sm_hdr.u.sms_submit.tp_vpf = 0x03; + sm_hdr.u.sms_submit.tp_rp = true; + sm_hdr.u.sms_submit.tp_udhi = true; + sm_hdr.u.sms_submit.tp_srr = true; + sm_hdr.u.sms_submit.tp_mr = 0x23; + sm_hdr.u.sms_submit.tp_da.extension = true; + sm_hdr.u.sms_submit.tp_da.type_of_number = 0; + sm_hdr.u.sms_submit.tp_da.numbering_plan = 1; + strcpy(sm_hdr.u.sms_submit.tp_da.digits, "1234567"); + sm_hdr.u.sms_submit.tp_pid = 0x23; + sm_hdr.u.sms_submit.tp_dcs = 0x24; + memset(sm_hdr.u.sms_submit.tp_vp, 0xAA, + sizeof(sm_hdr.u.sms_submit.tp_vp)); + sm_hdr.u.sms_submit.tp_udl = 0x99; + + rc = ss_sms_hdr_encode(result, sizeof(result), &sm_hdr); + printf(" rc=%i\n", rc); + if (rc >= 0) + printf(" result=%s\n", ss_hexdump(result, rc)); + printf("\n"); +} + +static void sms_tx_state_show(struct ss_context *ctx) +{ + struct ss_uicc_sms_tx_sm *sm; + + printf(" resulting ss_uicc_sms_tx_state:\n"); + + if (ctx->proactive.sms_tx_state.pending) { + printf(" pending SMS as command: %s\n", ss_hexdump(ctx->proactive.data, ctx->proactive.data_len)); + } + + SS_LIST_FOR_EACH(&ctx->proactive.sms_tx_state.sm, sm, struct ss_uicc_sms_tx_sm, list) { + printf(" SM:%s, last_msg=%s\n", + ss_hexdump(sm->msg, sm->msg_len), + sm->last_msg ? "true" : "false"); + } +} + +void ss_uicc_sms_tx_test_single(void) +{ + struct ss_context ctx; + int rc; + struct ss_sm_hdr sm_hdr; + uint8_t tp_ud[] = + { 0xc8, 0x22, 0x93, 0xf9, 0x64, 0x5d, 0x9f, 0x52, 0x26, 0x11 }; + + printf("test ss_uicc_sms_tx (message that fits in a single SM)\n"); + ready_ctx(&ctx); + + memset(&sm_hdr, 0, sizeof(sm_hdr)); + sm_hdr.tp_mti = SMS_MTI_SUBMIT; + sm_hdr.u.sms_submit.tp_da.extension = true; + sm_hdr.u.sms_submit.tp_da.type_of_number = 0; + sm_hdr.u.sms_submit.tp_da.numbering_plan = 1; + strcpy(sm_hdr.u.sms_submit.tp_da.digits, "23001"); + sm_hdr.u.sms_submit.tp_udl = sizeof(tp_ud) + 1; /* Depends on encoding, 10 bytes, but 11 digits */ + rc = ss_uicc_sms_tx(&ctx, &sm_hdr, NULL, 0, tp_ud, sizeof(tp_ud), + NULL); + if (rc < 0) + assert(false); + + sms_tx_state_show(&ctx); + ss_uicc_sms_tx_clear(&ctx); +} + +void ss_uicc_sms_tx_test_multi(void) +{ + struct ss_context ctx; + int rc; + struct ss_sm_hdr sm_hdr; + uint8_t tp_ud[300]; + uint8_t ud_hdr[10]; + size_t i; + + printf + ("test ss_uicc_sms_tx (message that needs to be splitted over multiple SM)\n"); + + memset(ud_hdr, 0xf1, sizeof(ud_hdr)); + + /* Fill tp_ud with distinctive pattern */ + for (i = 0; i < sizeof(tp_ud); i++) { + tp_ud[i] = (uint8_t) i & 0x0f; + tp_ud[i] |= tp_ud[i] << 4; + } + tp_ud[sizeof(tp_ud) - 1] = 0x41; + + ready_ctx(&ctx); + + memset(&sm_hdr, 0, sizeof(sm_hdr)); + sm_hdr.tp_mti = SMS_MTI_SUBMIT; + sm_hdr.u.sms_submit.tp_da.extension = true; + sm_hdr.u.sms_submit.tp_da.type_of_number = 0; + sm_hdr.u.sms_submit.tp_da.numbering_plan = 1; + strcpy(sm_hdr.u.sms_submit.tp_da.digits, "23001"); + sm_hdr.u.sms_submit.tp_dcs = 0xF6; + rc = ss_uicc_sms_tx(&ctx, &sm_hdr, ud_hdr, sizeof(ud_hdr), tp_ud, + sizeof(tp_ud), NULL); + if (rc < 0) + assert(false); + + sms_tx_state_show(&ctx); + + ss_uicc_sms_tx_clear(&ctx); +} + +int main(int argc, char **argv) +{ + ss_sms_hdr_decode_sms_deliver_test(); + ss_sms_hdr_decode_sms_status_report(); + ss_sms_hdr_decode_sms_submit_report(); + ss_sms_hdr_encode_test_sms_deliver_report(); + ss_sms_hdr_encode_test_sms_command(); + ss_sms_hdr_encode_test_sms_submit(); + ss_uicc_sms_tx_test_single(); + ss_uicc_sms_tx_test_multi(); + return 0; +} diff --git a/tests/sms/sms_test.ok b/tests/sms/sms_test.ok new file mode 100644 index 0000000..068a3eb --- /dev/null +++ b/tests/sms/sms_test.ok @@ -0,0 +1,55 @@ +receive SMS-DELIVER tpu + rc=17 + tp_mti=00 + tp_mms=0 + tp_rp=0 + tp_udhi=1 + tp_sri=0 + tp_oa.extension=1 + tp_oa.type_of_number=0 + tp_oa.numbering_plan=1 + tp_oa.digits=55667788 + tp_pid=7f + tp_dcs=f6 + tp_scts=00112912000004 + tp_udl=61 + user data: 02700000381506012525b0001080766f57f0f89bbdbc09af97b8b7ef7edc6c8bd2a35a571470374975003bfd77ac39531cc482714e7547a3f85cc5dc10 + +receive SMS-STATUS-REPORT tpu + rc=23 + tp_mti=02 + tp_mr=23 + tp_mms=0 + tp_ra.extension=1 + tp_ra.type_of_number=0 + tp_ra.numbering_plan=1 + tp_ra.digits=55667788 + tp_scts=00112912000004 + tp_dt=00112912000004 + tp_st=42 + +receive SMS-DELIVER tpu + rc=2 + tp_mti=01 + tp_fcs=42 + +send SMS-DELIVER-REPORT tpu + rc=6 + result=404207232425 + +send SMS-COMMAND tpu + rc=12 + result=62234203110781214365f799 + +send SMS-SUBMIT tpu + rc=18 + result=fd230781214365f72324aaaaaaaaaaaaaa99 + +test ss_uicc_sms_tx (message that fits in a single SM) + resulting ss_uicc_sms_tx_state: + pending SMS as command: d01f8103011300820281838b14010005813200f100000bc82293f9645d9f522611 +test ss_uicc_sms_tx (message that needs to be splitted over multiple SM) + resulting ss_uicc_sms_tx_state: + pending SMS as command: d081a28103011300820281838b8196410005813200f100f68c0f0003010301f1f1f1f1f1f1f1f1f1f100112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabb + SM:410005813200f100f68c050003010302ccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff0011, last_msg=false + SM:410005813200f100f6300500030103032233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aa41, last_msg=true diff --git a/tests/testsuite.at b/tests/testsuite.at new file mode 100644 index 0000000..b9704ea --- /dev/null +++ b/tests/testsuite.at @@ -0,0 +1,62 @@ +AT_INIT +AT_BANNER([Regression tests.]) + +AT_SETUP([list]) +AT_KEYWORDS([list]) +cat $abs_srcdir/list/list_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/list/list_test], [0], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([btlv]) +AT_KEYWORDS([btlv]) +cat $abs_srcdir/btlv/btlv_test.err > experr +AT_CHECK([$abs_top_builddir/tests/btlv/btlv_test], [0], [ignore], [experr]) +AT_CLEANUP + +AT_SETUP([ctlv]) +AT_KEYWORDS([ctlv]) +cat $abs_srcdir/ctlv/ctlv_test.err > experr +AT_CHECK([$abs_top_builddir/tests/ctlv/ctlv_test], [0], [ignore], [experr]) +AT_CLEANUP + +AT_SETUP([tlv8]) +AT_KEYWORDS([tlv8]) +cat $abs_srcdir/tlv8/tlv8_test.err > experr +AT_CHECK([$abs_top_builddir/tests/tlv8/tlv8_test], [0], [ignore], [experr]) +AT_CLEANUP + +AT_SETUP([utils]) +AT_KEYWORDS([utils]) +cat $abs_srcdir/utils/utils_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/utils/utils_test], [0], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([fcp]) +AT_KEYWORDS([fcp]) +cat $abs_srcdir/fcp/fcp_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/fcp/fcp_test], [0], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([sms]) +AT_KEYWORDS([sms]) +cat $abs_srcdir/sms/sms_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/sms/sms_test], [0], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([aes]) +AT_KEYWORDS([aes]) +cat $abs_srcdir/aes/aes_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/aes/aes_test], [0], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([des]) +AT_KEYWORDS([des]) +cat $abs_srcdir/des/des_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/des/des_test], [0], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([ota]) +AT_KEYWORDS([ota]) +cat $abs_srcdir/ota/ota_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/ota/ota_test], [0], [expout], [ignore]) +AT_CLEANUP \ No newline at end of file diff --git a/tests/tlv8/CMakeLists.txt b/tests/tlv8/CMakeLists.txt new file mode 100644 index 0000000..ebe7667 --- /dev/null +++ b/tests/tlv8/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +add_executable(tlv8_test tlv8_test.c) + +include_directories(${PROJECT_SOURCE_DIR}) +include_directories(${PROJECT_SOURCE_DIR}/include) + +target_link_libraries(tlv8_test uicc) + +add_test(NAME tlv8_test COMMAND sh -c "$ > tlv8_test.out") diff --git a/tests/tlv8/Makefile.am b/tests/tlv8/Makefile.am new file mode 100644 index 0000000..020a0cf --- /dev/null +++ b/tests/tlv8/Makefile.am @@ -0,0 +1,28 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(NULL) + +EXTRA_DIST = \ + tlv8_test.ok \ + $(NULL) + +noinst_PROGRAMS = \ + tlv8_test \ + $(NULL) + +tlv8_test_SOURCES = \ + tlv8_test.c \ + $(NULL) + +tlv8_test_LDADD = \ + $(top_srcdir)/src/softsim/uicc/libuicc.a \ + $(NULL) diff --git a/tests/tlv8/tlv8_test.c b/tests/tlv8/tlv8_test.c new file mode 100644 index 0000000..cbb8a5d --- /dev/null +++ b/tests/tlv8/tlv8_test.c @@ -0,0 +1,73 @@ +/* + * Author: Philipp Maier + * + */ + +#include +#include +#include +#include +#include +#include "src/softsim/uicc/tlv8.h" + +static void ss_tlv8_decode_test(void) +{ + uint8_t encoded[] = + { 0x81, 0x03, 0x01, 0x21, 0x00, 0x82, 0x02, 0x81, 0x02, + 0x8d, 0x0c, 0x04, 'H', 'E', 'L', 'L', 'O', ',', 'W', 'O', 'R', + 'L', 'D', + 0xc8, 0x04, 0x01, 0x02, 0x03, 0x04 + }; + + struct ss_list *decoded; + + fprintf(stderr, "\nTEST: decode a TLV8 encoded string\n"); + fprintf(stderr, "encoded input: %s\n", + ss_hexdump(encoded, sizeof(encoded))); + + fprintf(stderr, "decoded output:\n"); + decoded = ss_tlv8_decode(encoded, sizeof(encoded)); + ss_tlv8_dump(decoded, 0, STLV8, LDEBUG); + ss_tlv8_free(decoded); +} + +static void ss_tlv8_encode_test(void) +{ + struct ss_list *decoded; + uint8_t encoded[1024]; + int rc; + + uint8_t encoded_expected[] = + { 0x81, 0x03, 0x01, 0x21, 0x00, 0x82, 0x02, 0x81, 0x02, + 0x8d, 0x0c, 0x04, 'H', 'E', 'L', 'L', 'O', ',', 'W', 'O', 'R', + 'L', 'D', + 0xc8, 0x04, 0x01, 0x02, 0x03, 0x04 + }; + + /* Set up BER-TLV tree */ + decoded = SS_ALLOC(struct ss_list); + ss_list_init(decoded); + + ss_tlv8_new_ie(decoded, 0x81, 3, (uint8_t *) "\x01\x21\x00"); + ss_tlv8_new_ie(decoded, 0x82, 2, (uint8_t *) "\x81\x02"); + ss_tlv8_new_ie(decoded, 0x8d, 12, (uint8_t *) + "\x04\x48\x45\x4c\x4c\x4f\x2c\x57\x4f\x52\x4c\x44"); + ss_tlv8_new_ie(decoded, 0xc8, 4, (uint8_t *) "\x01\x02\x03\x04"); + + fprintf(stderr, + "\nTEST: encode a binary TLV8 encoded string from decoded list\n"); + fprintf(stderr, "TLV8 data to be encoded:\n"); + ss_tlv8_dump(decoded, 2, STLV8, LDEBUG); + rc = ss_tlv8_encode(encoded, sizeof(encoded), decoded); + fprintf(stderr, "expected result: %s\n", + ss_hexdump(encoded_expected, sizeof(encoded_expected))); + fprintf(stderr, "encoded result: %s\n", ss_hexdump(encoded, rc)); + ss_tlv8_free(decoded); +} + +int main(int argc, char **argv) +{ + ss_tlv8_decode_test(); + ss_tlv8_encode_test(); + return 0; +} diff --git a/tests/tlv8/tlv8_test.err b/tests/tlv8/tlv8_test.err new file mode 100644 index 0000000..50d1a5a --- /dev/null +++ b/tests/tlv8/tlv8_test.err @@ -0,0 +1,17 @@ + +TEST: decode a TLV8 encoded string +encoded input: 8103012100820281028d0c0448454c4c4f2c574f524c44c80401020304 +decoded output: + TLV8 DEBUG (tag=0x81, len=3): 012100 + TLV8 DEBUG (tag=0x82, len=2): 8102 + TLV8 DEBUG (tag=0x8d, len=12): 0448454c4c4f2c574f524c44 + TLV8 DEBUG (tag=0xc8, len=4): 01020304 + +TEST: encode a binary TLV8 encoded string from decoded list +TLV8 data to be encoded: + TLV8 DEBUG (tag=0x81, len=3): 012100 + TLV8 DEBUG (tag=0x82, len=2): 8102 + TLV8 DEBUG (tag=0x8d, len=12): 0448454c4c4f2c574f524c44 + TLV8 DEBUG (tag=0xc8, len=4): 01020304 +expected result: 8103012100820281028d0c0448454c4c4f2c574f524c44c80401020304 +encoded result: 8103012100820281028d0c0448454c4c4f2c574f524c44c80401020304 diff --git a/tests/utils/CMakeLists.txt b/tests/utils/CMakeLists.txt new file mode 100644 index 0000000..32e5f72 --- /dev/null +++ b/tests/utils/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +add_executable(utils_test utils_test.c) + +include_directories(${PROJECT_SOURCE_DIR}) +include_directories(${PROJECT_SOURCE_DIR}/include) + +target_link_libraries(utils_test uicc) + +add_test(NAME utils_test COMMAND sh -c "$ > utils_test.out") + +add_test(NAME utils_test_compare + COMMAND ${CMAKE_COMMAND} -E compare_files --ignore-eol + ${PROJECT_SOURCE_DIR}/build/tests/utils/utils_test.out + ${CMAKE_CURRENT_SOURCE_DIR}/utils_test.ok +) diff --git a/tests/utils/Makefile.am b/tests/utils/Makefile.am new file mode 100644 index 0000000..497903d --- /dev/null +++ b/tests/utils/Makefile.am @@ -0,0 +1,28 @@ +# Copyright (c) 2024 Onomondo ApS. All rights reserved. +# SPDX-License-Identifier: GPL-3.0-only + +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(NULL) + +EXTRA_DIST = \ + utils_test.ok \ + $(NULL) + +noinst_PROGRAMS = \ + utils_test \ + $(NULL) + +utils_test_SOURCES = \ + utils_test.c \ + $(NULL) + +utils_test_LDADD = \ + $(top_srcdir)/src/softsim/uicc/libuicc.a \ + $(NULL) diff --git a/tests/utils/utils_test.c b/tests/utils/utils_test.c new file mode 100644 index 0000000..58da7fa --- /dev/null +++ b/tests/utils/utils_test.c @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 Onomondo ApS. All rights reserved. + * + * SPDX-License-Identifier: GPL-3.0-only + * + * Author: Philipp Maier + */ + +#include +#include + +void ss_binary_from_hexstr_test(void) +{ + uint8_t output[512]; + uint8_t output_short[10]; + size_t rc; + + char testvec_1[] = "aabbccddeeff00112233445566778899"; + char testvec_2[] = "aabbccddeeff001122334455667788991"; + char testvec_3[] = "aabbccddeeff001HELLO34455667788991"; + + rc = ss_binary_from_hexstr(output, sizeof(output), testvec_1); + printf("input: %s, output: %s\n", testvec_1, ss_hexdump(output, rc)); + + rc = ss_binary_from_hexstr(output_short, sizeof(output_short), + testvec_1); + printf("input: %s, output (short): %s\n", testvec_1, + ss_hexdump(output_short, rc)); + + rc = ss_binary_from_hexstr(output, sizeof(output), testvec_2); + printf("input: %s, output: %s\n", testvec_2, ss_hexdump(output, rc)); + + rc = ss_binary_from_hexstr(output_short, sizeof(output_short), + testvec_2); + printf("input: %s, output (short): %s\n", testvec_2, + ss_hexdump(output_short, rc)); + + rc = ss_binary_from_hexstr(output, sizeof(output), testvec_3); + printf("input: %s, output: %s\n", testvec_3, ss_hexdump(output, rc)); +} + +int main(int argc, char **argv) +{ + ss_binary_from_hexstr_test(); + return 0; +} diff --git a/tests/utils/utils_test.ok b/tests/utils/utils_test.ok new file mode 100644 index 0000000..8846adb --- /dev/null +++ b/tests/utils/utils_test.ok @@ -0,0 +1,5 @@ +input: aabbccddeeff00112233445566778899, output: aabbccddeeff00112233445566778899 +input: aabbccddeeff00112233445566778899, output (short): aabbccddeeff00112233 +input: aabbccddeeff001122334455667788991, output: aabbccddeeff00112233445566778899 +input: aabbccddeeff001122334455667788991, output (short): aabbccddeeff00112233 +input: aabbccddeeff001HELLO34455667788991, output: aabbccddeeff00ffffff34455667788991