mirror of
https://github.com/status-im/keycard-connect.git
synced 2025-01-10 02:55:58 +00:00
add key creation/import
This commit is contained in:
parent
5bb2df6156
commit
a40d878eef
@ -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" />
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -64,6 +64,7 @@ class MainActivity : AppCompatActivity(), ScriptListener {
|
||||
override fun onScriptFinished(result: CardCommand.Result) {
|
||||
this.runOnUiThread {
|
||||
viewSwitcher.showNext()
|
||||
Registry.scriptExecutor.defaultScript = cardCheckupScript()
|
||||
}
|
||||
}
|
||||
|
||||
|
99
app/src/main/res/layout/activity_load_key.xml
Normal file
99
app/src/main/res/layout/activity_load_key.xml
Normal 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>
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user