|
|
|
@@ -11,6 +11,7 @@ import android.nfc.cardemulation.HostApduService
|
|
|
|
|
import android.os.Bundle
|
|
|
|
|
import android.util.Log
|
|
|
|
|
import androidx.preference.PreferenceManager
|
|
|
|
|
import kotlinx.coroutines.withContext
|
|
|
|
|
import org.xmlpull.v1.XmlPullParser
|
|
|
|
|
import java.io.File
|
|
|
|
|
import java.io.UnsupportedEncodingException
|
|
|
|
@@ -36,17 +37,31 @@ class MyHostApduService : HostApduService() {
|
|
|
|
|
0x82.toByte(), // SW2 Status byte 2 - Command processing qualifier
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
private var selectedNdefFile = 0
|
|
|
|
|
private val selectedNdefFilePath get() = String.format("%04X", this.selectedNdefFile)
|
|
|
|
|
private val A_CLASS_NOT_SUPPORTED = byteArrayOf(
|
|
|
|
|
0x6E.toByte(),
|
|
|
|
|
0x00.toByte(),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
private var selectedNdefFile = 0
|
|
|
|
|
private var validatedNdefFile = 0
|
|
|
|
|
private var validatedNdefFileType = 0
|
|
|
|
|
private val selectedNdefFilePath get() = String.format("%04X", this.selectedNdefFile)
|
|
|
|
|
private var retry = 3
|
|
|
|
|
|
|
|
|
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
|
|
|
Log.i(TAG, "onStartCommand()")
|
|
|
|
|
selectedNdefFile = 0
|
|
|
|
|
resetVars()
|
|
|
|
|
|
|
|
|
|
return Service.START_STICKY
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun resetVars() {
|
|
|
|
|
selectedNdefFile = 0
|
|
|
|
|
retry = 3
|
|
|
|
|
validatedNdefFile = 0
|
|
|
|
|
validatedNdefFileType = 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@OptIn(ExperimentalStdlibApi::class)
|
|
|
|
|
override fun processCommandApdu(commandApdu: ByteArray, extras: Bundle?): ByteArray {
|
|
|
|
|
//
|
|
|
|
@@ -57,20 +72,27 @@ class MyHostApduService : HostApduService() {
|
|
|
|
|
val apduDissect = parseApdu(commandApdu)
|
|
|
|
|
Log.i(TAG, apduDissect.toString())
|
|
|
|
|
|
|
|
|
|
if(apduDissect["CLA"] != "00") { return A_CLASS_NOT_SUPPORTED }
|
|
|
|
|
when (apduDissect["INS"]) {
|
|
|
|
|
"A4" -> { //ADPU_SELECT
|
|
|
|
|
Log.i(TAG, "APDU_SELECT triggered.")
|
|
|
|
|
when (apduDissect["P1"]) {
|
|
|
|
|
"04" -> { //Select by name
|
|
|
|
|
when (apduDissect["Data"]) {
|
|
|
|
|
if(apduDissect["P2"] != "00") { return A_ERROR }
|
|
|
|
|
return when (apduDissect["Data"]) {
|
|
|
|
|
resources.getString(R.string.NDEF_AID) -> {
|
|
|
|
|
Log.i(TAG, "NDEF App")
|
|
|
|
|
return A_OKAY
|
|
|
|
|
A_OKAY
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
|
A_ERROR
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"00" -> { //Select by ID
|
|
|
|
|
if(apduDissect["P2"] != "0C") { return A_ERROR }
|
|
|
|
|
when (apduDissect["Data"]) {
|
|
|
|
|
"E103" -> { //CCFile
|
|
|
|
|
Log.i(TAG, "NDEF file " + apduDissect["Data"] + "(CCF) selected")
|
|
|
|
@@ -82,7 +104,7 @@ class MyHostApduService : HostApduService() {
|
|
|
|
|
if(apduDissect["Data"]!!.toInt(16) < 0xE1FF) {
|
|
|
|
|
Log.i(
|
|
|
|
|
TAG,
|
|
|
|
|
"NDEF file " + apduDissect["Data"] + "(NDEF File) selected"
|
|
|
|
|
"NDEF file " + apduDissect["Data"] + " (NDEF File) selected"
|
|
|
|
|
)
|
|
|
|
|
selectedNdefFile = apduDissect["Data"].toString().hexToInt()
|
|
|
|
|
return A_OKAY
|
|
|
|
@@ -164,9 +186,102 @@ class MyHostApduService : HostApduService() {
|
|
|
|
|
return A_OKAY
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"20" -> {//Verify
|
|
|
|
|
Log.i(TAG,"Verify")
|
|
|
|
|
if(selectedNdefFile == 0) return byteArrayOf(0x69.toByte(),0x85.toByte())
|
|
|
|
|
val passwordIdentification = apduDissect["P1"].plus(apduDissect["P2"]).toInt(16)
|
|
|
|
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
|
|
|
|
if (apduDissect["Lc"]?.toInt(16) == 0) {
|
|
|
|
|
//is a password required
|
|
|
|
|
return when(passwordIdentification){
|
|
|
|
|
0x0001 -> if(!needPasswordRead()) A_OKAY else byteArrayOf(0x63.toByte(),0x00.toByte())
|
|
|
|
|
0x0002 -> if(!needPasswordWrite()) A_OKAY else byteArrayOf(0x63.toByte(),0x00.toByte())
|
|
|
|
|
else -> A_ERROR
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else{
|
|
|
|
|
if(retry <= 0) return byteArrayOf(0x63.toByte(),0x00.toByte())
|
|
|
|
|
val storedPassword = when(passwordIdentification){
|
|
|
|
|
0x0001 -> sharedPreferences.getString(String.format("ndef_file_%S_read_password",selectedNdefFilePath),"00") ?: "00"
|
|
|
|
|
0x0002 -> sharedPreferences.getString(String.format("ndef_file_%S_write_password",selectedNdefFilePath),"00") ?: "00"
|
|
|
|
|
else -> return A_ERROR
|
|
|
|
|
}
|
|
|
|
|
return if(storedPassword == apduDissect["Data"]) {
|
|
|
|
|
validatedNdefFile = selectedNdefFile
|
|
|
|
|
validatedNdefFileType = passwordIdentification
|
|
|
|
|
A_OKAY
|
|
|
|
|
} else{
|
|
|
|
|
retry-=1
|
|
|
|
|
byteArrayOf(0x63.toByte(),(0xC0+retry).toByte())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"24" -> {//Change Reference Data
|
|
|
|
|
Log.i(TAG,"Set password")
|
|
|
|
|
if(selectedNdefFile == 0) return byteArrayOf(0x69.toByte(),0x85.toByte())
|
|
|
|
|
if(selectedNdefFile in intArrayOf(0xE101, 0xE103)) return byteArrayOf(0x6A.toByte(),0x80.toByte())
|
|
|
|
|
val passwordIdentification = apduDissect["P1"].plus(apduDissect["P2"]).toInt(16)
|
|
|
|
|
when(passwordIdentification){
|
|
|
|
|
0x0001 -> if(!allowedRead()) return byteArrayOf(0x69.toByte(),0x82.toByte())
|
|
|
|
|
0x0002 -> if(!allowedWrite()) return byteArrayOf(0x69.toByte(),0x82.toByte())
|
|
|
|
|
else -> return byteArrayOf(0x64.toByte(),0x86.toByte())
|
|
|
|
|
}
|
|
|
|
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this).edit()
|
|
|
|
|
if(apduDissect["Lc"]?.toInt(16) != 0x10) return byteArrayOf(0x65.toByte(),0x81.toByte())
|
|
|
|
|
when(passwordIdentification){
|
|
|
|
|
0x0001 -> sharedPreferences.putString(String.format("ndef_file_%S_read_password",selectedNdefFilePath),apduDissect["Data"])
|
|
|
|
|
0x0002 -> sharedPreferences.putString(String.format("ndef_file_%S_write_password",selectedNdefFilePath),apduDissect["Data"])
|
|
|
|
|
}
|
|
|
|
|
sharedPreferences.apply()
|
|
|
|
|
return A_OKAY
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"28" -> {//enable verification
|
|
|
|
|
Log.i(TAG,"Enable password")
|
|
|
|
|
if(selectedNdefFile == 0) return byteArrayOf(0x69.toByte(),0x85.toByte())
|
|
|
|
|
if(selectedNdefFile in intArrayOf(0xE101, 0xE103)) return byteArrayOf(0x6A.toByte(),0x80.toByte())
|
|
|
|
|
val passwordIdentification = apduDissect["P1"].plus(apduDissect["P2"]).toInt(16)
|
|
|
|
|
if(passwordIdentification !in intArrayOf(0x0001,0x0002)) return byteArrayOf(0x6A.toByte(),0x86.toByte())
|
|
|
|
|
when(passwordIdentification){ //check perm
|
|
|
|
|
0x0001 -> if(!allowedRead()) return byteArrayOf(0x69.toByte(),0x82.toByte())
|
|
|
|
|
0x0002 -> if(!allowedWrite()) return byteArrayOf(0x69.toByte(),0x82.toByte())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this).edit()
|
|
|
|
|
when(passwordIdentification){ //check perm
|
|
|
|
|
0x0001 -> sharedPreferences.putBoolean(String.format("ndef_file_%S_read",selectedNdefFilePath),true)
|
|
|
|
|
0x0002 -> sharedPreferences.putBoolean(String.format("ndef_file_%S_read",selectedNdefFilePath),true)
|
|
|
|
|
}
|
|
|
|
|
sharedPreferences.apply()
|
|
|
|
|
return A_OKAY
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"26" -> {//enable verification
|
|
|
|
|
Log.i(TAG,"Disable password")
|
|
|
|
|
if(selectedNdefFile == 0) return byteArrayOf(0x69.toByte(),0x85.toByte())
|
|
|
|
|
if(selectedNdefFile in intArrayOf(0xE101, 0xE103)) return byteArrayOf(0x6A.toByte(),0x80.toByte())
|
|
|
|
|
val passwordIdentification = apduDissect["P1"].plus(apduDissect["P2"]).toInt(16)
|
|
|
|
|
if(passwordIdentification !in intArrayOf(0x0001,0x0002)) return byteArrayOf(0x6A.toByte(),0x86.toByte())
|
|
|
|
|
when(passwordIdentification){ //check perm
|
|
|
|
|
0x0001 -> if(!allowedRead()) return byteArrayOf(0x69.toByte(),0x82.toByte())
|
|
|
|
|
0x0002 -> if(!allowedWrite()) return byteArrayOf(0x69.toByte(),0x82.toByte())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this).edit()
|
|
|
|
|
when(passwordIdentification){ //check perm
|
|
|
|
|
0x0001 -> sharedPreferences.putBoolean(String.format("ndef_file_%S_read",selectedNdefFilePath),false)
|
|
|
|
|
0x0002 -> sharedPreferences.putBoolean(String.format("ndef_file_%S_read",selectedNdefFilePath),false)
|
|
|
|
|
}
|
|
|
|
|
sharedPreferences.apply()
|
|
|
|
|
return A_OKAY
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
|
Log.w(TAG, "Unknown INS : " + apduDissect["INS"])
|
|
|
|
|
return A_ERROR
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
@@ -176,6 +291,28 @@ class MyHostApduService : HostApduService() {
|
|
|
|
|
return A_ERROR
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun needPasswordRead(): Boolean {
|
|
|
|
|
if(selectedNdefFile in intArrayOf(0xE101,0xE103)) return false
|
|
|
|
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
|
|
|
|
return !sharedPreferences.getBoolean(String.format("ndef_file_%S_read",selectedNdefFilePath),true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun needPasswordWrite(): Boolean {
|
|
|
|
|
if(selectedNdefFile in intArrayOf(0xE101,0xE103)) return true
|
|
|
|
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
|
|
|
|
return !sharedPreferences.getBoolean(String.format("ndef_file_%S_read",selectedNdefFilePath),true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun allowedRead(): Boolean {
|
|
|
|
|
if(selectedNdefFile in intArrayOf(0xE101,0xE103)) return true
|
|
|
|
|
return (validatedNdefFile == selectedNdefFile) and (validatedNdefFileType == 0x0001)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun allowedWrite(): Boolean {
|
|
|
|
|
if(selectedNdefFile in intArrayOf(0xE101,0xE103)) return false
|
|
|
|
|
return (validatedNdefFile == selectedNdefFile) and (validatedNdefFileType == 0x0002)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun makeCCFileApdu(): ByteArray {
|
|
|
|
|
val ccFile = CCFile()
|
|
|
|
|
val ccHashMap = parseCCFile()
|
|
|
|
@@ -256,7 +393,7 @@ class MyHostApduService : HostApduService() {
|
|
|
|
|
|
|
|
|
|
override fun onDeactivated(reason: Int) {
|
|
|
|
|
Log.i(TAG, "onDeactivated() Fired! Reason: $reason")
|
|
|
|
|
selectedNdefFile = 0
|
|
|
|
|
resetVars()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private val HEX_CHARS = "0123456789ABCDEF".toCharArray()
|
|
|
|
|