diff --git a/dialer/android/app/src/main/kotlin/com/icingDialer/KeyDeleter.kt b/dialer/android/app/src/main/kotlin/com/icingDialer/KeyDeleter.kt new file mode 100644 index 0000000..51a14f7 --- /dev/null +++ b/dialer/android/app/src/main/kotlin/com/icingDialer/KeyDeleter.kt @@ -0,0 +1,28 @@ +package com.example.keystore + +import java.security.KeyStore + +object KeyDeleterHelper { + + private const val ANDROID_KEYSTORE = "AndroidKeyStore" + + /** + * Deletes the key pair associated with the given alias from the Android Keystore. + * + * @param alias The alias of the key pair to delete. + * @throws Exception if deletion fails. + */ + fun deleteKeyPair(alias: String) { + try { + val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) } + + if (!keyStore.containsAlias(alias)) { + throw Exception("No key found with alias \"$alias\" to delete.") + } + + keyStore.deleteEntry(alias) + } catch (e: Exception) { + throw Exception("Failed to delete key pair: ${e.message}", e) + } + } +} diff --git a/dialer/android/app/src/main/kotlin/com/icingDialer/KeyGenerator.kt b/dialer/android/app/src/main/kotlin/com/icingDialer/KeyGenerator.kt new file mode 100644 index 0000000..f896930 --- /dev/null +++ b/dialer/android/app/src/main/kotlin/com/icingDialer/KeyGenerator.kt @@ -0,0 +1,47 @@ +package com.example.keystore + +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import java.security.KeyPairGenerator +import java.security.KeyStore + +object KeyGeneratorHelper { + + private const val ANDROID_KEYSTORE = "AndroidKeyStore" + + /** + * Generates an ECDSA P-256 key pair and stores it in the Android Keystore. + * + * @param alias Unique identifier for the key pair. + * @throws Exception if key generation fails. + */ + fun generateECKeyPair(alias: String) { + try { + val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) } + + // Check if the key already exists + if (keyStore.containsAlias(alias)) { + throw Exception("Key with alias \"$alias\" already exists.") + } + + val keyPairGenerator = KeyPairGenerator.getInstance( + KeyProperties.KEY_ALGORITHM_EC, + ANDROID_KEYSTORE + ) + + val parameterSpec = KeyGenParameterSpec.Builder( + alias, + KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY + ) + .setAlgorithmParameterSpec(java.security.spec.ECGenParameterSpec("secp256r1")) + .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA512) + .setUserAuthenticationRequired(false) // Set to true if you require user authentication + .build() + + keyPairGenerator.initialize(parameterSpec) + keyPairGenerator.generateKeyPair() + } catch (e: Exception) { + throw Exception("Failed to generate EC key pair: ${e.message}", e) + } + } +} diff --git a/dialer/android/app/src/main/kotlin/com/icingDialer/MainActivity.kt b/dialer/android/app/src/main/kotlin/com/icingDialer/MainActivity.kt index a8a39f9..326c551 100644 --- a/dialer/android/app/src/main/kotlin/com/icingDialer/MainActivity.kt +++ b/dialer/android/app/src/main/kotlin/com/icingDialer/MainActivity.kt @@ -14,102 +14,28 @@ import javax.crypto.SecretKey import android.util.Base64 class MainActivity: FlutterActivity() { - private val CHANNEL = "com.yourapp/keystore" + private val CHANNEL = "com.icingDialer/keystore" - override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> when (call.method) { - "getSymmetricKey" -> { - try { - getOrCreateSymmetricKey() - result.success("symmetric_key_alias") - } catch (e: Exception) { - result.error("KEY_ERROR", e.message, null) + "generateKeyPair" -> { + val alias = call.argument("alias") + if (alias != null) { + try { + KeyGeneratorHelper.generateECKeyPair(alias) + result.success(null) + } catch (e: Exception) { + result.error("KEY_GENERATION_FAILED", e.message, null) + } + } else { + result.error("INVALID_ARGUMENT", "Alias is required", null) } } - "encryptSeed" -> { - val args = call.arguments as Map - val seed = args["seed"]?.let { Base64.decode(it, Base64.DEFAULT) } - if (seed == null) { - result.error("INVALID_ARGUMENT", "Seed is null", null) - return@setMethodCallHandler - } - try { - val encrypted = encryptSeed(seed) - result.success(Base64.encodeToString(encrypted, Base64.DEFAULT)) - } catch (e: Exception) { - result.error("ENCRYPTION_ERROR", e.message, null) - } - } - "decryptSeed" -> { - val args = call.arguments as Map - val encryptedSeed = args["encryptedSeed"]?.let { Base64.decode(it, Base64.DEFAULT) } - if (encryptedSeed == null) { - result.error("INVALID_ARGUMENT", "Encrypted seed is null", null) - return@setMethodCallHandler - } - try { - val decrypted = decryptSeed(encryptedSeed) - result.success(Base64.encodeToString(decrypted, Base64.DEFAULT)) - } catch (e: Exception) { - result.error("DECRYPTION_ERROR", e.message, null) - } - } - else -> { - result.notImplemented() - } + // Handle other methods: signData, getPublicKey, deleteKeyPair + else -> result.notImplemented() } } } - - private fun getOrCreateSymmetricKey(): SecretKey { - val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } - val alias = "symmetric_key_alias" - - // Check if the key already exists - if (!keyStore.containsAlias(alias)) { - // Create the key if it doesn't exist - val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") - val keyGenParameterSpec = KeyGenParameterSpec.Builder( - alias, - KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT - ) - .setBlockModes(KeyProperties.BLOCK_MODE_GCM) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) - .setKeySize(256) - .build() - keyGenerator.init(keyGenParameterSpec) - return keyGenerator.generateKey() - } - - // Retrieve the existing key - return keyStore.getKey(alias, null) as SecretKey - } - - private fun encryptSeed(seed: ByteArray): ByteArray { - val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } - val key = keyStore.getKey("symmetric_key_alias", null) as SecretKey - - val cipher = Cipher.getInstance("AES/GCM/NoPadding") - cipher.init(Cipher.ENCRYPT_MODE, key) - val encryptionIv = cipher.iv - val encryptedBytes = cipher.doFinal(seed) - - // Prepend IV to encrypted bytes for storage - return encryptionIv + encryptedBytes - } - - private fun decryptSeed(encryptedSeed: ByteArray): ByteArray { - val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } - val key = keyStore.getKey("symmetric_key_alias", null) as SecretKey - - // Extract IV and encrypted bytes - val iv = encryptedSeed.copyOfRange(0, 12) // GCM IV is 12 bytes - val ciphertext = encryptedSeed.copyOfRange(12, encryptedSeed.size) - - val cipher = Cipher.getInstance("AES/GCM/NoPadding") - cipher.init(Cipher.DECRYPT_MODE, key, javax.crypto.spec.GCMParameterSpec(128, iv)) - return cipher.doFinal(ciphertext) - } } diff --git a/dialer/android/app/src/main/kotlin/com/icingDialer/PublicKeyRetriever.kt b/dialer/android/app/src/main/kotlin/com/icingDialer/PublicKeyRetriever.kt new file mode 100644 index 0000000..3ced8b2 --- /dev/null +++ b/dialer/android/app/src/main/kotlin/com/icingDialer/PublicKeyRetriever.kt @@ -0,0 +1,30 @@ +package com.example.keystore + +import java.security.KeyStore +import java.security.PublicKey +import android.util.Base64 + +object PublicKeyHelper { + + private const val ANDROID_KEYSTORE = "AndroidKeyStore" + + /** + * Retrieves the public key associated with the given alias. + * + * @param alias The alias of the key pair. + * @return The public key as a Base64-encoded string. + * @throws Exception if retrieval fails. + */ + fun getPublicKey(alias: String): String { + try { + val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) } + + val certificate = keyStore.getCertificate(alias) ?: throw Exception("Certificate not found for alias \"$alias\".") + val publicKey: PublicKey = certificate.publicKey + + return Base64.encodeToString(publicKey.encoded, Base64.DEFAULT) + } catch (e: Exception) { + throw Exception("Failed to retrieve public key: ${e.message}", e) + } + } +} diff --git a/dialer/android/app/src/main/kotlin/com/icingDialer/Signer.kt b/dialer/android/app/src/main/kotlin/com/icingDialer/Signer.kt new file mode 100644 index 0000000..beb5417 --- /dev/null +++ b/dialer/android/app/src/main/kotlin/com/icingDialer/Signer.kt @@ -0,0 +1,36 @@ +package com.example.keystore + +import android.security.keystore.KeyProperties +import java.security.KeyStore +import java.security.Signature +import android.util.Base64 + +object SignerHelper { + + private const val ANDROID_KEYSTORE = "AndroidKeyStore" + + /** + * Signs the provided data using the private key associated with the given alias. + * + * @param alias The alias of the key pair. + * @param data The data to sign. + * @return The signature as a Base64-encoded string. + * @throws Exception if signing fails. + */ + fun signData(alias: String, data: ByteArray): String { + try { + val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) } + + val privateKey = keyStore.getKey(alias, null) ?: throw Exception("Private key not found for alias \"$alias\".") + + val signature = Signature.getInstance("SHA256withECDSA") + signature.initSign(privateKey) + signature.update(data) + val signedBytes = signature.sign() + + return Base64.encodeToString(signedBytes, Base64.DEFAULT) + } catch (e: Exception) { + throw Exception("Failed to sign data: ${e.message}", e) + } + } +} diff --git a/dialer/lib/features/settings/key/generate_keypair_new.dart b/dialer/lib/features/settings/key/generate_keypair_new.dart index c59b32d..e69de29 100644 --- a/dialer/lib/features/settings/key/generate_keypair_new.dart +++ b/dialer/lib/features/settings/key/generate_keypair_new.dart @@ -1,93 +0,0 @@ -// generate_with_recovery_phrase.dart - -import 'package:bip39_mnemonic/bip39_mnemonic.dart'; -import 'package:flutter/material.dart'; -import 'widgets/key_management.dart'; - -class GenerateWithRecoveryPhrasePage extends StatefulWidget { - const GenerateWithRecoveryPhrasePage({super.key}); - - @override - _GenerateWithRecoveryPhrasePageState createState() => _GenerateWithRecoveryPhrasePageState(); -} - -class _GenerateWithRecoveryPhrasePageState extends State { - final KeyManagement _keyManagement = KeyManagement(); - bool _isGenerating = false; - String? _recoveryPhrase; - - Future _generateKeyPair() async { - setState(() { - _isGenerating = true; - }); - - try { - Mnemonic mnemonic = await _keyManagement.generateKeyPairWithRecovery(); - setState(() { - _recoveryPhrase = mnemonic.toString(); - }); - _showRecoveryPhraseDialog(mnemonic.toString()); - } catch (e) { - _showErrorDialog('Failed to generate key pair: $e'); - } finally { - setState(() { - _isGenerating = false; - }); - } - } - - void _showRecoveryPhraseDialog(String phrase) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Recovery Phrase'), - content: SingleChildScrollView( - child: Text( - 'Please write down your recovery phrase and keep it in a safe place. This phrase can be used to restore your key pair.\n\n$phrase', - style: const TextStyle(fontSize: 16), - ), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('OK'), - ), - ], - ), - ); - } - - void _showErrorDialog(String message) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Error'), - content: Text(message), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('OK'), - ), - ], - ), - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.black, - appBar: AppBar( - title: const Text('Generate Key Pair with Recovery'), - ), - body: Center( - child: _isGenerating - ? const CircularProgressIndicator() - : ElevatedButton( - onPressed: _generateKeyPair, - child: const Text('Generate Key Pair'), - ), - ), - ); - } -} diff --git a/dialer/lib/features/settings/key/load_backup.dart b/dialer/lib/features/settings/key/load_backup.dart index 1208a6c..e69de29 100644 --- a/dialer/lib/features/settings/key/load_backup.dart +++ b/dialer/lib/features/settings/key/load_backup.dart @@ -1,115 +0,0 @@ -// restore_from_recovery_phrase.dart - -import 'package:flutter/material.dart'; -import './widgets/key_management.dart'; - -class RestoreFromRecoveryPhrasePage extends StatefulWidget { - const RestoreFromRecoveryPhrasePage({super.key}); - - @override - _RestoreFromRecoveryPhrasePageState createState() => _RestoreFromRecoveryPhrasePageState(); -} - -class _RestoreFromRecoveryPhrasePageState extends State { - final KeyManagement _keyManagement = KeyManagement(); - final TextEditingController _controller = TextEditingController(); - bool _isRestoring = false; - - Future _restoreKeyPair() async { - String mnemonic = _controller.text.trim(); - - if (mnemonic.isEmpty) { - _showErrorDialog('Please enter your recovery phrase.'); - return; - } - - setState(() { - _isRestoring = true; - }); - - try { - EcdsaKeyPair keyPair = await _keyManagement.restoreKeyPair(mnemonic); - _showSuccessDialog('Key pair restored successfully.'); - } catch (e) { - _showErrorDialog('Failed to restore key pair: $e'); - } finally { - setState(() { - _isRestoring = false; - }); - } - } - - void _showSuccessDialog(String message) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Success'), - content: Text(message), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('OK'), - ), - ], - ), - ); - } - - void _showErrorDialog(String message) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Error'), - content: Text(message), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('OK'), - ), - ], - ), - ); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.black, - appBar: AppBar( - title: const Text('Restore Key Pair'), - ), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: _isRestoring - ? const Center(child: CircularProgressIndicator()) - : Column( - children: [ - const Text( - 'Enter your 12-word recovery phrase to restore your key pair.', - style: TextStyle(color: Colors.white), - ), - const SizedBox(height: 20), - TextField( - controller: _controller, - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'Recovery Phrase', - ), - ), - const SizedBox(height: 20), - ElevatedButton( - onPressed: _restoreKeyPair, - child: const Text('Restore Key Pair'), - ), - ], - ), - ), - ); - } -}