Skip to content

Commit

Permalink
Implement error and outcome enums
Browse files Browse the repository at this point in the history
* Error enum should always be less than zero and the associated error
  string is intended for developer use only
* Outcome enums should always be greater than zero and the associated
  outcome string is intended to be one of the standard messages in
  either EMV 4.4 Book 4, 11.2 or EMV Contactless Book A v2.10, 9.4
* The primary reason for creating separate enum types is such that the
  errors and outcomes can be used entirely separately in future, for
  example when the errors are function return values while the outcomes
  form part of a separate transaction output data set
  • Loading branch information
leonlynch committed Apr 9, 2023
1 parent f611fac commit 23bacea
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 25 deletions.
72 changes: 52 additions & 20 deletions src/emv.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,50 @@ const char* emv_lib_version_string(void)
return EMV_UTILS_VERSION_STRING;
}

const char* emv_error_get_string(enum emv_error_t error)
{
switch (error) {
case EMV_ERROR_INTERNAL: return "Internal error";
case EMV_ERROR_INVALID_PARAMETER: return "Invalid function parameter";
}

return "Unknown error";
}

const char* emv_outcome_get_string(enum emv_outcome_t outcome)
{
switch (outcome) {
case EMV_OUTCOME_CARD_ERROR: return "Card error";
}

return "Invalid outcome";
}

int emv_atr_parse(const void* atr, size_t atr_len)
{
int r;
struct iso7816_atr_info_t atr_info;
unsigned int TD1_protocol = 0; // Default is T=0
unsigned int TD2_protocol = 0; // Default is T=0

if (!atr || !atr_len) {
emv_debug_trace_msg("atr=%p, atr_len=%zu", atr, atr_len);
emv_debug_error("Invalid parameter");
return EMV_ERROR_INVALID_PARAMETER;
}

r = iso7816_atr_parse(atr, atr_len, &atr_info);
if (r) {
emv_debug_trace_msg("iso7816_atr_parse() failed; r=%d", r);
emv_debug_error("Failed to parse ATR");
return r;

if (r < 0) {
emv_debug_error("Internal error");
return EMV_ERROR_INTERNAL;
}
if (r > 0) {
emv_debug_error("Failed to parse ATR");
return EMV_OUTCOME_CARD_ERROR;
}
}

// The intention of this function is to validate the ATR in accordance with
Expand All @@ -69,20 +101,20 @@ int emv_atr_parse(const void* atr, size_t atr_len)
(*atr_info.TA[1] < 0x11 || *atr_info.TA[1] > 0x13) // TA1 must be in the range 0x11 to 0x13
) {
emv_debug_error("TA2 indicates specific mode but TA1 is invalid");
return 1;
return EMV_OUTCOME_CARD_ERROR;
}

if (!atr_info.TA[2]) { // TA2 is absent
// Max frequency must be at least 5 MHz
if ((*atr_info.TA[1] & ISO7816_ATR_TA1_FI_MASK) == 0) {
emv_debug_error("TA2 indicates negotiable mode but TA1 is invalid");
return 1;
return EMV_OUTCOME_CARD_ERROR;
}

// Baud rate adjustment factor must be at least 4
if ((*atr_info.TA[1] & ISO7816_ATR_TA1_DI_MASK) < 3) {
emv_debug_error("TA2 indicates negotiable mode but TA1 is invalid");
return 1;
return EMV_OUTCOME_CARD_ERROR;
}
}
}
Expand All @@ -97,7 +129,7 @@ int emv_atr_parse(const void* atr, size_t atr_len)
// TC1 must be either 0x00 or 0xFF
if (*atr_info.TC[1] != 0x00 && *atr_info.TC[1] != 0xFF) {
emv_debug_error("TC1 is invalid");
return 1;
return EMV_OUTCOME_CARD_ERROR;
}
}

