Switstack moka Terminal (SmT) for Contact

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 contact kernel. Going online consists in getting an emulating issuer response.

/**
 * @brief Main entry point and main loop to perform a contact payment.
 * @note See functions below for the different transaction steps.
 */
uint16_t
moka_perform_ct_transaction(const char* vcard, const char* trd, const char* issuer)
{
    uint16_t moka_error = MOKA_OK;
    uint8_t detected_interface = 0;
    uint8_t power_on = 3;
    uint8_t outbound[BUFFER_SIZE_512] = "";
    uint16_t out_length = sizeof(outbound);
    uint8_t issuer_data[BUFFER_SIZE_512] = "";
    uint16_t issuer_data_length = sizeof(issuer_data);
    moka_contact_outcome_parameters_set_t ops;
    uint8_t resume_data[20] = "";
    uint16_t resume_data_length = 0;
    uint8_t* p_resume_data = NULL;

    /** 1. Detect card */
    MOKA_UNUSED(vcard);
    MOKA_ERROR_FUNC(moka_detect_card(&detected_interface));

    while (power_on) {
        if (glase.power_contact_on(outbound, &out_length) == MOKA_OK) {
            break;
        }
        power_on--;
    }
    MOKA_ERROR_IF(!power_on, MOKA_ERR_RDR_POWER_ON);

    /** 2. Initiate */
    MOKA_ERROR_FUNC(s_moka_perform_ct_initiate(trd, &ops));

    /** 3. Process outcome: moka API uses granular calls for each EMV contact step */
    while (ops.status == CONTACT_OPS_STATUS_HALTED) {
        resume_data_length = 0;
        p_resume_data = NULL;
        if (ops.user_action) {
            p_resume_data = resume_data;
            MOKA_ERROR_FUNC(user_action_execute(
                ops.user_action, &p_resume_data, &resume_data_length));
        }
        out_length = sizeof(outbound);
        hal_os_memset(outbound, 0x00, out_length);
        MOKA_ERROR_FUNC(s_moka_ct_resume(
            p_resume_data, resume_data_length, outbound, &out_length, &ops));
    }

    /** 4. Complete */
    if (ops.status == CONTACT_OPS_STATUS_ONLINE_REQUEST) {
        if (moka_go_online(
                issuer, issuer_data, &issuer_data_length) != MOKA_OK) {
            MOKA_ERROR_FUNC(s_moka_perform_ct_complete(NULL, 0, &ops));
        }
        else {
            MOKA_ERROR_FUNC(s_moka_perform_ct_complete(
                issuer_data, issuer_data_length, &ops));
        }
    }

    /** 5. Power off */
    MOKA_ERROR_FUNC(glase.power_contact_off());

    /* 6. ... for TA qualification */
    MOKA_ERROR_FUNC(moka_print_receipt_contact(&ops));

    return MOKA_OK;
}

A moa contact transaction flow is split up into 3 different steps: 1- Initiate 2- Resume 3- Complete.

Info

Initiate a transaction Once the card insertion has been detected, the very first step is to build a mutual list of candidates in order to select an EMV application. The mutual is provided under the form of a serialized structure exposing any issuer's information enabling prorietary selection method. Then, the system initiates the EMV transaction flow, i.e. sends a get processing options command to card. While the system receives a select_next error, the system iterates on this sequence. Any other errors leads to resume the transaction.

/**
 * @brief Initiate step
 * @note This step builds the candidates list, selects the application and initiates the transaction.
 * See calls to glase API.
 */
