add PUK handling

This commit is contained in:
Michele Balistreri 2019-11-04 13:24:19 +03:00
parent 43f74a85a2
commit fe4af3e5d8
No known key found for this signature in database
GPG Key ID: E9567DA33A4F791A
8 changed files with 193 additions and 9 deletions

View File

@ -16,7 +16,8 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
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.PairingActivity" />
<activity android:name=".ui.PINActivity" />

View File

@ -5,6 +5,7 @@ import java.io.IOException
import java.lang.Exception
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 {
try {
context.cmdSet.changePIN(newPIN).checkOK()

View File

@ -3,16 +3,15 @@ package im.status.keycard.connect.card
import android.app.Activity
import android.content.Intent
import im.status.keycard.connect.Registry
import im.status.keycard.connect.data.*
import im.status.keycard.connect.ui.PINActivity
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.REQ_INTERACTIVE_SCRIPT
import im.status.keycard.connect.ui.PUKActivity
import im.status.keycard.io.APDUException
import im.status.keycard.io.WrongPINException
import java.io.IOException
class VerifyPINCommand : CardCommand {
private var retries = -1
class VerifyPINCommand(private var retries: Int = -1) : CardCommand {
private var pukRetries = -1
private fun promptPIN(activity: Activity, instanceUID: ByteArray): CardCommand.Result {
val intent = Intent(activity, PINActivity::class.java).apply {
@ -25,9 +24,41 @@ class VerifyPINCommand : CardCommand {
return CardCommand.Result.UX_ONGOING
}
override fun run(context: CardScriptExecutor.ScriptContext): CardCommand.Result {
//TODO: handle retries == 0 with UNBLOCK PIN
private fun promptPUK(activity: Activity): CardCommand.Result {
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)
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)
}
}

View File

@ -5,6 +5,8 @@ const val PAIRING_ACTIVITY_PASSWORD = "pairingPassword"
const val PIN_ACTIVITY_ATTEMPTS = "remainingAttempts"
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_PUK = "initPUK"
const val INIT_ACTIVITY_PAIRING = "initPairing"

View File

@ -12,6 +12,10 @@ class PINCache {
private val pins: MutableMap<ByteArrayKey, String> = 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)
init {
@ -22,6 +26,15 @@ class PINCache {
val now: Long = System.currentTimeMillis()
timestamps.filterKeys { (now - it) < CACHE_VALIDITY }
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? {

View File

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

View 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>

View File

@ -10,4 +10,5 @@
<string name="title_activity_change_pin">ChangePINActivity</string>
<string name="change_pin_prompt">New PIN</string>
<string name="change_pin">Change PIN</string>
<string name="puk_prompt" >Insert your PUK</string>
</resources>