Switstack moka Terminal (SmT) for Contactless

Switstack moka Terminal is a tool that comes with moka to perform transactions in the scope of certification activities. It helps developers to realize specific tests, and debug EMV Level 2 flows using traces. Using a simple Command Line Interface (CLI), it is possible to trace moka very rapidly using either a PCSC reader (PCSC mode) or virtual cards (VEP-L mode).

Info

Running options are:

-h: displays help -c: specifies virtual card -f: specifies config -t: specifies trd -k: specifies cakeys -r: specifies revocations -i: specifies issuer response -m: specifies transaction mode (ct or cl) -s: specifies contact kernel ics -n: specifies number of transactions to run in row (ct only) -p: specifies test pin values -v: displays version

Moka generates different traces to help investigating outcomes.

Info

Type of traces:

[DBG]:display miscellaneous traces [SPC]: displays a reference to EMV Level 2 specification [CERT]: displays information required to valide a certification test [ERR]: displays errors [WRN]: displays warnings

Switstack moka Terminal implements a basic EMV Level 3 logic required to certify EMV Level 2 kernels (contact and contactless). This example presents some API calls demonstrating the integration of moka. It is a very simple sequence in regards to acquirer host connection management but it covers key calls to perfrom card processing. Note that there is no specific API for a specific payment brand. The code is presented from top-down perspective. Pre-condition to these calls: moka has been set with all combinations, cakeys, revocation certificates.

Info

Code below is an extraction of the SmT used to qualified the contactless kernels. Going online consists in getting an emulating issuer response.

/**
 * @brief Main entry point to perform a contactless payment.
 * @note See functions below for each distinct start.
 */
uint16_t
moka_perform_cl_transaction(const char* vcard, const char* trd, const char* issuer)
{
    uint16_t moka_error = MOKA_OK;
    uint64_t transaction_amount = 0;
    uint8_t issuer_data[BUFFER_SIZE_512] = "";
    uint16_t issuer_data_length = sizeof(issuer_data);
    moka_outcome_parameters_set_t ops;

    /* 1. Pre-processing */
    transaction_amount = moka_get_transaction_amount(trd);
    MOKA_ERROR_FUNC(s_moka_perform_pre_processing(trd));

    /* 2. Card selection and presentment */
    MOKA_ERROR_FUNC(moka_present_card(vcard));

    /* 3. Polling and application selection */
    MOKA_ERROR_FUNC(s_moka_perform_selection(NULL, 0));

    /* 4. Initiate */
    MOKA_ERROR_FUNC(s_moka_perform_cl_initiate(transaction_amount, &ops));

    /* 5. Complete */
    if (ops.status == OPS_STATUS_ONLINE_REQUEST) {
        MOKA_ERROR_FUNC(moka_go_online(issuer, issuer_data, &issuer_data_length));
        MOKA_ERROR_FUNC(s_moka_perform_cl_complete(issuer_data, issuer_data_length, &ops));
    }

    /* 6. Card removal */
    MOKA_ERROR_FUNC(card.remove());

    /* 7. ... for TA qualification */
    MOKA_ERROR_FUNC(moka_print_receipt_contactless(&ops));

    return MOKA_OK;
}
/**
 * @brief Start A selecting all elligible combinations according the transaction amount and the transaction
 * type.
 */
static uint16_t
s_moka_perform_pre_processing(const char* trd)
{
    uint16_t moka_error = MOKA_OK;
    uint8_t trd_data[BUFFER_SIZE_128] = "";
    uint16_t trd_data_length = sizeof(trd_data);
    uint8_t buffer[GLASE_MAX_OUTPUT_SIZE_PRE_PROCESSING] = "";
    uint16_t buffer_length = sizeof(buffer);

    MOKA_TRACE_D("start A\n");
    MOKA_ERROR_FUNC(moka_get_trd(trd, trd_data, &trd_data_length));
    MOKA_ERROR_FUNC(glase.pre_processing(trd_data, trd_data_length, buffer, &buffer_length));
    if (buffer_length) {
        MOKA_TRACE_DH("start A outcome >", buffer, buffer_length);
    }
    return MOKA_OK;
}
/**
 * @brief Encapsulates calls to start B and C.
 */
