Switcloud L2

SwitcloudL2 is the lowest item of the Switcloud infrastructure. It simply wraps native EMV L2 frameworks into a Kotlin glue exposing GLA interfaces. This allows SwitcloudClt (or payment applications) to always use the exact same API to drive any EMV L2 framework.

Warning

Directly integrating SwitcloudL2 into a payment application doesn't benefit from the the Switcloud infrastructure.

Note

To integrate Switcloud L2 into your application, please follow the instruction in deliverables.

1. Data model

Switcloud L2 follows pretty much the Moka data model (or rather the GLA data model).

2. Hands on

2.1 Instanciation

The SwitcloudL2 class is a singleton - no explicit instanciation, no constructor - that can be retrieved using the following code:

val switcloudL2 = SwitcloudL2.getInstance()

2.2 Services

SwitcloudL2 uses a concept of services inherited from Moka to drive different aspects of the EMV L2 process. Currently SwitcloudL2 exposes 3 different services:

  • Glase (GLA)
  • Reader
  • VCard (optional, for virtualization only)

These services, can be access through simple getters such as below:

val glase = switcloudL2.glase()
val reader = switcloudL2.reader()
val vcard = switcloudL2.vcard()

2.2.1 Reader

The Reader service will drive the interaction between the readers and the card.

This service exposes only one function configure which lets the application configure certain aspects of the interaction such as the supported interfaces or the timeout.

Code sample:

val readerParams = byteArrayOf(
    // Supported interfaces (the 'Value' is a mask:
    // - 0x01 = magstripe
    // - 0x02 = contact
    // - 0x04 = contactless
    0xDF.toByte(), 0xA0.toByte(), 0x06, 0x01, 0x04,
    // Trace (on)
    0xDF.toByte(), 0xA0.toByte(), 0x18, 0x01, 0x01,
    // Timeout interfaces detection (30 sec)
    0xDF.toByte(), 0xA0.toByte(), 0x08, 0x01, 0x1e
)

reader.configure(readerParams)

2.2.2 Glase

The Glase service will drive all the EMV L2 aspects, including the the EMV configuration.

EMV configuration happens in more or less 4 steps:

  • Entry Point configuration (a few configuration settings)
  • Combination(s) (or AID(s)) injection
  • CAPK injection
  • CRL injection

Note

Methods to clean/flush CAPKs and CRLs do exists but are not necessary in this introductory documentation. Please refer to the full service documentation here for more information.

2.2.2.1 Entry Point configuration

The Entry Point configuration must be performed via the configureEntryPoint method.

Code sample:

val entryPointConfiguration = byteArrayOf(
    // Reference transaction type (0x00 = Purchase)
    0xDF.toByte(), 0xA0.toByte(), 0x25, 0x01, 0x00
)

glase.configureEntryPoint(entryPointConfiguration)
2.2.2.2 Combinations and AIDs

Combinations and AIDs are blobs of data which provide the kernel with the necessary information to perform a certain type of transaction. In SwitcloudL2, "combinations" refer to contactless transactions while "AIDs" to contact transactions.

A combination is always bound to a "triplet" AID-Kernel ID-Transaction Type. To add a combination into the EMV L2 framework behind SwitcloudL2 simply call the addCombination method.

Note

Contact transactions support is not available yet therefore the addAid method is not available either.

Find below a sample code which injects two combinations into the EMV L2 stack:

  • A Mastercard AID (... 041010) x a Mastercard kernel ID (0x02) x Purchase (0x00)
  • A Visa AID (... 031010) x a Visa kernel ID (0x03) x Purchase (0x00)
// Mastercard combination
val combinationMastercard = byteArrayOf(
    0xE1.toByte(), 0x32,
        // AID
        0x9F.toByte(), 0x06, 0x07, 0xA0.toByte(), 0x00, 0x00, 0x00, 0x04, 0x10, 0x10,
        // Kernel ID
        0xDF.toByte(), 0x81.toByte(), 0x0C, 0x01, 0x02,
        // Partial Selection flag
        0xDF.toByte(), 0xA0.toByte(), 0x10, 0x01, 0x01,
        // Transaction Type
        0x9c.toByte(), 0x01, 0x00,
        // Configuration container + length
        0xE2.toByte(), 0x19,
        // Reader Contactless Floor Limit
        0xDF.toByte(), 0x81.toByte(), 0x23, 0x06, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00,
        // Reader Contactless Transaction Limit (No On-Device CVM)
        0xDF.toByte(), 0x81.toByte(), 0x24, 0x06, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00,
        // Security Capability
        0xDF.toByte(), 0x81.toByte(), 0x1F, 0x01, 0x08
)

