Skip to content

Commit

Permalink
Implement EMV application selection
Browse files Browse the repository at this point in the history
* emv_tal_select_app() is responsible for selecting the application,
  validating the FCI and ensuring that the AID and DF Name match, in
  accordance with EMV 4.4 Book 1, 12.4
* emv_select_application() is responsible updating the candidate
  application list and determining the outcome of the application
  selection attempt in accordance with EMV 4.4 Book 4, 11.3
  • Loading branch information
leonlynch committed Feb 11, 2024
1 parent f4897fc commit f400c01
Show file tree
Hide file tree
Showing 5 changed files with 264 additions and 67 deletions.
85 changes: 85 additions & 0 deletions src/emv.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
#define EMV_DEBUG_SOURCE EMV_DEBUG_SOURCE_EMV
#include "emv_debug.h"

#include <stddef.h>
#include <stdint.h>
#include <string.h>

const char* emv_lib_version_string(void)
{
return EMV_UTILS_VERSION_STRING;
Expand All @@ -51,6 +55,7 @@ const char* emv_outcome_get_string(enum emv_outcome_t outcome)
case EMV_OUTCOME_CARD_ERROR: return "Card error"; // Message 06
case EMV_OUTCOME_CARD_BLOCKED: return "Card blocked"; // Not in EMV specification
case EMV_OUTCOME_NOT_ACCEPTED: return "Not accepted"; // Message 0C
case EMV_OUTCOME_TRY_AGAIN: return "Try again"; // Message 13
}

return "Invalid outcome";
Expand Down Expand Up @@ -346,3 +351,83 @@ int emv_build_candidate_list(

return 0;
}

int emv_select_application(
struct emv_ttl_t* ttl,
struct emv_app_list_t* app_list,
unsigned int index,
struct emv_app_t** selected_app
)
{
int r;
struct emv_app_t* current_app = NULL;
uint8_t current_aid[16];
size_t current_aid_len;

if (!ttl || !app_list || !selected_app) {
emv_debug_trace_msg("ttl=%p, app_list=%p, index=%u, selected_app=%p", ttl, app_list, index, selected_app);
emv_debug_error("Invalid parameter");
return EMV_ERROR_INVALID_PARAMETER;
}
*selected_app = NULL;

current_app = emv_app_list_remove_index(app_list, index);
if (!current_app) {
return EMV_ERROR_INVALID_PARAMETER;
}

if (current_app->aid->length > sizeof(current_aid)) {
goto try_again;
}
current_aid_len = current_app->aid->length;
memcpy(current_aid, current_app->aid->value, current_app->aid->length);
emv_app_free(current_app);
current_app = NULL;

r = emv_tal_select_app(
ttl,
current_aid,
current_aid_len,
selected_app
);
if (r) {
emv_debug_trace_msg("emv_tal_select_app() failed; r=%d", r);
if (r < 0) {
emv_debug_error("Error during application selection; terminate session");
if (r == EMV_TAL_ERROR_CARD_BLOCKED) {
r = EMV_OUTCOME_CARD_BLOCKED;
} else {
r = EMV_OUTCOME_CARD_ERROR;
}
goto exit;
}
if (r > 0) {
emv_debug_info("Failed to select application; continue session");
goto try_again;
}
}

// Success
r = 0;
goto exit;

try_again:
// If no applications remain, terminate session
// Otherwise, try again
// See EMV 4.4 Book 1, 12.4
// See EMV 4.4 Book 4, 11.3
if (emv_app_list_is_empty(app_list)) {
emv_debug_info("Candidate list empty");
r = EMV_OUTCOME_NOT_ACCEPTED;
} else {
r = EMV_OUTCOME_TRY_AGAIN;
}

exit:
if (current_app) {
emv_app_free(current_app);
current_app = NULL;
}

return r;
}
28 changes: 28 additions & 0 deletions src/emv.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ __BEGIN_DECLS
struct emv_ttl_t;
struct emv_tlv_list_t;
struct emv_app_list_t;
struct emv_app_t;

/**
* EMV errors
Expand All @@ -51,6 +52,7 @@ enum emv_outcome_t {
EMV_OUTCOME_CARD_ERROR = 1, ///< Malfunction of the card or non-conformance to Answer To Reset (ATR)
EMV_OUTCOME_CARD_BLOCKED = 2, ///< Card blocked
EMV_OUTCOME_NOT_ACCEPTED = 3, ///< Card not accepted or no supported applications
EMV_OUTCOME_TRY_AGAIN = 4, ///< Try again by selecting a different application
};

/**
Expand Down Expand Up @@ -110,6 +112,32 @@ int emv_build_candidate_list(
struct emv_app_list_t* app_list
);

/**
* Select EMV application by index from the candidate application list. The
* candidate application list will be updated by removing the selected
* application regardless of processing outcome. If application selection fails
* this function will return either @ref EMV_OUTCOME_NOT_ACCEPTED or
* @ref EMV_OUTCOME_TRY_AGAIN, depending on whether the candidate application
* list is empty or not.
* @remark See EMV 4.4 Book 1, 12.4
* @remark See EMV 4.4 Book 4, 11.3
*
* @param ttl EMV Terminal Transport Layer context
* @param app_list Candidate application list
* @param index Index (starting from zero) of EMV application to select
* @param selected_app Selected EMV application output. Use @ref emv_app_free() to free memory.
*
* @return Zero for success
* @return Less than zero for errors. See @ref emv_error_t
* @return Greater than zero for EMV processing outcome. See @ref emv_outcome_t
*/
int emv_select_application(
struct emv_ttl_t* ttl,
struct emv_app_list_t* app_list,
unsigned int index,
struct emv_app_t** selected_app
);

__END_DECLS

