/* * This file is part of GNU Taler * (C) 2020 Taler Systems S.A. * * GNU Taler is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation; either version 3, or (at your option) any later version. * * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * GNU Taler; see the file COPYING. If not, see */ package net.taler.wallet import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.net.Uri import android.nfc.cardemulation.HostApduService import android.os.Bundle import android.util.Log import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.util.concurrent.ConcurrentLinkedDeque fun makeApduSuccessResponse(payload: ByteArray): ByteArray { val stream = ByteArrayOutputStream() stream.write(payload) stream.write(0x90) stream.write(0x00) return stream.toByteArray() } fun makeApduFailureResponse(): ByteArray { val stream = ByteArrayOutputStream() stream.write(0x6F) stream.write(0x00) return stream.toByteArray() } fun readApduBodySize(stream: ByteArrayInputStream): Int { val b0 = stream.read() if (b0 == -1) { return 0 } if (b0 != 0) { return b0 } val b1 = stream.read() val b2 = stream.read() return (b1 shl 8) and b2 } class HostCardEmulatorService : HostApduService() { val queuedRequests: ConcurrentLinkedDeque = ConcurrentLinkedDeque() private lateinit var receiver: BroadcastReceiver override fun onCreate() { super.onCreate() receiver = object : BroadcastReceiver() { override fun onReceive(p0: Context?, p1: Intent?) { queuedRequests.addLast(p1!!.getStringExtra("tunnelMessage")) } } IntentFilter(HTTP_TUNNEL_REQUEST).also { filter -> registerReceiver(receiver, filter) } } override fun onDestroy() { super.onDestroy() unregisterReceiver(receiver) } override fun onDeactivated(reason: Int) { Log.d(TAG, "Deactivated: $reason") Intent().also { intent -> intent.action = MERCHANT_NFC_DISCONNECTED sendBroadcast(intent) } } override fun processCommandApdu( commandApdu: ByteArray?, extras: Bundle? ): ByteArray { Log.d(TAG, "Processing command APDU") if (commandApdu == null) { Log.d(TAG, "APDU is null") return makeApduFailureResponse() } val stream = ByteArrayInputStream(commandApdu) val command = stream.read() if (command != 0) { Log.d(TAG, "APDU has invalid command") return makeApduFailureResponse() } val instruction = stream.read() // Read instruction parameters, currently ignored. stream.read() stream.read() if (instruction == SELECT_INS) { // FIXME: validate body! return makeApduSuccessResponse(ByteArray(0)) } if (instruction == GET_INS) { val req = queuedRequests.poll() return if (req != null) { Log.v(TAG, "sending tunnel request") makeApduSuccessResponse(req.toByteArray(Charsets.UTF_8)) } else { makeApduSuccessResponse(ByteArray(0)) } } if (instruction == PUT_INS) { val bodySize = readApduBodySize(stream) val talerInstr = stream.read() val bodyBytes = stream.readBytes() if (1 + bodyBytes.size != bodySize) { Log.w(TAG, "mismatched body size ($bodySize vs ${bodyBytes.size}") } when (talerInstr) { 1 -> { val url = String(bodyBytes, Charsets.UTF_8) Log.v(TAG, "got URL: '$url'") Intent(this, MainActivity::class.java).also { intent -> intent.data = Uri.parse(url) intent.action = Intent.ACTION_VIEW intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) startActivity(intent) } } 2 -> { Log.v(TAG, "got http response: ${bodyBytes.toString(Charsets.UTF_8)}") Intent().also { intent -> intent.action = HTTP_TUNNEL_RESPONSE intent.putExtra("response", bodyBytes.toString(Charsets.UTF_8)) sendBroadcast(intent) } } else -> { Log.v(TAG, "taler instruction $talerInstr unknown") } } return makeApduSuccessResponse(ByteArray(0)) } return makeApduFailureResponse() } companion object { const val TAG = "taler-wallet-hce" const val SELECT_INS = 0xA4 const val PUT_INS = 0xDA const val GET_INS = 0xCA const val TRIGGER_PAYMENT_ACTION = "net.taler.TRIGGER_PAYMENT_ACTION" const val MERCHANT_NFC_CONNECTED = "net.taler.MERCHANT_NFC_CONNECTED" const val MERCHANT_NFC_DISCONNECTED = "net.taler.MERCHANT_NFC_DISCONNECTED" const val HTTP_TUNNEL_RESPONSE = "net.taler.HTTP_TUNNEL_RESPONSE" const val HTTP_TUNNEL_REQUEST = "net.taler.HTTP_TUNNEL_REQUEST" } }