lynxis lazus has uploaded this change for review. ( https://gerrit.osmocom.org/c/simtrace2/+/43185?usp=email )
Change subject: Add osmo_apdu_segment_in2() to ensure correct parsing of GP GET DATA ......................................................................
Add osmo_apdu_segment_in2() to ensure correct parsing of GP GET DATA
osmo_apdu_segment_in() don't have the context of the previous APDU, for nearly all calls this is fine, except for GP GET DATA, which can be a case 2 or case 4 APDU. GP GET DATA defines the Le field as 0x00 which is used to detect if this is a case 2 or case 4. But if the card responded to a case 2 GP GET DATA with Le = 0 with a SW 6cXX, the previous GP GET DATA case 2 must set the Le field to XX, which got misinterpreted by the osmo_apdu_segment_in() as a case 4 instead of a case 2 with Le != 0.
Introduce osmo_apdu_segment_in2() containing the previous APDU as context.
Related: SYS#8147 Change-Id: Ie238662e7e6a10dd4283ede0c8d8d73bc375c247 --- M TODO-RELEASE M host/Makefile.am M host/configure.ac M host/include/osmocom/simtrace2/apdu_dispatch.h M host/lib/apdu_dispatch.c A host/tests/apdu_dispatch/Makefile.am A host/tests/apdu_dispatch/apdu_dispatch_test.c A host/tests/apdu_dispatch/apdu_dispatch_test.ok A host/tests/testsuite.at 9 files changed, 156 insertions(+), 1 deletion(-)
git pull ssh://gerrit.osmocom.org:29418/simtrace2 refs/changes/85/43185/1
diff --git a/TODO-RELEASE b/TODO-RELEASE index 0ed7189..5922210 100644 --- a/TODO-RELEASE +++ b/TODO-RELEASE @@ -7,3 +7,4 @@ # If any interfaces have been added since the last public release: c:r:a + 1. # If any interfaces have been removed or changed since the last public release: c:r:0. #library what description / commit summary line +libosmo-simtrace2 added osmo_apdu_segment_in2() diff --git a/host/Makefile.am b/host/Makefile.am index 338a46b..465a4e0 100644 --- a/host/Makefile.am +++ b/host/Makefile.am @@ -1,7 +1,7 @@ AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -SUBDIRS = include lib src contrib #tests examples doc +SUBDIRS = include lib src contrib tests # examples doc
EXTRA_DIST = .version
diff --git a/host/configure.ac b/host/configure.ac index 83a2b23..84b4147 100644 --- a/host/configure.ac +++ b/host/configure.ac @@ -100,4 +100,6 @@ src/Makefile lib/Makefile contrib/Makefile + tests/Makefile + tests/apdu_dispatch/Makefile Makefile) diff --git a/host/include/osmocom/simtrace2/apdu_dispatch.h b/host/include/osmocom/simtrace2/apdu_dispatch.h index 62ff762..f3c0ada 100644 --- a/host/include/osmocom/simtrace2/apdu_dispatch.h +++ b/host/include/osmocom/simtrace2/apdu_dispatch.h @@ -45,3 +45,6 @@
int osmo_apdu_segment_in(struct osmo_apdu_context *ac, const uint8_t *apdu_buf, unsigned int apdu_len, bool new_apdu); + +int osmo_apdu_segment_in2(struct osmo_apdu_context *ac, struct osmo_apdu_context *prev_ac, + const uint8_t *apdu_buf, unsigned int apdu_len, bool new_apdu); diff --git a/host/lib/apdu_dispatch.c b/host/lib/apdu_dispatch.c index 4c8f505..d504255 100644 --- a/host/lib/apdu_dispatch.c +++ b/host/lib/apdu_dispatch.c @@ -78,14 +78,54 @@ int osmo_apdu_segment_in(struct osmo_apdu_context *ac, const uint8_t *apdu_buf, unsigned int apdu_len, bool new_apdu) { + return osmo_apdu_segment_in2(ac, NULL, apdu_buf, apdu_len, new_apdu); +} + +/*! \brief input function for APDU segmentation + * \param ac APDU context accross successive calls + * \param prev_ac Previous APDU context accross successive calls + * \param[in] apdu_buf APDU input data buffer + * \param[in] apdu_len Length of apdu_buf + * \param[in] new_apdu Is this the beginning of a new APDU? + * + * The function returns APDU_ACT_TX_CAPDU_TO_CARD once there is + * sufficient data of the APDU received to transmit the command-APDU to + * the actual card. + * + * The function returns APDU_ACT_RX_MORE_CAPDU_FROM_READER when there + * is more data to be received from the card reader (GSM Phone). + */ +int osmo_apdu_segment_in2(struct osmo_apdu_context *ac, struct osmo_apdu_context *prev_ac, + const uint8_t *apdu_buf, unsigned int apdu_len, + bool new_apdu) +{ int rc = 0;
if (new_apdu) { + if (prev_ac) + memcpy(prev_ac, ac, sizeof(*ac)); /* initialize the apdu context structure */ memset(ac, 0, sizeof(*ac)); /* copy APDU header over */ memcpy(&ac->hdr, apdu_buf, sizeof(ac->hdr)); ac->apdu_case = osim_determine_apdu_case(&osim_uicc_sim_cic_profile, apdu_buf); + + /* Corner case in GP when card returns SW 6CXX. + * Most of GP defines Le to be 0x00, except if card returned SW 6cXX, + * Le must be set to XX. osim_determine_apdu_case() might return the wrong + * APDU case because osim_determine_apdu_case() uses the Le field to determine if the + * GP GET DATA is a case 2 or 4 which fails when Le != 0. + */ + if (prev_ac && prev_ac->sw[0] == 0x6c && /* check if Le was invalid and must be set to sw[1] */ + ac->hdr.cla & 0x80 && /* check for GP */ + ((ac->hdr.ins & 0xca) == 0xca) && /* check for GET DATA */ + ac->hdr.cla == prev_ac->hdr.cla && /* check if CLA, INS, P1, P2 are equal */ + ac->hdr.ins == prev_ac->hdr.ins && + ac->hdr.p1 == prev_ac->hdr.p1 && + ac->hdr.p2 == prev_ac->hdr.p2) { + ac->apdu_case = prev_ac->apdu_case; + } + switch (ac->apdu_case) { case 1: /* P3 == 0, No Lc/Le */ ac->le.tot = ac->lc.tot = 0; diff --git a/host/tests/apdu_dispatch/Makefile.am b/host/tests/apdu_dispatch/Makefile.am new file mode 100644 index 0000000..06a07b5 --- /dev/null +++ b/host/tests/apdu_dispatch/Makefile.am @@ -0,0 +1,13 @@ +AM_LDFLAGS = -no-install +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS=-Wall $(LIBOSMONETIF_CFLAGS) $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) +LDADD = $(top_builddir)/lib/.libs/libosmo-simtrace2.la \ + $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMONETIF_LIBS) $(LIBSCTP_LIBS) + +EXTRA_DIST = \ + apdu_dispatch_test.ok \ + $(NULL) + +check_PROGRAMS = apdu_dispatch_test + +apdu_dispatch_test_SOURCES = apdu_dispatch_test.c diff --git a/host/tests/apdu_dispatch/apdu_dispatch_test.c b/host/tests/apdu_dispatch/apdu_dispatch_test.c new file mode 100644 index 0000000..76b1ddd --- /dev/null +++ b/host/tests/apdu_dispatch/apdu_dispatch_test.c @@ -0,0 +1,80 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> + +#include <osmocom/simtrace2/apdu_dispatch.h> + +const uint8_t get_data_c2_ca[] = { 0x81, 0xCA, 0x00, 0x5A, 0x00 }; +const uint8_t get_data_c2_cb[] = { 0x81, 0xCB, 0x00, 0x5A, 0x00 }; + +/* teset data for 6cXX test */ +const uint8_t get_data_c2_ca_le_23[] = { 0x81, 0xCA, 0x00, 0x5A, 0x23 }; +const uint8_t get_data_c2_cb_le_23[] = { 0x81, 0xCb, 0x00, 0x5A, 0x23 }; + +#define APDU_SEGMENT_IN(apdu, exp_rc) \ + do { \ + memset(&ac, 0, sizeof(ac)); \ + memset(&prev_ac, 0, sizeof(prev_ac)); \ + printf("Testing " #apdu "\n"); \ + int rc = osmo_apdu_segment_in(&ac, apdu, ARRAY_SIZE(apdu), 1); \ + if (rc != exp_rc) \ + printf("%d (actual) != %d (intended)\n", rc, exp_rc);\ + OSMO_ASSERT(rc == exp_rc); \ + } while (0) + +#define APDU_SEGMENT_IN2(apdu, exp_rc) \ + do { \ + memset(&ac, 0, sizeof(ac)); \ + memset(&prev_ac, 0, sizeof(prev_ac)); \ + printf("Testing " #apdu "\n"); \ + int rc = osmo_apdu_segment_in2(&ac, &prev_ac, apdu, ARRAY_SIZE(apdu), 1); \ + if (rc != exp_rc) \ + printf("%d (actual) != %d (intended)\n", rc, exp_rc);\ + OSMO_ASSERT(rc == exp_rc); \ + } while (0) + +void test_apdu_dispatch_simple(void) +{ + struct osmo_apdu_context ac, prev_ac; + APDU_SEGMENT_IN(get_data_c2_ca, APDU_ACT_TX_CAPDU_TO_CARD); + APDU_SEGMENT_IN(get_data_c2_cb, APDU_ACT_TX_CAPDU_TO_CARD); + + APDU_SEGMENT_IN2(get_data_c2_ca, APDU_ACT_TX_CAPDU_TO_CARD); + APDU_SEGMENT_IN2(get_data_c2_cb, APDU_ACT_TX_CAPDU_TO_CARD); +} + +void test_apdu_dispatch_context(void) +{ + struct osmo_apdu_context ac, prev_ac; + int rc; + + printf("Testing GET DATA / 0xCA with SW 6Cxx\n"); + memset(&ac, 0, sizeof(ac)); + memset(&prev_ac, 0, sizeof(prev_ac)); + rc = osmo_apdu_segment_in2(&ac, &prev_ac, get_data_c2_ca, ARRAY_SIZE(get_data_c2_ca), 1); + OSMO_ASSERT(rc == APDU_ACT_TX_CAPDU_TO_CARD); + ac.sw[0] = 0x6c; + ac.sw[1] = 0x23; + rc = osmo_apdu_segment_in2(&ac, &prev_ac, get_data_c2_ca_le_23, ARRAY_SIZE(get_data_c2_ca_le_23), 1); + OSMO_ASSERT(rc == APDU_ACT_TX_CAPDU_TO_CARD); + + printf("Testing GET DATA / 0xCB with SW 6Cxx\n"); + memset(&ac, 0, sizeof(ac)); + memset(&prev_ac, 0, sizeof(prev_ac)); + rc = osmo_apdu_segment_in2(&ac, &prev_ac, get_data_c2_cb, ARRAY_SIZE(get_data_c2_cb), 1); + OSMO_ASSERT(rc == APDU_ACT_TX_CAPDU_TO_CARD); + ac.sw[0] = 0x6c; + ac.sw[1] = 0x23; + rc = osmo_apdu_segment_in2(&ac, &prev_ac, get_data_c2_cb_le_23, ARRAY_SIZE(get_data_c2_cb_le_23), 1); + OSMO_ASSERT(rc == APDU_ACT_TX_CAPDU_TO_CARD); +} + +int main(int argc, char **argv) +{ + test_apdu_dispatch_simple(); + test_apdu_dispatch_context(); + + printf("All tests passed.\n"); + return 0; +} diff --git a/host/tests/apdu_dispatch/apdu_dispatch_test.ok b/host/tests/apdu_dispatch/apdu_dispatch_test.ok new file mode 100644 index 0000000..108450f --- /dev/null +++ b/host/tests/apdu_dispatch/apdu_dispatch_test.ok @@ -0,0 +1,7 @@ +Testing get_data_c2_ca +Testing get_data_c2_cb +Testing get_data_c2_ca +Testing get_data_c2_cb +Testing GET DATA / 0xCA with SW 6Cxx +Testing GET DATA / 0xCB with SW 6Cxx +All tests passed. diff --git a/host/tests/testsuite.at b/host/tests/testsuite.at new file mode 100644 index 0000000..22fcdc4 --- /dev/null +++ b/host/tests/testsuite.at @@ -0,0 +1,9 @@ +AT_INIT +AT_BANNER([Regression tests.]) + +AT_SETUP([apdu_dispatch]) +AT_KEYWORDS([apdu_dispatch]) +cat $abs_srcdir/apdu_dispatch/apdu_dispatch_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/apdu_dispatch/apdu_dispatch_test], [], [expout], [ignore]) +AT_CLEANUP +