Basic transaction
A basic OLA transaction includes the following steps:
OLA classes instantiation (once at startup)
private val ola: Ola private val contact: OlaContact private val contactless: OlaContactless private val emv: OlaEmv private val dev: Dev private val publicKey: OlaPublicKey ... init { ola = Ola.getInstance() contact = OlaContact.getInstance() contactless = OlaContactless.getInstance() emv = OlaEmv.getInstance() publicKey = OlaPublicKey() ... }
OLA component initialisation (once at startup)
/** * Configures OLA layer (key files paths, trace, etc...) */ private fun olaConfig() { val path = byteArrayOf(0xdf.toByte(), 0x01, 28) val pathValue = "/data/data/ca.amadis.tester/" val cakeys = byteArrayOf(0xdf.toByte(), 0x02, 34) val cakeysValue = "/data/data/ca.amadis.tester/CAKeys" val crl = byteArrayOf(0xdf.toByte(), 0x03, 31) val crlValue = "/data/data/ca.amadis.tester/CRL" val efl = byteArrayOf(0xdf.toByte(), 0x04, 31) val eflValue = "/data/data/ca.amadis.tester/EFL" val pcsc = byteArrayOf(0xdf.toByte(), 0x05, 0x00) // pcsc not used val lang = byteArrayOf(0xdf.toByte(), 0x06, 0x00) // lang not used val runtimeParams = byteArrayOf(0xdf.toByte(), 0x07, 0x07, 0x00, 0x0F, 0x04, 0x0F, 0x0A, 0x00, 0x01) val traceLevel = byteArrayOf(0xdf.toByte(), 0x08, 0x01, 0x05) val pollingTechno = byteArrayOf(0xdf.toByte(), 0x10, 0x01, 0x04) // Contactless only var sredMode: ByteArray if (BuildConfig.FLAVOR.lowercase().contains("nosred")) { sredMode = byteArrayOf(0xdf.toByte(), 0x17, 0x01, 0x00) // SRED mode not active } else { sredMode = byteArrayOf(0xdf.toByte(), 0x17, 0x01, 0x01) // SRED mode active } try { val out = ByteArrayOutputStream() out.write(path) out.write(pathValue.toByteArray()) out.write(cakeys) out.write(cakeysValue.toByteArray()) out.write(crl) out.write(crlValue.toByteArray()) out.write(efl) out.write(eflValue.toByteArray()) out.write(pcsc) out.write(lang) out.write(runtimeParams) out.write(traceLevel) out.write(pollingTechno) out.write(sredMode) ola.initializeAtStartUp(out.toByteArray()) } catch (e: IOException) { } }
AID configuration
private fun terminalContactlessConfig() { Log.v("Transaction", "terminalContactlessConfig") val contactlessCfg = arrayOf( ContactlessCfg(AID.FromBytes(byteArrayOf(0xA0.toByte(), 0x00, 0x00, 0x00, 0x04, 0x10, 0x10)), true, 2, mcCfg), ContactlessCfg(AID.FromBytes(byteArrayOf(0xA0.toByte(), 0x00, 0x00, 0x00, 0x04, 0x20, 0x10)), true, 2, mcCfg), ...) contactless.flushAIDSupported() for (config in contactlessCfg) { contactless.addAIDSupported( config.aid, config.partial, config.kernelId.toByte(), config.additionalData ) } contactless.commitSupportedAIDs() }
Keys configuration
private fun terminalPublicKeyConfig() { Log.v("Transaction", "terminalPublicKeyConfig") val publicKeyCfg = arrayOf( /* MasterCard */ PublicKeyCfg(byteArrayOf(0xa0.toByte(), 0x00, 0x00, 0x00, 0x03), 0x92.toByte(), byteArrayOf(0x99.toByte(), 0x6a, 0xf5.toByte(), 0x6f, 0x56, 0x91.toByte(), 0x87.toByte(), 0xd0.toByte(), 0x92.toByte(), 0x93.toByte(), 0xc1.toByte(), 0x48, 0x10, 0x45, 0x0e, 0xd8.toByte(), 0xee.toByte(), 0x33, 0x57, 0x39, 0x7b, 0x18, 0xa2.toByte(), 0x45, 0x8e.toByte(), 0xfa.toByte(), 0xa9.toByte(), 0x2d, 0xa3.toByte(), 0xb6.toByte(), 0xdf.toByte(), 0x65, 0x14, 0xec.toByte(), 0x06, 0x01, 0x95.toByte(), 0x31, 0x8f.toByte(), 0xd4.toByte(), 0x3b, 0xe9.toByte(), 0xb8.toByte(), 0xf0.toByte(), 0xcc.toByte(), 0x66, 0x9e.toByte(), 0x3f, 0x84.toByte(), 0x40, 0x57, 0xcb.toByte(), 0xdd.toByte(), 0xf8.toByte(), 0xbd.toByte(), 0xa1.toByte(), 0x91.toByte(), 0xbb.toByte(), 0x64, 0x47, 0x3b, 0xc8.toByte(), 0xdc.toByte(), 0x9a.toByte(), 0x73, 0x0d, 0xb8.toByte(), 0xf6.toByte(), 0xb4.toByte(), 0xed.toByte(), 0xe3.toByte(), 0x92.toByte(), 0x41, 0x86.toByte(), 0xff.toByte(), 0xd9.toByte(), 0xb8.toByte(), 0xc7.toByte(), 0x73, 0x57, 0x89.toByte(), 0xc2.toByte(), 0x3a, 0x36, 0xba.toByte(), 0x0b, 0x8a.toByte(), 0xf6.toByte(), 0x53, 0x72, 0xeb.toByte(), 0x57, 0xea.toByte(), 0x5d, 0x89.toByte(), 0xe7.toByte(), 0xd1.toByte(), 0x4e, 0x9c.toByte(), 0x7b, 0x6b, 0x55, 0x74, 0x60, 0xf1.toByte(), 0x08, 0x85.toByte(), 0xda.toByte(), 0x16, 0xac.toByte(), 0x92.toByte(), 0x3f, 0x15, 0xaf.toByte(), 0x37, 0x58, 0xf0.toByte(), 0xf0.toByte(), 0x3e, 0xbd.toByte(), 0x3c, 0x5c, 0x2c, 0x94.toByte(), 0x9c.toByte(), 0xba.toByte(), 0x30, 0x6d, 0xb4.toByte(), 0x4e, 0x6a, 0x2c, 0x07, 0x6c, 0x5f, 0x67, 0xe2.toByte(), 0x81.toByte(), 0xd7.toByte(), 0xef.toByte(), 0x56, 0x78, 0x5d, 0xc4.toByte(), 0xd7.toByte(), 0x59, 0x45, 0xe4.toByte(), 0x91.toByte(), 0xf0.toByte(), 0x19, 0x18, 0x80.toByte(), 0x0a, 0x9e.toByte(), 0x2d, 0xc6.toByte(), 0x6f, 0x60, 0x08, 0x05, 0x66, 0xce.toByte(), 0x0d, 0xaf.toByte(), 0x8d.toByte(), 0x17, 0xea.toByte(), 0xd4.toByte(), 0x6a, 0xd8.toByte(), 0xe3.toByte(), 0x0a, 0x24, 0x7c, 0x9f.toByte()), byteArrayOf(0x03), byteArrayOf(0x24, 0x12, 0x31)), ...) publicKey.flush() for (config in publicKeyCfg) { val maxReached = SingleObjectHolder(false) val key: PublicKeyData = publicKey.PublicKeyData( config.rid, config.idx, config.modulus, config.exponentValue, config.expirDate ) publicKey.add(key, null, maxReached) } publicKey.commit() }
Transaction related data ()
private fun txnSetTransactionRelatedData() { Log.v("Transaction", "txnSetTransactionRelatedData") emv.setTag(OlaTag.TRANSACTION_DATE, byteArrayOf(0x20, 0x10, 0x28)) // 9A emv.setTag(OlaTag.TRANSACTION_TIME, byteArrayOf(0x12, 0x00, 0x00)) // 9F21 emv.setTag(OlaTag.AMOUNT_AUTHORISED, byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x01, 0x50)) // 9F02 emv.setTag(OlaTag.AMOUNT_OTHER_NUM, byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00, 0x50)) // 9F03 emv.setTag(OlaTag.TRANSACTION_CURRENCY_CODE, byteArrayOf(0x08, 0x40)) // 5F2A emv.setTag(OlaTag.TRANSACTION_CURRENCY_EXPONENT, byteArrayOf(0x02)) // 5F36 emv.setTag(OlaTag.TRANSACTION_TYPE, byteArrayOf(0x00)) // 9C /* * Other possible tags to set here: * Account Type (5F57) * Transaction Category Code (9F53) * * All other tags must be set through the combinations! */ emv.setTag(0x9F53, byteArrayOf(0x11)); }
Preprocessing
private fun txnPreProcess(): OlaError { Log.v("Transaction", "txnPreProcess") val ret = contactless.preprocess() if (ret != OlaError.OLA_OK) { Log.e("Transaction", "txnPreProcess - contactless pre-process failed") } return ret }
Card detection
private fun txnGetCard(): Int { Log.v("Transaction", "txnGetCard - **** PRESENT CARD ****") val foundTechnos = dev.technoPolling(30) Log.v("Transaction", "txnGetCard - result of foundTechnos: $foundTechnos") return if (foundTechnos == 4) { clcEMV_CARD //TODO remove hard-coding } else { 0 } }
Do transaction
private fun doTransaction(): OlaError { Log.v("Transaction", "doTransaction - buildCandidateList") val nbCandidatesHolder = SingleObjectHolder<Int>() var ret = contactless.buildCandidateList(nbCandidatesHolder) if (ret != OlaError.OLA_OK) { Outcome.showOutcome(contactless, ret) } Log.v("Transaction", "doTransaction - nb of candidates: ${nbCandidatesHolder.get()}") Log.v("Transaction", "doTransaction - finalSelectCandidate") val kernel_id_holder = SingleObjectHolder<Byte>() ret = contactless.finalSelectCandidate(1, kernel_id_holder) if (ret != OlaError.OLA_OK) { Outcome.showOutcome(contactless, ret) } Log.v("Transaction", "doTransaction - doTransaction") ret = contactless.doTransaction() Outcome.showOutcome(contactless, ret) if (ret == OlaError.OLA_OK) { val bytesHolder = SingleObjectHolder<ByteArray>() for (olaTag in batch) { val result = emv.getTag(olaTag, bytesHolder) if (result == OlaError.OLA_OK) { Log.v("Transaction", "doTransaction - result for getTag(): $result => " + Utils.bytesToHex(bytesHolder.get())) } else { Log.v("Transaction", "doTransaction - result for getTag(): $result => tag missing") } } } Log.d("Transaction", "doTransaction - **** REMOVE CARD **** ") contactless.clean(); return ret }