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:
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:
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:
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:
DFA01Bfor LED management: 1 byte long, high nibble indicates the LED to switch, low nibble indicates 'on' or 'off'.DF8116for 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).
```