Expand All @@ -107,7 +139,7 @@ int emv_atr_parse(const void* atr, size_t atr_len)
// TD1 protocol type must be T=0 or T=1
if ((*atr_info.TD[1] & ISO7816_ATR_Tx_OTHER_MASK) > ISO7816_PROTOCOL_T1) {
emv_debug_error("TD1 protocol is invalid");
return 1;
return EMV_OUTCOME_CARD_ERROR;
}
TD1_protocol = *atr_info.TD[1] & ISO7816_ATR_Tx_OTHER_MASK;
}
Expand All @@ -121,13 +153,13 @@ int emv_atr_parse(const void* atr, size_t atr_len)
TA2_protocol = *atr_info.TA[2] & ISO7816_ATR_TA2_PROTOCOL_MASK;
if (TA2_protocol != TD1_protocol) {
emv_debug_error("TA2 protocol differs from TD1 protocol");
return 1;
return EMV_OUTCOME_CARD_ERROR;
}

// TA2 must indicate specific mode, not implicit mode
if (*atr_info.TA[2] & ISO7816_ATR_TA2_IMPLICIT) {
emv_debug_error("TA2 implicit mode is invalid");
return 1;
return EMV_OUTCOME_CARD_ERROR;
}
}

Expand All @@ -141,13 +173,13 @@ int emv_atr_parse(const void* atr, size_t atr_len)
// TC2 is specific to T=0
if (TD1_protocol != ISO7816_PROTOCOL_T0) {
emv_debug_error("TC2 is not allowed when protocol is not T=0");
return 1;
return EMV_OUTCOME_CARD_ERROR;
}

// TC2 for T=0 must be 0x0A
if (*atr_info.TC[2] != 0x0A) {
emv_debug_error("TC2 for T=0 is invalid");
return 1;
return EMV_OUTCOME_CARD_ERROR;
}
}

Expand All @@ -159,15 +191,15 @@ int emv_atr_parse(const void* atr, size_t atr_len)
(*atr_info.TD[2] & ISO7816_ATR_Tx_OTHER_MASK) != ISO7816_PROTOCOL_T15
) {
emv_debug_error("TD2 protocol is invalid");
return 1;
return EMV_OUTCOME_CARD_ERROR;
}

// TD2 protocol type must be T=1 if TD1 protocol type was T=1
if (TD1_protocol == ISO7816_PROTOCOL_T1 &&
(*atr_info.TD[2] & ISO7816_ATR_Tx_OTHER_MASK) != ISO7816_PROTOCOL_T1
) {
emv_debug_error("TD2 protocol is invalid");
return 1;
return EMV_OUTCOME_CARD_ERROR;
}

TD2_protocol = *atr_info.TD[2] & ISO7816_ATR_Tx_OTHER_MASK;
Expand All @@ -177,7 +209,7 @@ int emv_atr_parse(const void* atr, size_t atr_len)
// See EMV Level 1 Contact Interface v1.0, 8.3.3.10
if (TD1_protocol == ISO7816_PROTOCOL_T1) {
emv_debug_error("TD2 for T=1 is absent");
return 1;
return EMV_OUTCOME_CARD_ERROR;
}
}

Expand All @@ -190,7 +222,7 @@ int emv_atr_parse(const void* atr, size_t atr_len)
// iso7816_atr_parse() already rejects 0xFF
if (*atr_info.TA[3] < 0x10) {
emv_debug_error("TA3 for T=1 is invalid");
return 1;
return EMV_OUTCOME_CARD_ERROR;
}
}

Expand All @@ -200,13 +232,13 @@ int emv_atr_parse(const void* atr, size_t atr_len)
// TB3 for T=1 BWI must be 4 or less
if (((*atr_info.TB[3] & ISO7816_ATR_TBi_BWI_MASK) >> ISO7816_ATR_TBi_BWI_SHIFT) > 4) {
emv_debug_error("TB3 for T=1 has invalid BWI");
return 1;
return EMV_OUTCOME_CARD_ERROR;
}