glase.addCombination(combinationMastercard)

// Visa combination
val combinationVisa = byteArrayOf(
    0xE1.toByte(), 0x52,
        // Kernel ID
        0xDF.toByte(), 0x81.toByte(), 0x0C, 0x01, 0x03,
        // AID
        0x9F.toByte(), 0x06, 0x07, 0xA0.toByte(), 0x00, 0x00, 0x00, 0x03, 0x10, 0x10,
        // Partial Selection flag
        0xDF.toByte(), 0xA0.toByte(), 0x10, 0x01, 0x01,
        // Transaction Type
        0x9c.toByte(), 0x01, 0x00,
        // Configuration container + length
        0xE2.toByte(), 0x45,
        // TTQ
        0x9F.toByte(), 0x66, 0x04, 0x27, 0x00, 0x40, 0x00,
        0x9F.toByte(), 0x1B, 0x04, 0x00, 0x00, 0x27, 0x10,
        0x9F.toByte(), 0x82.toByte(), 0x0D, 0x06, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
        0x9F.toByte(), 0x82.toByte(), 0x0E, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
        0xDF.toByte(), 0xA0.toByte(), 0x20, 0x06, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
        0xDF.toByte(), 0xA0.toByte(), 0x14, 0x01, 0x01,
        0xDF.toByte(), 0xA0.toByte(), 0x15, 0x01, 0x01,
        0xDF.toByte(), 0xA0.toByte(), 0x16, 0x01, 0x01,
        0xDF.toByte(), 0xA0.toByte(), 0x17, 0x01, 0x01,
        0x9F.toByte(), 0x1A, 0x02, 0x08, 0x40
)

glase.addCombination(combinationVisa)
2.2.2.3 CAPKs / CRLs

Injecting and EMV public key in the EMV L2 stack is rather straightforward. Simply call the addCAKey method with a serialized CAPK which format is described in the sample below:

val caKey = byteArrayOf(
    0xDF.toByte(), 0xA0.toByte(), 0x23, 0x82.toByte(), 0x01, 0x22,
    // RID
    0xA0.toByte(), 0x00, 0x00, 0x00, 0x04,
    // Index
    0x00,
    // Hash indicator (RFU)
    0x00,
    // Algo indicator (RFU)
    0x00,
    // Modulus length
    0x00,
    0xA0.toByte(),
    // Modulus
    0x9C.toByte(), 0x6B, 0xE5.toByte(), 0xAD.toByte(), 0xB1.toByte(), 0x0B, 0x4B, 0xE3.toByte(),
    0xDC.toByte(), 0xE2.toByte(), 0x09, 0x9B.toByte(), 0x4B, 0x21, 0x06, 0x72, 0xB8.toByte(),
    0x96.toByte(), 0x56, 0xEB.toByte(), 0xA0.toByte(), 0x91.toByte(), 0x20, 0x4F, 0x61, 0x3E,
    0xCC.toByte(), 0x62, 0x3B, 0xED.toByte(), 0xC9.toByte(), 0xC6.toByte(), 0xD7.toByte(), 0x7B,
    0x66, 0x0E, 0x8B.toByte(), 0xAE.toByte(), 0xEA.toByte(), 0x7F, 0x7C, 0xE3.toByte(), 0x0F,
    0x1B, 0x15, 0x38, 0x79, 0xA4.toByte(), 0xE3.toByte(), 0x64, 0x59, 0x34, 0x3D, 0x1F,
    0xE4.toByte(), 0x7A, 0xCD.toByte(), 0xBD.toByte(), 0x41, 0xFC.toByte(), 0xD7.toByte(), 0x10,
    0x03, 0x0C, 0x2B, 0xA1.toByte(), 0xD9.toByte(), 0x46, 0x15, 0x97.toByte(), 0x98.toByte(),
    0x2C, 0x6E, 0x1B, 0xDD.toByte(), 0x08, 0x55, 0x4B, 0x72, 0x6F, 0x5E, 0xFF.toByte(), 0x79,
    0x13, 0xCE.toByte(), 0x59, 0xE7.toByte(), 0x9E.toByte(), 0x35, 0x72, 0x95.toByte(),
    0xC3.toByte(), 0x21, 0xE2.toByte(), 0x6D, 0x0B, 0x8B.toByte(), 0xE2.toByte(), 0x70,
    0xA9.toByte(), 0x44, 0x23, 0x45, 0xC7.toByte(), 0x53, 0xE2.toByte(), 0xAA.toByte(), 0x2A,
    0xCF.toByte(), 0xC9.toByte(), 0xD3.toByte(), 0x08, 0x50, 0x60, 0x2F, 0xE6.toByte(),
    0xCA.toByte(), 0xC0.toByte(), 0x0C, 0x6D, 0xDF.toByte(), 0x6B, 0x8D.toByte(), 0x9D.toByte(),
    0x9B.toByte(), 0x48, 0x79, 0x0B, 0x82.toByte(), 0x6B, 0x04, 0x2A, 0x07, 0xF0.toByte(),
    0xE5.toByte(), 0xAE.toByte(), 0x52, 0x6A, 0x3D, 0x3C, 0x4D, 0x22, 0xC7.toByte(), 0x2B,
    0x9E.toByte(), 0xAA.toByte(), 0x52, 0xEE.toByte(), 0xD8.toByte(), 0x89.toByte(), 0x38, 0x66,
    0xF8.toByte(), 0x66, 0x38, 0x7A, 0xC0.toByte(), 0x5A, 0x13, 0x99.toByte(),
    // Padding zeros
    *ByteArray(96) { 0x00 },
    // Exponent length
    0x01,
    // Exponent
    0x03,
    0x00,
    0x00,
    // Hash
    *ByteArray(20) { 0x00 }
)

