add key creation/import

This commit is contained in:
Michele Balistreri 2019-11-21 18:45:49 +03:00
parent 5bb2df6156
commit a40d878eef
No known key found for this signature in database
GPG Key ID: E9567DA33A4F791A
12 changed files with 231 additions and 43 deletions

View File

@ -17,7 +17,8 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".ui.SignTransactionActivity"></activity>
<activity android:name=".ui.LoadKeyActivity"></activity>
<activity android:name=".ui.SignTransactionActivity" />
<activity android:name=".ui.SignMessageActivity" />
<activity android:name=".ui.PUKActivity" />
<activity android:name=".ui.ChangePINActivity" />

View File

@ -7,15 +7,9 @@ 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 {
return runOnCard {
context.cmdSet.changePIN(newPIN).checkOK()
Registry.pinCache.putPIN(context.cmdSet.applicationInfo.instanceUID, newPIN)
} catch(e: IOException) {
return CardCommand.Result.RETRY
} catch (e: Exception) {
return CardCommand.Result.CANCEL
}
return CardCommand.Result.OK
}
}

View File

@ -13,7 +13,7 @@ class ExportKeyCommand(private val listener: Listener, private val path: String?
}
override fun run(context: CardScriptExecutor.ScriptContext): CardCommand.Result {
try {
return runOnCard {
var response: APDUResponse? = null
if (path != null) {
@ -29,12 +29,6 @@ class ExportKeyCommand(private val listener: Listener, private val path: String?
}
listener.onResponse(BIP32KeyPair.fromTLV(response?.checkOK()?.data))
} catch(e: IOException) {
return CardCommand.Result.RETRY
} catch (e: Exception) {
return CardCommand.Result.CANCEL
}
return CardCommand.Result.OK
}
}

View File

@ -1,25 +1,62 @@
package im.status.keycard.connect.card
import android.app.Activity
import android.content.Intent
import im.status.keycard.applet.KeycardCommandSet
import im.status.keycard.applet.Mnemonic
import im.status.keycard.connect.data.*
import im.status.keycard.connect.ui.LoadKeyActivity
import java.io.IOException
import java.lang.Exception
class LoadKeyCommand : CardCommand {
class LoadKeyCommand(private var loadType: Int = LOAD_NONE, private var mnemonic: String? = null) : CardCommand {
private fun promptKey(activity: Activity) : CardCommand.Result {
val intent = Intent(activity, LoadKeyActivity::class.java)
activity.startActivityForResult(intent, REQ_INTERACTIVE_SCRIPT)
return CardCommand.Result.UX_ONGOING
}
private fun showMnemonic(activity: Activity, m: Mnemonic) {
//TODO: implement show mnemonic screen
println(m.toMnemonicPhrase())
}
private fun generateKey(cmdSet: KeycardCommandSet): CardCommand.Result {
return runOnCard {
cmdSet.generateKey().checkOK()
}
}
private fun generateBIP39(activity: Activity, cmdSet: KeycardCommandSet): CardCommand.Result {
return runOnCard {
val m = Mnemonic(cmdSet.generateMnemonic(KeycardCommandSet.GENERATE_MNEMONIC_12_WORDS).checkOK().data)
m.fetchBIP39EnglishWordlist()
cmdSet.loadKey(m.toBIP32KeyPair()).checkOK()
showMnemonic(activity, m)
}
}
private fun importBIP39(cmdSet: KeycardCommandSet): CardCommand.Result {
return runOnCard {
cmdSet.loadKey(Mnemonic.toBinarySeed(mnemonic, "")).checkOK()
}
}
override fun run(context: CardScriptExecutor.ScriptContext): CardCommand.Result {
/* TODO: this should instead prompt and ask if
* 1. You want to generate keys on card with no backup (most secure)
* 2. You want to generate a new key with backup phrase
* 3. You want to import an existing key
*/
try {
context.cmdSet.generateKey().checkOK()
} catch(e: IOException) {
return CardCommand.Result.RETRY
} catch (e: Exception) {
return CardCommand.Result.CANCEL
return when (loadType) {
LOAD_NONE -> promptKey(context.activity)
LOAD_GENERATE -> generateKey(context.cmdSet)
LOAD_GENERATE_BIP39 -> generateBIP39(context.activity, context.cmdSet)
LOAD_IMPORT_BIP39 -> importBIP39(context.cmdSet)
else -> CardCommand.Result.CANCEL
}
}
return CardCommand.Result.OK
override fun onDataReceived(data: Intent?) {
loadType = data?.getIntExtra(LOAD_TYPE, LOAD_NONE) ?: LOAD_NONE
if (loadType == LOAD_IMPORT_BIP39) {
mnemonic = data?.getStringExtra(LOAD_MNEMONIC)
}
}
}

View File

@ -1,5 +1,20 @@
package im.status.keycard.connect.card
import java.io.IOException
import java.lang.Exception
fun scriptWithSecureChannel(): List<CardCommand> = listOf(SelectCommand(), InitCommand(), OpenSecureChannelCommand())
fun scriptWithAuthentication(): List<CardCommand> = scriptWithSecureChannel().plus(VerifyPINCommand())
fun cardCheckupScript(): List<CardCommand> = scriptWithSecureChannel().plus(CheckMasterKeyCommand()).plus(VerifyPINCommand()).plus(LoadKeyCommand())
fun runOnCard(body: () -> Unit) : CardCommand.Result {
try {
body()
} catch(e: IOException) {
return CardCommand.Result.RETRY
} catch (e: Exception) {
return CardCommand.Result.CANCEL
}
return CardCommand.Result.OK
}

View File

@ -6,14 +6,8 @@ import java.lang.Exception
class SelectCommand : CardCommand {
override fun run(context: CardScriptExecutor.ScriptContext): CardCommand.Result {
//TODO: handle not-installed-applet/not-a-keycard
try {
return runOnCard {
context.cmdSet.select().checkOK()
} catch(e: IOException) {
return CardCommand.Result.RETRY
} catch (e: Exception) {
return CardCommand.Result.CANCEL
}
return CardCommand.Result.OK
}
}

View File

@ -13,7 +13,7 @@ class SignCommand(private val listener: Listener, private val hash: ByteArray, p
}
override fun run(context: CardScriptExecutor.ScriptContext): CardCommand.Result {
try {
return runOnCard {
var response: APDUResponse? = null
if (path != null) {
@ -34,12 +34,6 @@ class SignCommand(private val listener: Listener, private val hash: ByteArray, p
val signature = RecoverableSignature(hash, response?.checkOK()?.data)
listener.onResponse(signature)
} catch(e: IOException) {
return CardCommand.Result.RETRY
} catch (e: Exception) {
return CardCommand.Result.CANCEL
}
return CardCommand.Result.OK
}
}

View File

@ -18,6 +18,14 @@ const val SIGN_TX_CURRENCY = "signTxCurrency"
const val SIGN_TX_DATA = "signTxData"
const val SIGN_TX_TO = "signTxTo"
const val LOAD_TYPE = "loadKeyType"
const val LOAD_NONE = -1
const val LOAD_IMPORT_BIP39 = 0
const val LOAD_GENERATE_BIP39 = 1
const val LOAD_GENERATE = 2
const val LOAD_MNEMONIC = "loadKeyMnemonic"
const val REQ_INTERACTIVE_SCRIPT = 0x01
const val REQ_WALLETCONNECT = 0x02

View File

@ -0,0 +1,45 @@
package im.status.keycard.connect.ui
import android.app.Activity
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.EditText
import im.status.keycard.connect.R
import im.status.keycard.connect.data.*
class LoadKeyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_load_key)
}
fun generateBIP39(@Suppress("UNUSED_PARAMETER") view: View) {
val intent = Intent()
intent.putExtra(LOAD_TYPE, LOAD_GENERATE_BIP39)
setResult(Activity.RESULT_OK, intent)
finish()
}
fun generateOnCard(@Suppress("UNUSED_PARAMETER") view: View) {
val intent = Intent()
intent.putExtra(LOAD_TYPE, LOAD_GENERATE)
setResult(Activity.RESULT_OK, intent)
finish()
}
fun importBIP39(@Suppress("UNUSED_PARAMETER") view: View) {
val intent = Intent()
intent.putExtra(LOAD_TYPE, LOAD_IMPORT_BIP39)
intent.putExtra(LOAD_MNEMONIC, findViewById<EditText>(R.id.importMnemonicText).text.toString())
setResult(Activity.RESULT_OK, intent)
finish()
}
fun cancel(@Suppress("UNUSED_PARAMETER") view: View) {
setResult(Activity.RESULT_CANCELED)
finish()
}
}

View File

@ -64,6 +64,7 @@ class MainActivity : AppCompatActivity(), ScriptListener {
override fun onScriptFinished(result: CardCommand.Result) {
this.runOnUiThread {
viewSwitcher.showNext()
Registry.scriptExecutor.defaultScript = cardCheckupScript()
}
}

View File

@ -0,0 +1,99 @@
<?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.LoadKeyActivity">
<TextView
android:id="@+id/generateMnemonicLabel"
android:layout_width="323dp"
android:layout_height="45dp"
android:layout_marginTop="48dp"
android:text="@string/load_generate_mnemonic_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/generateBIP39Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:onClick="generateBIP39"
android:text="@string/load_generate_mnemonic"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/generateMnemonicLabel" />
<TextView
android:id="@+id/importMnemonicLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="@string/load_import_mnemonic_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/generateBIP39Button" />
<Button
android:id="@+id/importMnemonic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:onClick="importBIP39"
android:text="@string/load_import_mnemonic"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.496"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/importMnemonicText" />
<EditText
android:id="@+id/importMnemonicText"
android:layout_width="370dp"
android:layout_height="130dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="20dp"
android:ems="10"
android:gravity="start|top"
android:inputType="textMultiLine"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/importMnemonicLabel" />
<TextView
android:id="@+id/generateOnCardLabel"
android:layout_width="347dp"
android:layout_height="38dp"
android:layout_marginTop="32dp"
android:text="@string/load_generate_on_card_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/importMnemonic" />
<Button
android:id="@+id/generateButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:onClick="generateOnCard"
android:text="@string/load_key_generate_oncard"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/generateOnCardLabel" />
<Button
android:id="@+id/cancelButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:onClick="cancel"
android:text="@android:string/cancel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/generateButton" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -18,4 +18,10 @@
<string name="sign_text_label">Sign a message</string>
<string name="sign_tx_label">Sign transaction</string>
<string name="sign_tx_to_label">To</string>
<string name="load_key_generate_oncard">Generate on card</string>
<string name="load_generate_on_card_label" >Generate keys on card without BIP39 mnemonic. Maximum security, but no backup possible.</string>
<string name="load_import_mnemonic" >Import Mnemonic</string>
<string name="load_import_mnemonic_label">Import BIP39 Mnemonic</string>
<string name="load_generate_mnemonic" >Generate BIP39 Mnemonic</string>
<string name="load_generate_mnemonic_label">Generate and load BIP39 mnemonic. You will be shown the mnemonic after it is loaded</string>
</resources>