diff --git a/src/emv.c b/src/emv.c index f04ab31..534c809 100644 --- a/src/emv.c +++ b/src/emv.c @@ -335,5 +335,14 @@ int emv_build_candidate_list( return EMV_OUTCOME_NOT_ACCEPTED; } + // Sort application list according to priority + // See EMV 4.4 Book 1, 12.4, step 4 + r = emv_app_list_sort_priority(app_list); + if (r) { + emv_debug_trace_msg("emv_app_list_sort_priority() failed; r=%d", r); + emv_debug_error("Failed to sort application list; terminate session"); + return EMV_ERROR_INTERNAL; + } + return 0; } diff --git a/src/emv.h b/src/emv.h index a230396..1ac256d 100644 --- a/src/emv.h +++ b/src/emv.h @@ -92,7 +92,8 @@ int emv_atr_parse(const void* atr, size_t atr_len); /** * Build candidate application list using Payment System Environment (PSE) or - * discovery of supported AIDs + * discovery of supported AIDs, and then sort according to Application Priority + * Indicator * @remark See EMV 4.4 Book 1, 12.3 * * @param ttl EMV Terminal Transport Layer context diff --git a/src/emv_app.c b/src/emv_app.c index f7e37f6..e792b5d 100644 --- a/src/emv_app.c +++ b/src/emv_app.c @@ -407,3 +407,103 @@ struct emv_app_t* emv_app_list_pop(struct emv_app_list_t* list) return app; } + +static int emv_app_list_insert( + struct emv_app_list_t* list, + struct emv_app_t* pos, + struct emv_app_t* app +) +{ + if (!emv_app_list_is_valid(list)) { + return -1; + } + if (!pos) { + return -2; + } + if (!app) { + return -3; + } + + if (list->back == pos) { + // Insert at back of list + return emv_app_list_push(list, app); + } else if (pos->next) { + // Insert in middle of list + app->next = pos->next; + pos->next = app; + } else { + // Invalid list or position + return -4; + } + + return 0; +} + +static int emv_app_list_push_front( + struct emv_app_list_t* list, + struct emv_app_t* app +) +{ + if (!emv_app_list_is_valid(list)) { + return -1; + } + if (!app) { + return -2; + } + + if (list->front) { + app->next = list->front; + list->front = app; + } else { + list->front = app; + list->back = app; + } + + return 0; +} + +int emv_app_list_sort_priority(struct emv_app_list_t* list) +{ + int r; + struct emv_app_t* app; + struct emv_app_list_t sorted_list = EMV_APP_LIST_INIT; + + if (!emv_app_list_is_valid(list)) { + return -1; + } + + // Given that the app list should be very short, an insertion sort is a + // reasonable approach + while ((app = emv_app_list_pop(list))) { + struct emv_app_t* pos = NULL; + + // Value of 1 is the highest priority + // See EMV 4.4 Book 1, 12.2.3, table 13 + // However, the EMV specification does not state how an application + // without a priority indicator should be prioritised relative to an + // application with a priority indicator, and therefore this + // implementation chooses to favour applications with a priority + // indicator over those without. + for (struct emv_app_t* cur = sorted_list.front; cur != NULL; cur = cur->next) { + if (!cur->priority) { + break; + } + if (app->priority && app->priority < cur->priority) { + break; + } + pos = cur; + } + if (pos) { + r = emv_app_list_insert(&sorted_list, pos, app); + } else { + r = emv_app_list_push_front(&sorted_list, app); + } + if (r) { + emv_app_list_clear(&sorted_list); + return -2; + } + } + + *list = sorted_list; + return 0; +} diff --git a/src/emv_app.h b/src/emv_app.h index 48ec993..a4cefe6 100644 --- a/src/emv_app.h +++ b/src/emv_app.h @@ -167,6 +167,13 @@ int emv_app_list_push(struct emv_app_list_t* list, struct emv_app_t* app); */ struct emv_app_t* emv_app_list_pop(struct emv_app_list_t* list); +/** + * Sort EMV application list according to the priority field + * @param list EMV application list + * @return Zero for success. Less than zero for error. + */ +int emv_app_list_sort_priority(struct emv_app_list_t* list); + __END_DECLS #endif diff --git a/tests/emv_build_candidate_list_test.c b/tests/emv_build_candidate_list_test.c index aa6836c..4c66166 100644 --- a/tests/emv_build_candidate_list_test.c +++ b/tests/emv_build_candidate_list_test.c @@ -28,6 +28,7 @@ #include "emv_fields.h" #include +#include // For debug output #include "emv_debug.h" @@ -249,6 +250,34 @@ static const struct xpdu_t test_aid_multi_app_supported[] = { { 0 } }; +static const struct xpdu_t test_sorted_app_priority[] = { + { + 20, (uint8_t[]){ 0x00, 0xA4, 0x04, 0x00, 0x0E, 0x31, 0x50, 0x41, 0x59, 0x2E, 0x53, 0x59, 0x53, 0x2E, 0x44, 0x44, 0x46, 0x30, 0x31, 0x00 }, // SELECT 1PAY.SYS.DDF01 + 36, (uint8_t[]){ 0x6F, 0x20, 0x84, 0x0E, 0x31, 0x50, 0x41, 0x59, 0x2E, 0x53, 0x59, 0x53, 0x2E, 0x44, 0x44, 0x46, 0x30, 0x31, 0xA5, 0x0E, 0x88, 0x01, 0x01, 0x5F, 0x2D, 0x04, 0x6E, 0x6C, 0x65, 0x6E, 0x9F, 0x11, 0x01, 0x01, 0x90, 0x00 }, // FCI + }, + { + 5, (uint8_t[]){ 0x00, 0xB2, 0x01, 0x0C, 0x00 }, // READ RECORD 1,1 + 72, (uint8_t[]){ 0x70, 0x44, 0x61, 0x20, 0x4F, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x05, 0x50, 0x05, 0x41, 0x50, 0x50, 0x20, 0x35, 0x87, 0x01, 0x05, 0x73, 0x0B, 0x9F, 0x0A, 0x08, 0x00, 0x01, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x61, 0x20, 0x4F, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x03, 0x50, 0x05, 0x41, 0x50, 0x50, 0x20, 0x33, 0x87, 0x01, 0x04, 0x73, 0x0B, 0x9F, 0x0A, 0x08, 0x00, 0x01, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00 }, // AEF + }, + { + 5, (uint8_t[]){ 0x00, 0xB2, 0x02, 0x0C, 0x00 }, // READ RECORD 1,2 + 58, (uint8_t[]){ 0x70, 0x36, 0x61, 0x19, 0x4F, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x05, 0x10, 0x10, 0x50, 0x05, 0x41, 0x50, 0x50, 0x20, 0x38, 0x73, 0x07, 0x9F, 0x0A, 0x04, 0x00, 0x01, 0x01, 0x04, 0x61, 0x19, 0x4F, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x04, 0x10, 0x10, 0x50, 0x05, 0x41, 0x50, 0x50, 0x20, 0x37, 0x73, 0x07, 0x9F, 0x0A, 0x04, 0x00, 0x01, 0x01, 0x04, 0x90, 0x00 }, // AEF without application priority indicator, of which one AID is not supported + }, + { + 5, (uint8_t[]){ 0x00, 0xB2, 0x03, 0x0C, 0x00 }, // READ RECORD 1,3 + 72, (uint8_t[]){ 0x70, 0x44, 0x61, 0x20, 0x4F, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x01, 0x50, 0x05, 0x41, 0x50, 0x50, 0x20, 0x31, 0x87, 0x01, 0x01, 0x73, 0x0B, 0x9F, 0x0A, 0x08, 0x00, 0x01, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x61, 0x20, 0x4F, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x06, 0x50, 0x05, 0x41, 0x50, 0x50, 0x20, 0x36, 0x87, 0x01, 0x07, 0x73, 0x0B, 0x9F, 0x0A, 0x08, 0x00, 0x01, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00 }, // AEF + }, + { + 5, (uint8_t[]){ 0x00, 0xB2, 0x04, 0x0C, 0x00 }, // READ RECORD 1,4 + 72, (uint8_t[]){ 0x70, 0x44, 0x61, 0x20, 0x4F, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x02, 0x50, 0x05, 0x41, 0x50, 0x50, 0x20, 0x32, 0x87, 0x01, 0x01, 0x73, 0x0B, 0x9F, 0x0A, 0x08, 0x00, 0x01, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x61, 0x20, 0x4F, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x04, 0x50, 0x05, 0x41, 0x50, 0x50, 0x20, 0x34, 0x87, 0x01, 0x04, 0x73, 0x0B, 0x9F, 0x0A, 0x08, 0x00, 0x01, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00 }, // AEF + }, + { + 5, (uint8_t[]){ 0x00, 0xB2, 0x05, 0x0C, 0x00 }, // READ RECORD 1,5 + 2, (uint8_t[]){ 0x6A, 0x83 }, // Record not found + }, + { 0 } +}; + int main(void) { int r; @@ -256,6 +285,7 @@ int main(void) struct emv_ttl_t ttl; struct emv_tlv_list_t supported_aids = EMV_TLV_LIST_INIT; struct emv_app_list_t app_list = EMV_APP_LIST_INIT; + size_t app_count; ttl.cardreader.mode = EMV_CARDREADER_MODE_APDU; ttl.cardreader.ctx = &emul_ctx; @@ -539,6 +569,46 @@ int main(void) } printf("Success\n"); + printf("\nTesting sorted app priority...\n"); + emul_ctx.xpdu_list = test_sorted_app_priority; + emul_ctx.xpdu_current = NULL; + emv_app_list_clear(&app_list); + r = emv_build_candidate_list( + &ttl, + &supported_aids, + &app_list + ); + if (r) { + fprintf(stderr, "Unexpected emv_build_candidate_list() result; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r)); + r = 1; + goto exit; + } + if (emul_ctx.xpdu_current->c_xpdu_len != 0) { + fprintf(stderr, "Incomplete card interaction\n"); + r = 1; + goto exit; + } + if (emv_app_list_is_empty(&app_list)) { + fprintf(stderr, "Candidate list unexpectedly empty\n"); + r = 1; + goto exit; + } + app_count = 0; + for (struct emv_app_t* app = app_list.front; app != NULL; app = app->next) { + print_emv_app(app); + ++app_count; + + // Use application display name to validate sorted app order + char tmp[] = "APP x"; + tmp[4] = '0' + app_count; + if (strcmp(tmp, app->display_name) != 0) { + fprintf(stderr, "Invalid candidate list order\n"); + r = 1; + goto exit; + } + } + printf("Success\n"); + // Success r = 0; goto exit;