glase.addCAKey(caKey)

Note

CRL management works in the exact same manner. Please refer to addRevokedCertificate.

2.3 Basic flow

A typical SwitcloudL2 life-cycle would look like:

  • Application startup:
    • Access the SwitcloudL2 instance
    • Configure the necessary services
  • For each transaction:
    • Initialize services
    • Perform transaction (Glase)
    • Cleanup services

In practice this becomes:

// Application startup
//// Get instance (+ sub-components)
val switcloudL2 = SwitcloudL2.getInstance()
val glase = switcloudL2.glase()
val reader = switcloudL2.reader()

//// Configure services
reader.configure(readerParams)
glase.configureEntryPoint(entryPointConfiguration)

// Transactions
while (true) {
    //// Imagine a transaction trigger event...

    //// Init services (transient data)
    switcloudL2.initializeServices()

    //// Inject combinations, keys, CRLs
    glase.addCombination(combinationMastercard)
    glase.addCombination(combinationVisa)
    glase.addCAKey(caKey)

    //// EMV flow
    val preProcessingResult = glase.preProcessing(trd) // Start 1
    val detectedInterface = glase.protocolActivation(null) // Start B
    val combinationSelectionResult = glase.combinationSelection() // Start C
    val kernelActivationResult = glase.kernelActivation(null) // Start D

    /// Clean services (transient data)
    switcloudL2.cleanServices()
}

2.4 Delegated security

The delegated security feature is a mean for Moka/SwitcloudL2 to delegate operations performed with sensitive assets to more secure components. There operations are split in 3 categories: Reader (contact, contactless and magstripe access), Random and ODA (card authentication related features).

Warning

The delegation capability starts with version 1.5.0 of the switcloud-l2-kt package. This version only supports delegation readers (IRdrCallback) and randoms (IRngCallback). From version 2.0.0 and above ODA delegation is possible (IOdaCallback).

The payment application should then implement the 3 callback classes and register them using the registerCallbacks method as follows:

switcloudL2.registerCallbacks(myRdrCallbck, myRngCallback, myOdaCallback)

2.5 User messages

Switcloud L2 uses a Kotlin Flow mechanism to broadcast events such as user messages (UIRD). Any application that wants to receive those events should simply collect them from the SwitcloudL2.userInfo variable, using, for example, the below code:

scope.launch(Dispatchers.IO) {
    SwitcloudL2.getInstance().userInfo.collect { userInfo ->
        println(userInfo)
    }
}

The data collected for the UIRD event is in a TLV format.

The following tags are currently in use:

  • DFA01B for LED management: 1 byte long, high nibble indicates the LED to switch, low nibble indicates 'on' or 'off'.
  • DF8116 for UIRD: see Mastercard specfification for its content.

Note

For the sake of simplicity, Moka only sends 'on' LED events. That means that if a "LED n°2 + 'on'" event is received the application should make sure that all preceeding LEDs are also switched on (if not already) and all following LEDs should be switched off (if not already).

```