Skip to content

Commit

Permalink
Implement EMV candidate list discovery
Browse files Browse the repository at this point in the history
The emv_build_candidate_list() function uses emv_tal_read_pse() and
emv_tal_find_supported_apps() to build the candidate list as described
by EMV 4.4 Book 1, 12.3. The unit test data was created from the steps
in the EMV specification as well as from real card interactions.
  • Loading branch information
leonlynch committed Jan 21, 2024
1 parent 72a6a2a commit 87c5324
Show file tree
Hide file tree
Showing 7 changed files with 782 additions and 21 deletions.
65 changes: 63 additions & 2 deletions src/emv.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @file emv.c
* @brief High level EMV library interface
*
* Copyright (c) 2023 Leon Lynch
* Copyright (c) 2023-2024 Leon Lynch
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
Expand All @@ -21,6 +21,8 @@

#include "emv.h"
#include "emv_utils_config.h"
#include "emv_tal.h"
#include "emv_app.h"

#include "iso7816.h"

Expand All @@ -44,8 +46,11 @@ const char* emv_error_get_string(enum emv_error_t error)

const char* emv_outcome_get_string(enum emv_outcome_t outcome)
{
// See EMV 4.4 Book 4, 11.2, table 8
switch (outcome) {
case EMV_OUTCOME_CARD_ERROR: return "Card error";
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
}

return "Invalid outcome";
Expand Down Expand Up @@ -276,3 +281,59 @@ int emv_atr_parse(const void* atr, size_t atr_len)

return 0;
}

int emv_build_candidate_list(
struct emv_ttl_t* ttl,
const struct emv_tlv_list_t* supported_aids,
struct emv_app_list_t* app_list
)
{
int r;

if (!ttl || !supported_aids || !app_list) {
emv_debug_trace_msg("ttl=%p, supported_aids=%p, app_list=%p", ttl, supported_aids, app_list);
emv_debug_error("Invalid parameter");
return EMV_ERROR_INVALID_PARAMETER;
}

emv_debug_info("SELECT Payment System Environment (PSE)");
r = emv_tal_read_pse(ttl, supported_aids, app_list);
if (r < 0) {
emv_debug_trace_msg("emv_tal_read_pse() failed; r=%d", r);
emv_debug_error("Failed to read PSE; terminate session");
if (r == EMV_TAL_ERROR_CARD_BLOCKED) {
return EMV_OUTCOME_CARD_BLOCKED;
} else {
return EMV_OUTCOME_CARD_ERROR;
}
}
if (r > 0) {
emv_debug_trace_msg("emv_tal_read_pse() failed; r=%d", r);
emv_debug_info("Failed to process PSE; continue session");
}

// If PSE failed or no apps found by PSE, use list of AIDs method
// See EMV 4.4 Book 1, 12.3.2, step 5
if (emv_app_list_is_empty(app_list)) {
emv_debug_info("Discover list of AIDs");
r = emv_tal_find_supported_apps(ttl, supported_aids, app_list);
if (r) {
emv_debug_trace_msg("emv_tal_find_supported_apps() failed; r=%d", r);
emv_debug_error("Failed to find supported AIDs; terminate session");
if (r == EMV_TAL_ERROR_CARD_BLOCKED) {
return EMV_OUTCOME_CARD_BLOCKED;
} else {
return EMV_OUTCOME_CARD_ERROR;
}
}
}

// If there are no mutually supported applications, terminate session
// See EMV 4.4 Book 1, 12.4, step 1
if (emv_app_list_is_empty(app_list)) {
emv_debug_info("Candidate list empty");
return EMV_OUTCOME_NOT_ACCEPTED;
}

return 0;
}
28 changes: 27 additions & 1 deletion src/emv.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @file emv.h
* @brief High level EMV library interface
*
* Copyright (c) 2023 Leon Lynch
* Copyright (c) 2023-2024 Leon Lynch
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
Expand All @@ -27,6 +27,11 @@

__BEGIN_DECLS

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

/**
* EMV errors
* These are typically for internal errors and errors caused by invalid use of
Expand All @@ -44,6 +49,8 @@ enum emv_error_t {
*/
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
};

/**
Expand Down Expand Up @@ -83,6 +90,25 @@ const char* emv_outcome_get_string(enum emv_outcome_t outcome);
*/
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
* @remark See EMV 4.4 Book 1, 12.3
*
* @param ttl EMV Terminal Transport Layer context
* @param supported_aids Supported AID (field 9F06) list including ASI flags
* @param app_list Candidate application list output
*
* @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_build_candidate_list(
struct emv_ttl_t* ttl,
const struct emv_tlv_list_t* supported_aids,
struct emv_app_list_t* app_list
);

__END_DECLS

#endif
4 changes: 4 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ if (BUILD_TESTING)
target_link_libraries(emv_cvmlist_test PRIVATE emv)
add_test(emv_cvmlist_test emv_cvmlist_test)

add_executable(emv_build_candidate_list_test emv_build_candidate_list_test.c emv_cardreader_emul.c)
target_link_libraries(emv_build_candidate_list_test PRIVATE print_helpers emv)
add_test(emv_build_candidate_list_test emv_build_candidate_list_test)

add_executable(isocodes_test isocodes_test.c)
find_package(Intl)
if(Intl_FOUND)
Expand Down
Loading

0 comments on commit 87c5324

Please sign in to comment.