Skip to content

Commit

Permalink
Improve EMV Data Object List (DOL) processing
Browse files Browse the repository at this point in the history
* Update comments to EMV 4.4
* Implement truncation according to the field format when the DOL entry
  length is less than the EMV field length
* Implement padding according to the field format when the DOL entry
  length is more than the EMV field length
* Implement emv_tlv_is_terminal_format_n() to determine whether a
  specific EMV tag with source "Terminal" has format 'n'
* Implement testing for DOL processing

Note that for now this implementation focusses on PDOL processing and
therefore only assess EMV fields with source "Terminal" when determining
whether they are format 'n'. In future, when CDOL1/CDOL2, TDOL and DDOL
processing is implemented, this implementation will be updated
accordingly.
  • Loading branch information
leonlynch committed Feb 18, 2024
1 parent 429a01f commit 0bfc384
Show file tree
Hide file tree
Showing 6 changed files with 428 additions and 32 deletions.
54 changes: 44 additions & 10 deletions src/emv_dol.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @file emv_dol.c
* @brief EMV Data Object List processing functions
*
* Copyright (c) 2021 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 @@ -45,7 +45,7 @@ int emv_dol_decode(const void* ptr, size_t len, struct emv_dol_entry_t* entry)
return -2;
}

// NOTE: According to EMV 4.3 Book 3, 5.4, a Data Object List (DOL) entry
// NOTE: According to EMV 4.4 Book 3, 5.4, a Data Object List (DOL) entry
// consists of a BER encoded tag, followed by a one-byte length.

// Decode tag octets
Expand Down Expand Up @@ -117,6 +117,9 @@ int emv_dol_compute_data_length(const void* ptr, size_t len)
while ((r = emv_dol_itr_next(&itr, &entry)) > 0) {
total += entry.length;
}
if (r != 0) {
return -3;
}