// TB3 for T=1 CWI must be 5 or less
if ((*atr_info.TB[3] & ISO7816_ATR_TBi_CWI_MASK) > 5) {
emv_debug_error("TB3 for T=1 has invalid CWI");
return 1;
return EMV_OUTCOME_CARD_ERROR;
}

// For T=1, reject 2^CWI < (N + 1)
Expand All @@ -218,12 +250,12 @@ int emv_atr_parse(const void* atr, size_t atr_len)
unsigned int pow_2_CWI = 1 << CWI;
if (pow_2_CWI < (N + 1)) {
emv_debug_error("2^CWI < (N + 1) for T=1 is not allowed");
return 1;
return EMV_OUTCOME_CARD_ERROR;
}

} else { // TB3 is absent
emv_debug_error("TB3 for T=1 is absent");
return 1;
return EMV_OUTCOME_CARD_ERROR;
}

// TC3 - Interface Character
Expand All @@ -232,7 +264,7 @@ int emv_atr_parse(const void* atr, size_t atr_len)
// TC for T=1 must be 0x00
if (*atr_info.TC[3] != 0x00) {
emv_debug_error("TC3 for T=1 is invalid");
return 1;
return EMV_OUTCOME_CARD_ERROR;
}
}
}
Expand Down
40 changes: 38 additions & 2 deletions src/emv.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,48 @@

__BEGIN_DECLS

/**
* EMV errors
* These are typically for internal errors and errors caused by invalid use of
* the API functions in this header, and must have values less than zero.
*/
enum emv_error_t {
EMV_ERROR_INTERNAL = -1, ///< Internal error
EMV_ERROR_INVALID_PARAMETER = -2, ///< Invalid function parameter
};

/**
* EMV processing outcomes
* These indicate the EMV processing outcome, if any, and must have values
* greater than zero.
*/
enum emv_outcome_t {
EMV_OUTCOME_CARD_ERROR = 1, ///< Malfunction of the card or non-conformance to Answer To Reset (ATR)
};

/**
* Retrieve EMV library version string
* @return Pointer to null-terminated string. Do not free.
*/
const char* emv_lib_version_string(void);

/**
* Retrieve string associated with error value
* @param error Error value. Must be less than zero.
* @return Pointer to null-terminated string. Do not free.
*/
const char* emv_error_get_string(enum emv_error_t error);

/**
* Retrieve string associated with outcome value
* @remark See EMV 4.4 Book 4, 11.2
* @remark See EMV Contactless Book A v2.10, 9.4
*
* @param outcome Outcome value. Must be greater than zero.
* @return Pointer to null-terminated string. Do not free.
*/
const char* emv_outcome_get_string(enum emv_outcome_t outcome);

/**
* Parse the ISO 7816 Answer To Reset (ATR) message and determine whether the
* card is suitable for EMV processing
Expand All @@ -42,8 +78,8 @@ const char* emv_lib_version_string(void);
* @param atr_len Length of ATR data
*
* @return Zero for success
* @return Less than zero for internal error
* @return Greater than zero for parse error or card is not accepted
* @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_atr_parse(const void* atr, size_t atr_len);

Expand Down
9 changes: 6 additions & 3 deletions tools/emv-tool.c
Original file line number Diff line number Diff line change
Expand Up @@ -502,9 +502,12 @@ int main(int argc, char** argv)
print_atr(&atr_info);

r = emv_atr_parse(atr, atr_len);
if (r) {
// See EMV 4.4 Book 4, 11.2, Standard Messages
printf("Card error\n");
if (r < 0) {
printf("ERROR: %s\n", emv_error_get_string(r));
goto pcsc_exit;
}
if (r > 0) {
printf("OUTCOME: %s\n", emv_outcome_get_string(r));
goto pcsc_exit;
}

Expand Down

0 comments on commit 23bacea

Please sign in to comment.