From a40d878eef74cb272f288905705a204c1506a032 Mon Sep 17 00:00:00 2001 From: Michele Balistreri Date: Thu, 21 Nov 2019 18:45:49 +0300 Subject: [PATCH] add key creation/import --- app/src/main/AndroidManifest.xml | 3 +- .../keycard/connect/card/ChangePINCommand.kt | 8 +- .../keycard/connect/card/ExportKeyCommand.kt | 8 +- .../keycard/connect/card/LoadKeyCommand.kt | 65 +++++++++--- .../im/status/keycard/connect/card/Script.kt | 15 +++ .../keycard/connect/card/SelectCommand.kt | 8 +- .../keycard/connect/card/SignCommand.kt | 8 +- .../status/keycard/connect/data/Constants.kt | 8 ++ .../keycard/connect/ui/LoadKeyActivity.kt | 45 +++++++++ .../status/keycard/connect/ui/MainActivity.kt | 1 + app/src/main/res/layout/activity_load_key.xml | 99 +++++++++++++++++++ app/src/main/res/values/strings.xml | 6 ++ 12 files changed, 231 insertions(+), 43 deletions(-) create mode 100644 app/src/main/java/im/status/keycard/connect/ui/LoadKeyActivity.kt create mode 100644 app/src/main/res/layout/activity_load_key.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fddf0cf..e74d4df 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,7 +17,8 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> - + + diff --git a/app/src/main/java/im/status/keycard/connect/card/ChangePINCommand.kt b/app/src/main/java/im/status/keycard/connect/card/ChangePINCommand.kt index 241a5b8..ad835a4 100644 --- a/app/src/main/java/im/status/keycard/connect/card/ChangePINCommand.kt +++ b/app/src/main/java/im/status/keycard/connect/card/ChangePINCommand.kt @@ -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 } } \ No newline at end of file diff --git a/app/src/main/java/im/status/keycard/connect/card/ExportKeyCommand.kt b/app/src/main/java/im/status/keycard/connect/card/ExportKeyCommand.kt index 499bb91..2a4c1e6 100644 --- a/app/src/main/java/im/status/keycard/connect/card/ExportKeyCommand.kt +++ b/app/src/main/java/im/status/keycard/connect/card/ExportKeyCommand.kt @@ -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 } } \ No newline at end of file diff --git a/app/src/main/java/im/status/keycard/connect/card/LoadKeyCommand.kt b/app/src/main/java/im/status/keycard/connect/card/LoadKeyCommand.kt index 6e75f9f..eabb07b 100644 --- a/app/src/main/java/im/status/keycard/connect/card/LoadKeyCommand.kt +++ b/app/src/main/java/im/status/keycard/connect/card/LoadKeyCommand.kt @@ -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) + } } } \ No newline at end of file diff --git a/app/src/main/java/im/status/keycard/connect/card/Script.kt b/app/src/main/java/im/status/keycard/connect/card/Script.kt index d2ba8dd..4c7b3b3 100644 --- a/app/src/main/java/im/status/keycard/connect/card/Script.kt +++ b/app/src/main/java/im/status/keycard/connect/card/Script.kt @@ -1,5 +1,20 @@ package im.status.keycard.connect.card +import java.io.IOException +import java.lang.Exception + fun scriptWithSecureChannel(): List = listOf(SelectCommand(), InitCommand(), OpenSecureChannelCommand()) fun scriptWithAuthentication(): List = scriptWithSecureChannel().plus(VerifyPINCommand()) fun cardCheckupScript(): List = 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 +} diff --git a/app/src/main/java/im/status/keycard/connect/card/SelectCommand.kt b/app/src/main/java/im/status/keycard/connect/card/SelectCommand.kt index eef5ba9..ad7e688 100644 --- a/app/src/main/java/im/status/keycard/connect/card/SelectCommand.kt +++ b/app/src/main/java/im/status/keycard/connect/card/SelectCommand.kt @@ -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 } } \ No newline at end of file diff --git a/app/src/main/java/im/status/keycard/connect/card/SignCommand.kt b/app/src/main/java/im/status/keycard/connect/card/SignCommand.kt index ca7adaf..1bce5e3 100644 --- a/app/src/main/java/im/status/keycard/connect/card/SignCommand.kt +++ b/app/src/main/java/im/status/keycard/connect/card/SignCommand.kt @@ -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 } } \ No newline at end of file diff --git a/app/src/main/java/im/status/keycard/connect/data/Constants.kt b/app/src/main/java/im/status/keycard/connect/data/Constants.kt index 5a05f77..895e670 100644 --- a/app/src/main/java/im/status/keycard/connect/data/Constants.kt +++ b/app/src/main/java/im/status/keycard/connect/data/Constants.kt @@ -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 diff --git a/app/src/main/java/im/status/keycard/connect/ui/LoadKeyActivity.kt b/app/src/main/java/im/status/keycard/connect/ui/LoadKeyActivity.kt new file mode 100644 index 0000000..7839f05 --- /dev/null +++ b/app/src/main/java/im/status/keycard/connect/ui/LoadKeyActivity.kt @@ -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(R.id.importMnemonicText).text.toString()) + setResult(Activity.RESULT_OK, intent) + finish() + } + + fun cancel(@Suppress("UNUSED_PARAMETER") view: View) { + setResult(Activity.RESULT_CANCELED) + finish() + } +} diff --git a/app/src/main/java/im/status/keycard/connect/ui/MainActivity.kt b/app/src/main/java/im/status/keycard/connect/ui/MainActivity.kt index bceeeb3..7408e89 100644 --- a/app/src/main/java/im/status/keycard/connect/ui/MainActivity.kt +++ b/app/src/main/java/im/status/keycard/connect/ui/MainActivity.kt @@ -64,6 +64,7 @@ class MainActivity : AppCompatActivity(), ScriptListener { override fun onScriptFinished(result: CardCommand.Result) { this.runOnUiThread { viewSwitcher.showNext() + Registry.scriptExecutor.defaultScript = cardCheckupScript() } } diff --git a/app/src/main/res/layout/activity_load_key.xml b/app/src/main/res/layout/activity_load_key.xml new file mode 100644 index 0000000..522b0ff --- /dev/null +++ b/app/src/main/res/layout/activity_load_key.xml @@ -0,0 +1,99 @@ + + + + + +