#endif
88 changes: 88 additions & 0 deletions src/emv_tal.c
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,94 @@ int emv_tal_find_supported_apps(
return 0;
}

int emv_tal_select_app(
struct emv_ttl_t* ttl,
const uint8_t* aid,
size_t aid_len,
struct emv_app_t** selected_app
)
{
int r;
uint8_t fci[EMV_RAPDU_DATA_MAX];
size_t fci_len = sizeof(fci);
uint16_t sw1sw2;
struct emv_app_t* app;

if (!ttl || !aid || aid_len < 5 || aid_len > 16 || !selected_app) {
// Invalid parameters; terminate session
return EMV_TAL_ERROR_INVALID_PARAMETER;
}
*selected_app = NULL;

// SELECT application
// See EMV 4.4 Book 1, 12.4
emv_debug_info_data("SELECT application", aid, aid_len);
r = emv_ttl_select_by_df_name(ttl, aid, aid_len, fci, &fci_len, &sw1sw2);
if (r) {
emv_debug_trace_msg("emv_ttl_select_by_df_name() failed; r=%d", r);

// TTL failure; terminate session
// (bad card or reader)
emv_debug_error("TTL failure");
return EMV_TAL_ERROR_TTL_FAILURE;
}

if (sw1sw2 != 0x9000) {
switch (sw1sw2) {
case 0x6A81:
// Card blocked or SELECT not supported; terminate session
emv_debug_error("Card blocked or SELECT not supported");
return EMV_TAL_ERROR_CARD_BLOCKED;

case 0x6A82:
// Application not found; ignore app and continue
emv_debug_info("Application not found");
return EMV_TAL_RESULT_APP_NOT_FOUND;

case 0x6283:
// Application blocked; ignore app and continue
emv_debug_info("Application is blocked");
return EMV_TAL_RESULT_APP_BLOCKED;

default:
// Unknown error; ignore app and continue
// See EMV Book 1, 12.4, regarding status other than 9000
emv_debug_error("SW1SW2=0x%04hX\n", sw1sw2);
return EMV_TAL_RESULT_APP_SELECTION_FAILED;
}
}

emv_debug_info_tlv("FCI", fci, fci_len);

// Parse FCI to confirm that selected application is valid
app = emv_app_create_from_fci(fci, fci_len);
if (!app) {
emv_debug_trace_msg("emv_app_create_from_fci() failed; app=%p", app);

// Failed to parse FCI; ignore app and continue
// See EMV Book 1, 12.4, regarding format errors
emv_debug_error("Failed to parse application FCI");
return EMV_TAL_RESULT_APP_FCI_PARSE_FAILED;
}

// Ensure that the AID used by the SELECT command exactly matches the
// DF Name (field 84) provided by the FCI
// See EMV 4.4 Book 1, 12.4
// NOTE: emv_app_create_from_fci() sets AID to DF Name (field 84)
if (app->aid->length != aid_len ||
memcmp(app->aid->value, aid, aid_len)
) {
emv_debug_error("DF Name mismatch");
emv_app_free(app);
return EMV_TAL_RESULT_APP_SELECTION_FAILED;
}

// Output
*selected_app = app;

return 0;
}

int emv_tal_parse_gpo_response(
const void* buf,
size_t len,
Expand Down
30 changes: 30 additions & 0 deletions src/emv_tal.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@

#include <sys/cdefs.h>
#include <stddef.h>
#include <stdint.h>

__BEGIN_DECLS

// Forward declarations
struct emv_ttl_t;
struct emv_app_list_t;
struct emv_app_t;
struct emv_tlv_list_t;
struct emv_tlv_t;

Expand Down Expand Up @@ -59,6 +61,10 @@ enum emv_tal_result_t {
EMV_TAL_RESULT_PSE_SFI_NOT_FOUND, ///< Failed to find Short File Identifier (SFI) for Payment System Environment (PSE)
EMV_TAL_RESULT_PSE_AEF_PARSE_FAILED, ///< Failed to parse Application Elementary File (AEF) of Payment System Environment (PSE)
EMV_TAL_RESULT_PSE_AEF_INVALID, ///< Invalid Payment System Environment (PSE) Application Elementary File (AEF) record
EMV_TAL_RESULT_APP_NOT_FOUND, ///< Selected application not found
EMV_TAL_RESULT_APP_BLOCKED, ///< Selected application is blocked
EMV_TAL_RESULT_APP_SELECTION_FAILED, ///< Application selection failed
EMV_TAL_RESULT_APP_FCI_PARSE_FAILED, ///< Failed to parse File Control Information (FCI) for selected application
};

/**
Expand Down Expand Up @@ -102,6 +108,30 @@ int emv_tal_find_supported_apps(
struct emv_app_list_t* app_list
);

/**
* SELECT application and validate File Control Information (FCI)
* @remark See EMV 4.4 Book 1, 12.4
*
* @param ttl EMV Terminal Transport Layer context
* @param aid Application Identifier (AID) field. Must be 5 to 16 bytes.
* @param aid_len Length of Application Identifier (AID) field. Must be 5 to 16 bytes.
* @param selected_app Selected EMV application output. Use @ref emv_app_free() to free memory.
*
* @return Zero for success
* @return Less than zero indicates that the terminal should terminate the
* card session. See @ref emv_tal_error_t
* @return Greater than zero indicates that selection or validation of the
* application failed and that the terminal may continue the card
* session. Typically the candidate application list should be
* consulted next for remaining application options.
*/
int emv_tal_select_app(
struct emv_ttl_t* ttl,
const uint8_t* aid,
size_t aid_len,
struct emv_app_t** selected_app
);

/**
* Parse GET PROCESSING OPTIONS response
* @param buf Buffer containing GET PROCESSING OPTIONS response data
Expand Down
Loading

0 comments on commit f400c01

Please sign in to comment.