Skip to content

Commit

Permalink
Implement EMV Read Application Data
Browse files Browse the repository at this point in the history
* emv_tal_read_sfi_records() is responsible for reading the records
  specified for a specific SFI by a single AFL entry. Each record is:
  - validated according to EMV 4.4 Book 3, 6.5.11.4;
  - assessed to determine whether it is suitable for processing during
    offline data authentication, although offline data authentication
    itself has not yet been implemented; and
  - parsed for EMV application data fields
* emv_tal_read_afl_records() is responsible for using the AFL helper
  functions to parse the AFL entries and then invoking
  emv_tal_read_sfi_records() for each AFL entry
* emv_read_application_data() is responsible for finding the AFL field
  in the ICC data, invoking emv_tal_read_afl_records() to read the
  application data, validating that there are no duplicate/redundant
  fields, validating that mandatory fields are present, appending the
  application data to the ICC data, and determining the outcome of the
  processing.
* Various Doxygen and comment updates for EMV 4.4
  • Loading branch information
leonlynch committed Mar 16, 2024
1 parent 55310aa commit ad683b9
Show file tree
Hide file tree
Showing 13 changed files with 1,053 additions and 44 deletions.
100 changes: 100 additions & 0 deletions src/emv.c
Original file line number Diff line number Diff line change
Expand Up @@ -573,3 +573,103 @@ int emv_initiate_application_processing(
exit:
return r;
}

int emv_read_application_data(
struct emv_ttl_t* ttl,
struct emv_tlv_list_t* icc
)
{
int r;
const struct emv_tlv_t* afl;
struct emv_tlv_list_t record_data = EMV_TLV_LIST_INIT;
bool found_5F24 = false;
bool found_5A = false;
bool found_8C = false;
bool found_8D = false;

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

// Process Application File Locator (AFL)
// See EMV 4.4 Book 3, 10.2
afl = emv_tlv_list_find(icc, EMV_TAG_94_APPLICATION_FILE_LOCATOR);
if (!afl) {
// AFL not found; terminate session
// See EMV 4.4 Book 3, 6.5.8.4
emv_debug_error("AFL not found");
return EMV_OUTCOME_CARD_ERROR;
}
r = emv_tal_read_afl_records(ttl, afl->value, afl->length, &record_data);
if (r) {
emv_debug_trace_msg("emv_tal_read_afl_records() failed; r=%d", r);
if (r < 0) {
emv_debug_error("Error reading application data");
if (r == EMV_TAL_ERROR_INTERNAL || r == EMV_TAL_ERROR_INVALID_PARAMETER) {
r = EMV_ERROR_INTERNAL;
} else {
r = EMV_OUTCOME_CARD_ERROR;
}
goto exit;
}
if (r != EMV_TAL_RESULT_ODA_RECORD_INVALID) {
emv_debug_error("Failed to read application data");
r = EMV_OUTCOME_CARD_ERROR;
goto exit;
}
// Continue regardless of offline data authentication failure
// See EMV 4.4 Book 3, 10.3 (page 98)
}

if (emv_tlv_list_has_duplicate(&record_data)) {
// Redundant primitive data objects are not permitted
// See EMV 4.4 Book 3, 10.2
emv_debug_error("Application data contains redundant fields");
r = EMV_OUTCOME_CARD_ERROR;
goto exit;
}

for (const struct emv_tlv_t* tlv = record_data.front; tlv != NULL; tlv = tlv->next) {
// Mandatory data objects
// See EMV 4.4 Book 3, 7.2
if (tlv->tag == EMV_TAG_5F24_APPLICATION_EXPIRATION_DATE) {
found_5F24 = true;
}
if (tlv->tag == EMV_TAG_5A_APPLICATION_PAN) {
found_5A = true;
}
if (tlv->tag == EMV_TAG_8C_CDOL1) {
found_8C = true;
}
if (tlv->tag == EMV_TAG_8D_CDOL2) {
found_8D = true;
}
}
if (!found_5F24 || !found_5A || !found_8C || !found_8D) {
// Mandatory field not found; terminate session
// See EMV 4.4 Book 3, 10.2
emv_debug_error("Mandatory field not found");
r = EMV_OUTCOME_CARD_ERROR;
goto exit;
}

r = emv_tlv_list_append(icc, &record_data);
if (r) {
emv_debug_trace_msg("emv_tlv_list_append() failed; r=%d", r);

// Internal error; terminate session
emv_debug_error("Internal error");
r = EMV_TAL_ERROR_INTERNAL;
goto exit;
}

// Success
r = 0;
goto exit;

exit:
emv_tlv_list_clear(&record_data);
return r;
}
21 changes: 21 additions & 0 deletions src/emv.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,27 @@ int emv_initiate_application_processing(
struct emv_tlv_list_t* icc
);

/**
* Read EMV application data by performing READ RECORD for all records
* specified by the Application File Locator (AFL), checking that there are no
* redundant TLV fields provided by the application records, and checking for
* the mandatory fields.
* @note Upon success, this function will append the TLV data to the ICC data
* output
* @remark See EMV 4.4 Book 3, 10.2
*
* @param ttl EMV Terminal Transport Layer context
* @param icc ICC data 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_read_application_data(
struct emv_ttl_t* ttl,
struct emv_tlv_list_t* icc
);

__END_DECLS

#endif
15 changes: 11 additions & 4 deletions src/emv_fields.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @file emv_fields.c
* @brief EMV field helper functions
*
* Copyright (c) 2021, 2022, 2023 Leon Lynch
* Copyright (c) 2021-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 Down Expand Up @@ -332,6 +332,7 @@ int emv_afl_itr_init(const void* afl, size_t afl_len, struct emv_afl_itr_t* itr)

if ((afl_len & 0x3) != 0) {
// Application File Locator (field 94) must be multiples of 4 bytes
// See EMV 4.4 Book 3, 10.2
return 1;
}

Expand Down Expand Up @@ -372,17 +373,23 @@ int emv_afl_itr_next(struct emv_afl_itr_t* itr, struct emv_afl_entry_t* entry)
// Remaining bits of AFL byte 1 must be zero
return -3;
}
if ((afl[0] & EMV_AFL_SFI_MASK) == 0 || (afl[0] & EMV_AFL_SFI_MASK) == EMV_AFL_SFI_MASK) {
// SFI must not be 0 or 31
// See EMV 4.4 Book 3, 7.5
return -4;
}
if (afl[1] == 0) {
// AFL byte 2 must never be zero
return -4;
return -5;
}
if (afl[2] < afl[1]) {
// AFL byte 3 must be greater than or equal to AFL byte 2
return -5;
// See EMV 4.4 Book 3, 7.5
return -6;
}
if (afl[3] > afl[2] - afl[1] + 1) {
// AFL byte 4 must not exceed the number of records indicated by AFL byte 2 and byte 3
return -6;
return -7;
}

// Decode AFL entry
Expand Down
2 changes: 1 addition & 1 deletion src/emv_fields.h
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,7 @@ struct emv_afl_entry_t {
};

/**
* Initialize Application File Locator (AFL) iterator
* Initialise Application File Locator (AFL) iterator
* @param afl Application File Locator (AFL) field. Must be multiples of 4 bytes.
* @param afl_len Length of Application File Locator (AFL) field. Must be multiples of 4 bytes.
* @param itr Application File Locator (AFL) iterator output
Expand Down
Loading

0 comments on commit ad683b9

Please sign in to comment.