return total;
}
Expand Down Expand Up @@ -159,7 +162,7 @@ int emv_dol_build_data(
}
if (!tlv) {
// If TLV is not found, zero data output
// See EMV 4.3 Book 3, 5.4, step 2b
// See EMV 4.4 Book 3, 5.4, step 2b
memset(data_ptr, 0, entry.length);
data_ptr += entry.length;
*data_len -= entry.length;
Expand All @@ -168,17 +171,48 @@ int emv_dol_build_data(

if (tlv->length == entry.length) {
// TLV is found and length matches DOL entry
memcpy(data_ptr, tlv->value, tlv->length);
data_ptr += tlv->length;
*data_len -= tlv->length;
memcpy(data_ptr, tlv->value, entry.length);
data_ptr += entry.length;
*data_len -= entry.length;
continue;
}

if (tlv->length != entry.length) {
// TODO: EMV 4.3 Book 3, 5.4, step 2c
// TODO: EMV 4.3 Book 3, 5.4, step 2d
return -3;
if (tlv->length > entry.length) {
// TLV is found and length is more than DOL entry, requiring truncation
// See EMV 4.4 Book 3, 5.4, step 2c
// TODO: Update for non-PDOL use, non-terminal fields and format CN
unsigned int offset = 0;
if (emv_tlv_is_terminal_format_n(tlv->tag)) {
offset = tlv->length - entry.length;
}
memcpy(data_ptr, tlv->value + offset, entry.length);
data_ptr += entry.length;
*data_len -= entry.length;
continue;
}

if (tlv->length < entry.length) {
// TLV is found and length is less than DOL entry, requiring padding
// See EMV 4.4 Book 3, 5.4, step 2d
// TODO: Update for non-PDOL use, non-terminal fields and format CN
unsigned int pad_len = entry.length - tlv->length;
if (emv_tlv_is_terminal_format_n(tlv->tag)) {
memset(data_ptr, 0, pad_len);
memcpy(data_ptr + pad_len, tlv->value, tlv->length);
} else {
memcpy(data_ptr, tlv->value, tlv->length);
memset(data_ptr + tlv->length, 0, pad_len);
}
data_ptr += entry.length;
*data_len -= entry.length;
continue;
}

// This should never happen
return -3;
}
if (r != 0) {
return -4;
}

*data_len = data_ptr - data;
Expand Down
44 changes: 22 additions & 22 deletions src/emv_dol.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/**
* @file emv_dol.h
* @brief EMV Data Object List processing functions
* @remark See EMV 4.3 Book 3, 5.4
* @brief EMV Data Object List (DOL) processing functions
* @remark See EMV 4.4 Book 3, 5.4
*
* Copyright (c) 2021 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 @@ -34,31 +34,31 @@ struct emv_tlv_list_t;

/// EMV Data Object List (DOL) entry
struct emv_dol_entry_t {
unsigned int tag;
unsigned int length;
unsigned int tag; ///< EMV tag
unsigned int length; ///< Expected length
};

/// EMV Data Object List (DOL) iterator
struct emv_dol_itr_t {
const void* ptr;
size_t len;
const void* ptr; ///< Encoded EMV Data Object List (DOL)
size_t len; ///< Length of encoded EMV Data Object List (DOL) in bytes
};

/**
* Decode EMV Data Object List (DOL) entry
* @remark See EMV 4.3 Book 3, 5.4
* @remark See EMV 4.4 Book 3, 5.4
*
* @param ptr Encoded EMV Data Object List (DOL) data
* @param len Length of encoded EMV Data Object List (DOL) data in bytes
* @param ptr Encoded EMV Data Object List (DOL) entry
* @param len Length of encoded EMV Data Object List (DOL) entry in bytes
* @param entry Decoded Data Object List (DOL) entry output
* @return Number of bytes consumed. Zero for end of data. Less than zero for error.
*/
int emv_dol_decode(const void* ptr, size_t len, struct emv_dol_entry_t* entry);

/**
* Initialize Data Object List (DOL) iterator
* @param ptr DOL encoded data
* @param len Length of DOL encoded data in bytes
* Initialise Data Object List (DOL) iterator
* @param ptr Encoded EMV Data Object List (DOL)
* @param len Length of encoded EMV Data Object List (DOL) in bytes
* @param itr DOL iterator output
* @return Zero for success. Less than zero for error.
*/
Expand All @@ -67,27 +67,27 @@ int emv_dol_itr_init(const void* ptr, size_t len, struct emv_dol_itr_t* itr);
/**
* Decode next Data Object List (DOL) entry and advance iterator
* @param itr DOL iterator
* @param entry Decoded Data Object List entry output
* @param entry Decoded Data Object List (DOL) entry output
* @return Number of bytes consumed. Zero for end of data. Less than zero for error.
*/
int emv_dol_itr_next(struct emv_dol_itr_t* itr, struct emv_dol_entry_t* entry);

/**
* Compute command data length required by Data Object List (DOL)
* @param ptr Encoded EMV Data Object List (DOL) data
* @param len Length of encoded EMV Data Object List (DOL) data in bytes
* Compute concatenated data length required by Data Object List (DOL)
* @param ptr Encoded EMV Data Object List (DOL)
* @param len Length of encoded EMV Data Object List (DOL) in bytes
* @return Length of command data. Less than zero for error.
*/
int emv_dol_compute_data_length(const void* ptr, size_t len);

/**
* Build command data according to Data Object List (DOL)
* @param ptr Encoded EMV Data Object List (DOL) data
* @param len Length of encoded EMV Data Object List (DOL) data in bytes
* Build concatenated data according to Data Object List (DOL)
* @param ptr Encoded EMV Data Object List (DOL)
* @param len Length of encoded EMV Data Object List (DOL) in bytes
* @param source1 EMV TLV list used as primary source. Required.
* @param source2 EMV TLV list used as secondary source. NULL to ignore.
* @param data Output data
* @param data_len Length of output data in bytes
* @param data Concatenated data output
* @param data_len Length of concatenated data output in bytes
* @return Zero for success. Less than zero for internal error. Greater than zero if output data length too small.
*/
int emv_dol_build_data(
Expand Down
29 changes: 29 additions & 0 deletions src/emv_tlv.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include "emv_tlv.h"
#include "iso8825_ber.h"
#include "emv_tags.h"

#include <stdbool.h>
#include <stdlib.h> // for malloc() and free()
Expand Down Expand Up @@ -244,6 +245,34 @@ int emv_tlv_parse(const void* ptr, size_t len, struct emv_tlv_list_t* list)
return 0;
}

bool emv_tlv_is_terminal_format_n(unsigned int tag)
{
// EMV tags with source 'Terminal' and format 'n'
// See EMV 4.4 Book 3, Annex A1
switch (tag) {
case EMV_TAG_9A_TRANSACTION_DATE:
case EMV_TAG_9C_TRANSACTION_TYPE:
case EMV_TAG_5F2A_TRANSACTION_CURRENCY_CODE:
case EMV_TAG_5F36_TRANSACTION_CURRENCY_EXPONENT:
case EMV_TAG_5F57_ACCOUNT_TYPE:
case EMV_TAG_9F01_ACQUIRER_IDENTIFIER:
case EMV_TAG_9F02_AMOUNT_AUTHORISED_NUMERIC:
case EMV_TAG_9F03_AMOUNT_OTHER_NUMERIC:
case EMV_TAG_9F15_MCC:
case EMV_TAG_9F1A_TERMINAL_COUNTRY_CODE:
case EMV_TAG_9F21_TRANSACTION_TIME:
case EMV_TAG_9F35_TERMINAL_TYPE:
case EMV_TAG_9F39_POS_ENTRY_MODE:
case EMV_TAG_9F3C_TRANSACTION_REFERENCE_CURRENCY:
case EMV_TAG_9F3D_TRANSACTION_REFERENCE_CURRENCY_EXPONENT:
case EMV_TAG_9F41_TRANSACTION_SEQUENCE_COUNTER:
return true;

default:
return false;
}
}

int emv_format_ans_to_non_control_str(
const uint8_t* buf,
size_t buf_len,
Expand Down
11 changes: 11 additions & 0 deletions src/emv_tlv.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,17 @@ int emv_tlv_list_append(struct emv_tlv_list_t* list, struct emv_tlv_list_t* othe
*/
int emv_tlv_parse(const void* ptr, size_t len, struct emv_tlv_list_t* list);

/**
* Determine whether a specific EMV tag with source 'Terminal' should be
* encoded as format 'n'
* @note This function is typically needed for Data Object List (DOL) processing
* @remark See EMV 4.4 Book 3, Annex A1
*
* @param tag EMV tag with source 'Terminal'
* @return Boolean indicating EMV tag should be encoded as format 'n'
*/
bool emv_tlv_is_terminal_format_n(unsigned int tag);

/**
* Convert EMV format "ans" to ISO/IEC 8859 string and omit control characters
* @note This function is typically needed for Application Preferred Name (field 9F12)
Expand Down
4 changes: 4 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ if (BUILD_TESTING)
target_link_libraries(emv_cvmlist_test PRIVATE emv)
add_test(emv_cvmlist_test emv_cvmlist_test)

add_executable(emv_dol_test emv_dol_test.c)
target_link_libraries(emv_dol_test PRIVATE print_helpers emv)
add_test(emv_dol_test emv_dol_test)

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

0 comments on commit 0bfc384

Please sign in to comment.