attempt at implementing password protection

android apps don't send the commands to the ndef app so it doesn't work
This commit is contained in:
2024-10-31 19:03:37 +01:00
parent d112bc38c5
commit beb9cd6274
4 changed files with 148 additions and 10 deletions

View File

@@ -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()

View File

@@ -12,4 +12,5 @@
<!-- Sync Preferences -->
<string name="settings">Settings</string>
<string name="current_file_id">Current file id : %S (%04X)</string>
<string name="NDEF_AID2" translatable="false">D2760000850100</string>
</resources>

View File

@@ -5,6 +5,6 @@
android:requireDeviceUnlock="false">
<aid-group android:description="@string/aiddescription"
android:category="other">
<aid-filter android:name="D2760000850101" />
<aid-filter android:name="@string/NDEF_AID" />
</aid-group>
</host-apdu-service>

View File

@@ -11,8 +11,8 @@
<MLc>0x00FF</MLc>
<!--Section 4.7.3 specifies the content of the NDEF-File_Ctrl_TLV block that contains information to control and manage the NDEF File for Mapping Version 2.0 (see Section 4.5).-->
<NDEF-File_Ctrl_TLV>
<T>0x04</T>
<L>0x06</L>
<T>0x04</T> <!--NDEF-File_Ctrl_TLV-->
<L>0x06</L> <!--Size-->
<V>
<NDEF_FILE_ID>0xE104</NDEF_FILE_ID>
<NDEF_FILE_SIZE>0x7FFF</NDEF_FILE_SIZE>