static uint16_t
s_moka_perform_selection(const uint8_t* inbound, uint16_t in_length)
{
    uint16_t moka_error = MOKA_OK;
    moka_outcome_parameters_set_t ops;

    do {
        /* Protocol activation */
        MOKA_ERROR_FUNC(s_moka_poll_card(inbound, in_length));

        /* Combination selection */
        moka_error = s_moka_select_application(&ops);

    } while ((ops.status == OPS_STATUS_TRY_AGAIN) && (ops.start == START_B));

    return moka_error;
}
/**
 * @brief Start B detecting tap (or eventually other technologies).
*/
static uint16_t
s_moka_poll_card(const uint8_t* inbound, uint16_t in_length)
{
    uint16_t moka_error = MOKA_OK;
    uint8_t detected_interface = HAL_NO_CARD_INTERFACE;

    MOKA_TRACE_D("start B\n");
    MOKA_ERROR_FUNC(glase.protocol_activation(inbound, in_length, &detected_interface));
    MOKA_TRACE_D("start B outcome (interface): %02x\n", detected_interface);

    return MOKA_OK;
}
/**
 * @brief Start C selecting the combination with highest priority corresponding to the detected card.
*/
static uint16_t
s_moka_select_application(moka_outcome_parameters_set_t* ops)
{
    uint16_t moka_error = MOKA_OK;
    uint8_t buffer[GLASE_MAX_OUTPUT_SIZE_COMBINATION_SELECTION] = "";
    uint16_t buffer_length = 0;

    /* Check parameters */
    MOKA_ERROR_IF(!ops, MOKA_ERR_SYS_INVALID_PARAM);

    MOKA_TRACE_D("start C\n");
    hal_os_memset(buffer, 0x00, sizeof(buffer));
    buffer_length = sizeof(buffer);
    moka_error = glase.combination_selection(buffer, &buffer_length);
    if (moka_error != MOKA_OK) {
        s_moka_get_outcome_parameters_set(buffer, buffer_length, ops);
        return moka_error;
    }

    return MOKA_OK;
}
/**
 * @brief Initiate a contactless card processing. ONce the final combination has been selected on start C,
 * the kernel can be activated. Moka includes a DEK/DET mechanism allowing to control the transaction flow.
 * On DEK reception, an proprietary process can be implemented. Then, a DET is sent back to the kernel
 * through a start D to resume the card processing.
*/
static uint16_t
s_moka_perform_cl_initiate(
    uint64_t amount,
    moka_outcome_parameters_set_t* ops)
{
    uint16_t moka_error = MOKA_OK;
    uint8_t buffer[BUFFER_SIZE_512] = "";
    uint16_t buffer_length = 0;
    uint8_t dek[BUFFER_SIZE_512] = "", det[BUFFER_SIZE_512] = "";
    uint16_t dek_length = 0, det_length = 0;

    /* Check parameters */
    MOKA_ERROR_IF(!ops, MOKA_ERR_SYS_INVALID_PARAM);

    while (1) {
        hal_os_memset(buffer, 0x00, sizeof(buffer));
        buffer_length = sizeof(buffer);
        MOKA_ERROR_FUNC(s_moka_activate_kernel(det, det_length, buffer, &buffer_length));

        /* Manage DEK */
        if (parser_tlv_utils_find_tag_from_tlv(
                MOKA_TAG_STRUCT_DEK, &dek_length, dek, buffer, buffer_length) == MOKA_OK) {
            hal_os_memset(det, 0x00, sizeof(det));
            MOKA_ERROR_FUNC(de_ds_perform_operator_logic(
                amount, dek, dek_length, det, &det_length));
            hal_os_memset(dek, 0x00, sizeof(dek));
            dek_length = 0;
            continue;
        };

        MOKA_ERROR_FUNC(s_moka_get_outcome_parameters_set(buffer, buffer_length, ops));
        if ((ops->status == OPS_STATUS_APPROVED)
            || (ops->status == OPS_STATUS_DECLINED)
            || (ops->status == OPS_STATUS_ONLINE_REQUEST)) {
            break;
        } else {
            if (ops->start != START_B) {
                ops->status = OPS_STATUS_END_APPLICATION;
                break;
            }
            MOKA_ERROR_FUNC(s_moka_perform_selection(NULL, 0));
        }
    };

    return MOKA_OK;
}
/**
 * @brief Start D activting kernel corresponding to detected card.
*/
static uint16_t
s_moka_activate_kernel(
    const uint8_t* inbound,
    uint16_t in_length,
    uint8_t* outbound,
    uint16_t* out_length)
{
    uint16_t moka_error = MOKA_OK;

    /* Check parameters */
    MOKA_ERROR_IF(!outbound, MOKA_ERR_SYS_INVALID_PARAM);
    MOKA_ERROR_IF(!out_length, MOKA_ERR_SYS_INVALID_PARAM);

    MOKA_TRACE_D("start D\n");
    hal_os_memset(outbound, 0x00, *out_length);
    MOKA_ERROR_FUNC(glase.kernel_activation(inbound, in_length, outbound, out_length));
    MOKA_TRACE_DH("start D outcome >", outbound, *out_length);

    return MOKA_OK;
}
/**
 * @brief Optional call depending on card's brand. If the kernel requested a restart (B, or D),
 * then a start D may be performed.
 * @note See ICS to identiy which kernel(s) support second tap.
*/
static uint16_t
s_moka_perform_cl_complete(
    const uint8_t* issuer_response,
    uint16_t issuer_response_length,
    moka_outcome_parameters_set_t* ops)
{
    uint16_t moka_error = MOKA_OK;
    moka_start_t last_start;
    uint8_t buffer[BUFFER_SIZE_512] = "";
    uint16_t buffer_length = 0;
    moka_bool_t perform_post_completion = MOKA_BOOL_FALSE;
    uint8_t dek[BUFFER_SIZE_512] = "", det[BUFFER_SIZE_512] = "";
    uint16_t dek_length = 0, det_length = 0;

    /* Check parameters */
    MOKA_ERROR_IF(!ops, MOKA_ERR_SYS_INVALID_PARAM);

    last_start = ops->start;
    emvco_signals_ops_initialize(ops);
    if (last_start != START_D) {
        if (s_moka_is_issuer_script_present(issuer_response, issuer_response_length)
            || de_ds_is_bf11_template_update_present(issuer_response, issuer_response_length)) {
            if (s_moka_is_transaction_accepted_by_issuer(issuer_response, issuer_response_length)) {
                ops->status = OPS_STATUS_APPROVED;
            } else {
                ops->status = OPS_STATUS_DECLINED;
            }
            // Second presentment (suported by moka only for Discover)
            if (s_moka_perform_selection(issuer_response, issuer_response_length) == MOKA_OK) {
                perform_post_completion = MOKA_BOOL_TRUE;
            }
        } else {
            // Standard case with no second presentment (SCA possible here based on ARC)
            if (s_moka_is_transaction_accepted_by_issuer(issuer_response, issuer_response_length)) {
                ops->status = OPS_STATUS_APPROVED;
            } else {
                ops->status = OPS_STATUS_DECLINED;
            }
        }
    } else {
        perform_post_completion = MOKA_BOOL_TRUE;
    }

    if (perform_post_completion) {
        hal_os_memset(buffer, 0x00, sizeof(buffer));
        buffer_length = sizeof(buffer);
        MOKA_ERROR_FUNC(s_moka_activate_kernel(
            issuer_response, issuer_response_length, buffer, &buffer_length));

        /* Manage DEK (only once) */
        if (parser_tlv_utils_find_tag_from_tlv(
                MOKA_TAG_STRUCT_DEK, &dek_length, dek, buffer, buffer_length) == MOKA_OK) {
            hal_os_memset(det, 0x00, sizeof(det_length));
            MOKA_ERROR_FUNC(de_ds_perform_operator_logic(0, dek, dek_length, det, &det_length));
            hal_os_memset(buffer, 0x00, sizeof(buffer));
            buffer_length = sizeof(buffer);
            MOKA_ERROR_FUNC(s_moka_activate_kernel(det, det_length, buffer, &buffer_length));
        };

        if (last_start == START_D) {
            MOKA_ERROR_FUNC(
                s_moka_get_outcome_parameters_set(buffer, buffer_length, ops));
        }
    }

    return MOKA_OK;
}