```C
static uint16_t
s_moka_perform_ct_initiate(
    const char* trd, moka_contact_outcome_parameters_set_t* ops)
{
    uint16_t moka_error = MOKA_OK;
    uint8_t selected_aid = 0;
    uint8_t mutual_list[BUFFER_SIZE_4096] = ""; // Huge space required to get seriazlized candidates (example: emvco conf73)
    uint16_t mutual_list_length = sizeof(mutual_list);
    uint8_t trd_data[BUFFER_SIZE_128] = "";
    uint16_t trd_data_length = sizeof(trd_data);
    uint8_t outbound[BUFFER_SIZE_512] = "";
    uint16_t out_length = sizeof(outbound);

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

    /** 1. Get mutual list, ordered by priority */
    MOKA_ERROR_FUNC(glase.build_contact_candidates_list(mutual_list, &mutual_list_length));
    if (!mutual_list_length) {
        MOKA_TRACE_D("mutual list empty\n");
        ops->status = CONTACT_OPS_STATUS_END_APPLICATION;
        return MOKA_OK;
    }

    do {
        /** 2. Proceed to EMV application selection */
        do {
            /** 2.1. Cardholder selection: This section provides visibility on full EMV processing but
             * it is not supported yet by moka's ICS: cardholder confirmation and language not supported.
             * These features are considered as minor changes.
             */
            MOKA_ERROR_FUNC(cardholder_selection_initialize());
            selected_aid = 0;
            moka_error = cardholder_selection_select_application(
                mutual_list, mutual_list_length, &selected_aid);

            if (moka_error != MOKA_OK) {
                if (moka_error == MOKA_ERR_EMV_EMPTY_CANDIDATE_LIST) {
                    MOKA_TRACE_D("empty candidate list\n");
                    ops->status = CONTACT_OPS_STATUS_NOT_ACCEPTED;
                    return MOKA_OK;
                }
                return moka_error;
            }

            if (!selected_aid) {
                MOKA_TRACE_D("no selectable aid\n");
                ops->status = CONTACT_OPS_STATUS_END_APPLICATION;
                return MOKA_OK;
            }

            MOKA_ERROR_FUNC(cardholder_selection_select_language());

            /** 2.2. Final select, eventually get updated ordered mutual list
             * if final selection fails
             */
            mutual_list_length = sizeof(mutual_list);
            hal_os_memset(mutual_list, 0x00, mutual_list_length);
            MOKA_TRACE_D("final select\n");
            moka_error = s_moka_final_select(selected_aid, mutual_list, &mutual_list_length);

        } while (moka_error == MOKA_ERR_EMV_SELECT_NEXT);  

        /** 3. Initiate transaction */
        MOKA_ERROR_IF(moka_error != MOKA_OK, moka_error);
        out_length = sizeof(outbound);
        hal_os_memset(outbound, 0x00, out_length);
        MOKA_ERROR_FUNC(moka_get_trd(trd, trd_data, &trd_data_length)); 
        MOKA_TRACE_D("initiate\n");
        MOKA_ERROR_FUNC(glase.initiate_card_processing(trd_data, trd_data_length, outbound, &out_length));
        MOKA_TRACE_DH("initiate outcome >", outbound, out_length);
        MOKA_ERROR_FUNC(s_moka_get_contact_outcome_parameters_set(outbound, out_length, ops));

        /** GPO may lead to a select next case. So, need to refesh mutual list */
        if (ops->status == CONTACT_OPS_STATUS_SELECT_NEXT) {
            mutual_list_length = sizeof(mutual_list);
            hal_os_memset(mutual_list, 0x00, mutual_list_length);
            MOKA_ERROR_FUNC(s_moka_get_mutual_list(mutual_list, &mutual_list_length));
            MOKA_TRACE_DH("mutual list >", mutual_list, mutual_list_length);
        }
    } while (ops->status == CONTACT_OPS_STATUS_SELECT_NEXT);

    return MOKA_OK;
}

Info

Resume a transaction All standard EMV steps will follow the initiate sequence. However, moka gives hand back to the system after each of them: read record, offline data authentication, processing restrictions, cardholder verification, terminal risk management, and terminal action analysis (as long as an error doesn't stopp the flow). The EMV sequence is "halted" so any proprietary computation may be realized. The transaction flow may be aborted at any time. In some cases, the EMV Level 2 may request the system to perform a user action such as "get amount", or "get online pin", ... Still, the sequence has to be resume until the stack reaches the first generate ac.

/**
 * @brief Resume step
 * @note This step resumes the contact card processing. See call to glase API.
 */
``` C
static uint16_t
s_moka_ct_resume(
    const uint8_t* inbound,
    uint16_t in_length,
    uint8_t* outbound,
    uint16_t* out_length,
    moka_contact_outcome_parameters_set_t* ops)
{
    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_ERROR_IF(!ops, MOKA_ERR_SYS_INVALID_PARAM);

    MOKA_TRACE_D("resume\n");
    MOKA_ERROR_FUNC(glase.resume_card_processing(inbound, in_length, outbound, out_length));
    MOKA_TRACE_DH("resume outcome >", outbound, *out_length);
    MOKA_ERROR_FUNC(s_moka_get_contact_outcome_parameters_set(outbound, *out_length, ops));

    return moka_error;
}

Info

Complete a transaction Once the generate ac has been reached, the system may need to go online, accept or decline the transaction, or terminate it. If the transaction goes online then system shall complete the transaction.

/**
 * @brief Complete step
 * @note This step completes the contact card processing. See call to glase API.
 */
static uint16_t
s_moka_perform_ct_complete(
    uint8_t* issuer_response,
    uint16_t issuer_response_length,
    moka_contact_outcome_parameters_set_t* ops)
{
    uint16_t moka_error = MOKA_OK;
    uint8_t* p_arc = NULL;
    uint16_t length = 0;
    uint8_t outcome[BUFFER_SIZE_512] = "";
    uint16_t outcome_length = sizeof(outcome);

    // Set CID for 2nds generate ac based on ARC
    if (parser_tlv_utils_find_tag_reference_from_tlv(
            0x8A, &length, &p_arc, issuer_response, issuer_response_length) == MOKA_OK) {
        if (p_arc && length == 2) {
            MOKA_TRACE_DH("issuer arc >", p_arc, 2);
            if (!hal_os_memcmp(p_arc, "\x30\x30", 2)) {
                hal_os_memcpy(issuer_response + issuer_response_length, "\xDF\x81\x14\x01\x40", 5);
                issuer_response_length += 5;
            }
            else {
                hal_os_memcpy(issuer_response + issuer_response_length, "\xDF\x81\x14\x01\x00", 5);                    
                issuer_response_length += 5;
            }
        }
    }

    MOKA_TRACE_D("complete\n");
    MOKA_ERROR_FUNC(glase.complete_card_processing(issuer_response, issuer_response_length, outcome, &outcome_length));
    MOKA_TRACE_DH("complete outcome >", outcome, outcome_length);
    MOKA_ERROR_FUNC(s_moka_get_contact_outcome_parameters_set(outcome, outcome_length, ops));

    return MOKA_OK;
}