mirror of
https://github.com/status-im/keycard-connect.git
synced 2025-01-09 10:42:28 +00:00
add PUK handling
This commit is contained in:
parent
43f74a85a2
commit
fe4af3e5d8
@ -16,7 +16,8 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
<activity android:name=".ui.ChangePINActivity"></activity>
|
<activity android:name=".ui.PUKActivity"></activity>
|
||||||
|
<activity android:name=".ui.ChangePINActivity" />
|
||||||
<activity android:name=".ui.InitActivity" />
|
<activity android:name=".ui.InitActivity" />
|
||||||
<activity android:name=".ui.PairingActivity" />
|
<activity android:name=".ui.PairingActivity" />
|
||||||
<activity android:name=".ui.PINActivity" />
|
<activity android:name=".ui.PINActivity" />
|
||||||
|
@ -5,6 +5,7 @@ import java.io.IOException
|
|||||||
import java.lang.Exception
|
import java.lang.Exception
|
||||||
|
|
||||||
class ChangePINCommand(private val newPIN: String) : CardCommand {
|
class ChangePINCommand(private val newPIN: String) : CardCommand {
|
||||||
|
//TODO: like for the PINCache, no strings should be used here
|
||||||
override fun run(context: CardScriptExecutor.ScriptContext): CardCommand.Result {
|
override fun run(context: CardScriptExecutor.ScriptContext): CardCommand.Result {
|
||||||
try {
|
try {
|
||||||
context.cmdSet.changePIN(newPIN).checkOK()
|
context.cmdSet.changePIN(newPIN).checkOK()
|
||||||
|
@ -3,16 +3,15 @@ package im.status.keycard.connect.card
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import im.status.keycard.connect.Registry
|
import im.status.keycard.connect.Registry
|
||||||
|
import im.status.keycard.connect.data.*
|
||||||
import im.status.keycard.connect.ui.PINActivity
|
import im.status.keycard.connect.ui.PINActivity
|
||||||
import im.status.keycard.connect.data.PIN_ACTIVITY_ATTEMPTS
|
import im.status.keycard.connect.ui.PUKActivity
|
||||||
import im.status.keycard.connect.data.PIN_ACTIVITY_CARD_UID
|
|
||||||
import im.status.keycard.connect.data.REQ_INTERACTIVE_SCRIPT
|
|
||||||
import im.status.keycard.io.APDUException
|
import im.status.keycard.io.APDUException
|
||||||
import im.status.keycard.io.WrongPINException
|
import im.status.keycard.io.WrongPINException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class VerifyPINCommand : CardCommand {
|
class VerifyPINCommand(private var retries: Int = -1) : CardCommand {
|
||||||
private var retries = -1
|
private var pukRetries = -1
|
||||||
|
|
||||||
private fun promptPIN(activity: Activity, instanceUID: ByteArray): CardCommand.Result {
|
private fun promptPIN(activity: Activity, instanceUID: ByteArray): CardCommand.Result {
|
||||||
val intent = Intent(activity, PINActivity::class.java).apply {
|
val intent = Intent(activity, PINActivity::class.java).apply {
|
||||||
@ -25,9 +24,41 @@ class VerifyPINCommand : CardCommand {
|
|||||||
return CardCommand.Result.UX_ONGOING
|
return CardCommand.Result.UX_ONGOING
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun run(context: CardScriptExecutor.ScriptContext): CardCommand.Result {
|
private fun promptPUK(activity: Activity): CardCommand.Result {
|
||||||
//TODO: handle retries == 0 with UNBLOCK PIN
|
val intent = Intent(activity, PUKActivity::class.java).apply {
|
||||||
|
putExtra(PUK_ACTIVITY_ATTEMPTS, pukRetries)
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.startActivityForResult(intent, REQ_INTERACTIVE_SCRIPT)
|
||||||
|
|
||||||
|
return CardCommand.Result.UX_ONGOING
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unblockPIN(context: CardScriptExecutor.ScriptContext): CardCommand.Result {
|
||||||
|
val pukAndPIN: Pair<String, String>? = Registry.pinCache.pukAndPIN
|
||||||
|
|
||||||
|
if (pukAndPIN != null) {
|
||||||
|
try {
|
||||||
|
context.cmdSet.unblockPIN(pukAndPIN.first, pukAndPIN.second).checkAuthOK()
|
||||||
|
Registry.pinCache.putPIN(context.cmdSet.applicationInfo.instanceUID, pukAndPIN.second)
|
||||||
|
retries = -1
|
||||||
|
pukRetries = -1
|
||||||
|
return CardCommand.Result.OK
|
||||||
|
} catch (e: WrongPINException) {
|
||||||
|
pukRetries = e.retryAttempts
|
||||||
|
} catch(e: IOException) {
|
||||||
|
return CardCommand.Result.RETRY
|
||||||
|
} catch(e: APDUException) {
|
||||||
|
return CardCommand.Result.CANCEL
|
||||||
|
} finally {
|
||||||
|
Registry.pinCache.pukAndPIN = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return promptPUK(context.activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun verifyPIN(context: CardScriptExecutor.ScriptContext): CardCommand.Result {
|
||||||
val pin = Registry.pinCache.getPIN(context.cmdSet.applicationInfo.instanceUID)
|
val pin = Registry.pinCache.getPIN(context.cmdSet.applicationInfo.instanceUID)
|
||||||
|
|
||||||
if (pin != null) {
|
if (pin != null) {
|
||||||
@ -45,6 +76,10 @@ class VerifyPINCommand : CardCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return promptPIN(context.activity, context.cmdSet.applicationInfo.instanceUID)
|
return if (retries == 0) promptPUK(context.activity) else promptPIN(context.activity, context.cmdSet.applicationInfo.instanceUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun run(context: CardScriptExecutor.ScriptContext): CardCommand.Result {
|
||||||
|
return if (retries == 0) unblockPIN(context) else verifyPIN(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,6 +5,8 @@ const val PAIRING_ACTIVITY_PASSWORD = "pairingPassword"
|
|||||||
const val PIN_ACTIVITY_ATTEMPTS = "remainingAttempts"
|
const val PIN_ACTIVITY_ATTEMPTS = "remainingAttempts"
|
||||||
const val PIN_ACTIVITY_CARD_UID = "cardUID"
|
const val PIN_ACTIVITY_CARD_UID = "cardUID"
|
||||||
|
|
||||||
|
const val PUK_ACTIVITY_ATTEMPTS = PIN_ACTIVITY_ATTEMPTS
|
||||||
|
|
||||||
const val INIT_ACTIVITY_PIN = "initPIN"
|
const val INIT_ACTIVITY_PIN = "initPIN"
|
||||||
const val INIT_ACTIVITY_PUK = "initPUK"
|
const val INIT_ACTIVITY_PUK = "initPUK"
|
||||||
const val INIT_ACTIVITY_PAIRING = "initPairing"
|
const val INIT_ACTIVITY_PAIRING = "initPairing"
|
||||||
|
@ -12,6 +12,10 @@ class PINCache {
|
|||||||
private val pins: MutableMap<ByteArrayKey, String> = HashMap()
|
private val pins: MutableMap<ByteArrayKey, String> = HashMap()
|
||||||
private val timestamps: MutableMap<Long, ByteArrayKey> = HashMap()
|
private val timestamps: MutableMap<Long, ByteArrayKey> = HashMap()
|
||||||
|
|
||||||
|
//This is needed to avoid passing PUK and new PIN with Intents, which could make unwanted copies
|
||||||
|
var pukAndPIN: Pair<String, String>? = null
|
||||||
|
private var latestPUKandPINHashCode: Int = 0
|
||||||
|
|
||||||
private val scheduler: ScheduledExecutorService = Executors.newScheduledThreadPool(1)
|
private val scheduler: ScheduledExecutorService = Executors.newScheduledThreadPool(1)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -22,6 +26,15 @@ class PINCache {
|
|||||||
val now: Long = System.currentTimeMillis()
|
val now: Long = System.currentTimeMillis()
|
||||||
timestamps.filterKeys { (now - it) < CACHE_VALIDITY }
|
timestamps.filterKeys { (now - it) < CACHE_VALIDITY }
|
||||||
pins.filterKeys { timestamps.containsValue(it) }
|
pins.filterKeys { timestamps.containsValue(it) }
|
||||||
|
|
||||||
|
//whatever happens, lets not leave PUK in cache more than 2 cache cleaning cycles
|
||||||
|
if (pukAndPIN != null) {
|
||||||
|
if (latestPUKandPINHashCode == pukAndPIN.hashCode()) {
|
||||||
|
pukAndPIN = null
|
||||||
|
} else {
|
||||||
|
latestPUKandPINHashCode = pukAndPIN.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPIN(instanceUID: ByteArray): String? {
|
fun getPIN(instanceUID: ByteArray): String? {
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
package im.status.keycard.connect.ui
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.TextView
|
||||||
|
import im.status.keycard.connect.R
|
||||||
|
import im.status.keycard.connect.Registry
|
||||||
|
import im.status.keycard.connect.data.PIN_ACTIVITY_ATTEMPTS
|
||||||
|
import im.status.keycard.connect.data.PIN_ACTIVITY_CARD_UID
|
||||||
|
import im.status.keycard.connect.data.PUK_ACTIVITY_ATTEMPTS
|
||||||
|
|
||||||
|
class PUKActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
//TODO: validate PUK length == 12
|
||||||
|
//TODO: validate PIN length == 6
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_puk)
|
||||||
|
val attempts = intent.getIntExtra(PUK_ACTIVITY_ATTEMPTS, -1)
|
||||||
|
|
||||||
|
val attemptLabel = findViewById<TextView>(R.id.attemptLabel)
|
||||||
|
|
||||||
|
if (attempts == -1) {
|
||||||
|
attemptLabel.text = ""
|
||||||
|
} else {
|
||||||
|
attemptLabel.text = getString(R.string.pin_attempts, attempts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ok(@Suppress("UNUSED_PARAMETER") view: View) {
|
||||||
|
val pukText = findViewById<EditText>(R.id.pukText)
|
||||||
|
val pinText = findViewById<EditText>(R.id.newPINText)
|
||||||
|
|
||||||
|
Registry.pinCache.pukAndPIN = Pair(pukText.text.toString(), pinText.text.toString())
|
||||||
|
setResult(Activity.RESULT_OK)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancel(@Suppress("UNUSED_PARAMETER") view: View) {
|
||||||
|
setResult(Activity.RESULT_CANCELED)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
84
app/src/main/res/layout/activity_puk.xml
Normal file
84
app/src/main/res/layout/activity_puk.xml
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".ui.PUKActivity">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pukPrompt"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:text="@string/puk_prompt"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.501"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/pukText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:ems="10"
|
||||||
|
android:inputType="numberPassword"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/pukPrompt" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/newPIN"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="52dp"
|
||||||
|
android:text="@string/change_pin_prompt"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.498"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/pukText" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/newPINText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:ems="10"
|
||||||
|
android:inputType="numberPassword"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/newPIN" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/attemptLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="36dp"
|
||||||
|
android:text="@string/pin_attempts"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/newPINText" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/okButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="60dp"
|
||||||
|
android:layout_marginEnd="68dp"
|
||||||
|
android:onClick="ok"
|
||||||
|
android:text="@android:string/ok"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/attemptLabel" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/cancelButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="68dp"
|
||||||
|
android:layout_marginTop="60dp"
|
||||||
|
android:onClick="cancel"
|
||||||
|
android:text="@android:string/cancel"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/attemptLabel" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -10,4 +10,5 @@
|
|||||||
<string name="title_activity_change_pin">ChangePINActivity</string>
|
<string name="title_activity_change_pin">ChangePINActivity</string>
|
||||||
<string name="change_pin_prompt">New PIN</string>
|
<string name="change_pin_prompt">New PIN</string>
|
||||||
<string name="change_pin">Change PIN</string>
|
<string name="change_pin">Change PIN</string>
|
||||||
|
<string name="puk_prompt" >Insert your PUK</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user