diff --git a/dialer/android/app/src/main/kotlin/com/icingDialer/KeystoreHelper.kt b/dialer/android/app/src/main/kotlin/com/icingDialer/KeystoreHelper.kt new file mode 100644 index 0000000..ae44f94 --- /dev/null +++ b/dialer/android/app/src/main/kotlin/com/icingDialer/KeystoreHelper.kt @@ -0,0 +1,153 @@ +package com.example.yourapp.services.cryptography.platform.android + +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import android.util.Base64 +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import java.security.KeyPairGenerator +import java.security.KeyStore +import java.security.Signature + +class KeystoreHelper(private val call: MethodCall, private val result: MethodChannel.Result) { + + private val ANDROID_KEYSTORE = "AndroidKeyStore" + + fun handleMethodCall() { + when (call.method) { + "generateKeyPair" -> generateECKeyPair() + "signData" -> signData() + "getPublicKey" -> getPublicKey() + "deleteKeyPair" -> deleteKeyPair() + "keyPairExists" -> keyPairExists() + else -> result.notImplemented() + } + } + + private fun generateECKeyPair() { + val alias = call.argument("alias") + if (alias == null) { + result.error("INVALID_ARGUMENT", "Alias is required", null) + return + } + + try { + val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) } + + if (keyStore.containsAlias(alias)) { + result.error("KEY_EXISTS", "Key with alias \"$alias\" already exists.", null) + return + } + + 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) + .build() + + keyPairGenerator.initialize(parameterSpec) + keyPairGenerator.generateKeyPair() + + result.success(null) + } catch (e: Exception) { + result.error("KEY_GENERATION_FAILED", e.message, null) + } + } + + private fun signData() { + val alias = call.argument("alias") + val data = call.argument("data") + if (alias == null || data == null) { + result.error("INVALID_ARGUMENT", "Alias and data are required", null) + return + } + + try { + val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) } + + val privateKey = keyStore.getKey(alias, null) ?: run { + result.error("KEY_NOT_FOUND", "Private key not found for alias \"$alias\".", null) + return + } + + val signature = Signature.getInstance("SHA256withECDSA") + signature.initSign(privateKey) + signature.update(data.toByteArray()) + val signedBytes = signature.sign() + + val signatureBase64 = Base64.encodeToString(signedBytes, Base64.DEFAULT) + result.success(signatureBase64) + } catch (e: Exception) { + result.error("SIGNING_FAILED", e.message, null) + } + } + + private fun getPublicKey() { + val alias = call.argument("alias") + if (alias == null) { + result.error("INVALID_ARGUMENT", "Alias is required", null) + return + } + + try { + val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) } + + val certificate = keyStore.getCertificate(alias) ?: run { + result.error("CERTIFICATE_NOT_FOUND", "Certificate not found for alias \"$alias\".", null) + return + } + + val publicKey = certificate.publicKey + val publicKeyBase64 = Base64.encodeToString(publicKey.encoded, Base64.DEFAULT) + result.success(publicKeyBase64) + } catch (e: Exception) { + result.error("PUBLIC_KEY_RETRIEVAL_FAILED", e.message, null) + } + } + + private fun deleteKeyPair() { + val alias = call.argument("alias") + if (alias == null) { + result.error("INVALID_ARGUMENT", "Alias is required", null) + return + } + + try { + val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) } + + if (!keyStore.containsAlias(alias)) { + result.error("KEY_NOT_FOUND", "No key found with alias \"$alias\" to delete.", null) + return + } + + keyStore.deleteEntry(alias) + result.success(null) + } catch (e: Exception) { + result.error("KEY_DELETION_FAILED", e.message, null) + } + } + + private fun keyPairExists() { + val alias = call.argument("alias") + if (alias == null) { + result.error("INVALID_ARGUMENT", "Alias is required", null) + return + } + + try { + val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) } + val exists = keyStore.containsAlias(alias) + result.success(exists) + } catch (e: Exception) { + result.error("KEY_CHECK_FAILED", e.message, null) + } + } +} 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 326c551..8ef69b5 100644 --- a/dialer/android/app/src/main/kotlin/com/icingDialer/MainActivity.kt +++ b/dialer/android/app/src/main/kotlin/com/icingDialer/MainActivity.kt @@ -1,41 +1,19 @@ -package com.icingDialer +package com.example.yourapp -import io.flutter.embedding.android.FlutterActivity -import android.os.Build -import android.security.keystore.KeyGenParameterSpec -import android.security.keystore.KeyProperties -import androidx.annotation.NonNull +import android.os.Bundle import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel -import java.security.KeyStore -import javax.crypto.KeyGenerator -import javax.crypto.SecretKey -import android.util.Base64 +import com.icingDialer.KeystoreHelper class MainActivity: FlutterActivity() { - private val CHANNEL = "com.icingDialer/keystore" + private val CHANNEL = "com.example.keystore" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> - when (call.method) { - "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) - } - } - // Handle other methods: signData, getPublicKey, deleteKeyPair - else -> result.notImplemented() - } + // Delegate method calls to KeystoreHelper + KeystoreHelper(call, result).handleMethodCall() } } } diff --git a/dialer/lib/features/settings/cryptography/delete_key_pair.dart b/dialer/lib/features/settings/cryptography/delete_key_pair.dart new file mode 100644 index 0000000..220374a --- /dev/null +++ b/dialer/lib/features/settings/cryptography/delete_key_pair.dart @@ -0,0 +1,64 @@ +// import 'package:flutter/material.dart'; +// +// class DeleteKeyPairPage extends StatelessWidget { +// const DeleteKeyPairPage({super.key}); +// +// Future _deleteKeyPair(BuildContext context) async { +// final keyStorage = KeyStorage(); +// await keyStorage.deleteKeys(); +// +// ScaffoldMessenger.of(context).showSnackBar( +// const SnackBar( +// content: Text('The key pair has been deleted.'), +// ), +// ); +// +// Navigator.pop(context); +// } +// +// void _showConfirmationDialog(BuildContext context) { +// showDialog( +// context: context, +// builder: (BuildContext context) { +// return AlertDialog( +// title: const Text('Confirm Deletion'), +// content: const Text( +// 'Are you sure you want to delete the cryptography pair? This action is irreversible.'), +// actions: [ +// TextButton( +// child: const Text('Cancel'), +// onPressed: () { +// Navigator.of(context).pop(); +// }, +// ), +// TextButton( +// child: const Text('Delete'), +// onPressed: () { +// Navigator.of(context).pop(); +// _deleteKeyPair(context); +// }, +// ), +// ], +// ); +// }, +// ); +// } +// +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// backgroundColor: Colors.black, +// appBar: AppBar( +// title: const Text('Delete a Key Pair'), +// ), +// body: Center( +// child: ElevatedButton( +// onPressed: () { +// _showConfirmationDialog(context); +// }, +// child: const Text('Delete Key Pair'), +// ), +// ), +// ); +// } +// } diff --git a/dialer/lib/features/settings/cryptography/generate_new_key_pair.dart b/dialer/lib/features/settings/cryptography/generate_new_key_pair.dart new file mode 100644 index 0000000..2158e91 --- /dev/null +++ b/dialer/lib/features/settings/cryptography/generate_new_key_pair.dart @@ -0,0 +1,40 @@ +// import 'package:flutter/material.dart'; +// import 'package:dialer/services/cryptography/asymmetric_crypto_service.dart'; +// import 'dart:math'; +// +// class GenerateNewKeyPairPage extends StatelessWidget { +// const GenerateNewKeyPairPage({super.key}); +// +// +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// backgroundColor: Colors.black, +// appBar: AppBar( +// title: const Text('Generate a New Key Pair'), +// ), +// body: Center( +// child: ElevatedButton( +// onPressed: () async { +// var keyName = Random().nextInt(10000000).toString(); +// await AsymmetricCryptoService().generateKeyPair(keyName); +// showDialog( +// context: context, +// builder: (context) => AlertDialog( +// title: const Text('Keys Generated (${keyName})'), +// content: const Text('The new key pair has been generated and stored securely.'), +// actions: [ +// TextButton( +// onPressed: () => Navigator.pop(context), +// child: const Text('OK'), +// ), +// ], +// ), +// ); +// }, +// child: const Text('Generate a New Key Pair'), +// ), +// ), +// ); +// } +// } diff --git a/dialer/lib/features/settings/key/generate_keypair_new.dart b/dialer/lib/features/settings/cryptography/load_certificate.dart similarity index 100% rename from dialer/lib/features/settings/key/generate_keypair_new.dart rename to dialer/lib/features/settings/cryptography/load_certificate.dart diff --git a/dialer/lib/features/settings/cryptography/manage_keys_page.dart b/dialer/lib/features/settings/cryptography/manage_keys_page.dart new file mode 100644 index 0000000..2254c1e --- /dev/null +++ b/dialer/lib/features/settings/cryptography/manage_keys_page.dart @@ -0,0 +1,249 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:uuid/uuid.dart'; +import 'package:dialer/services/cryptography/asymmetric_crypto_service.dart'; + +class ManageKeysPage extends StatefulWidget { + @override + _ManageKeysPageState createState() => _ManageKeysPageState(); +} + +class _ManageKeysPageState extends State { + List> _keys = []; + bool _isLoading = true; + + @override + void initState() { + super.initState(); + _fetchKeys(); + } + + Future _fetchKeys() async { + final AsymmetricCryptoService cryptoService = Provider.of(context, listen: false); + try { + final keys = await cryptoService.getAllKeys(); + setState(() { + _keys = keys; + _isLoading = false; + }); + } catch (e) { + // Handle error, e.g., show a snackbar + setState(() { + _isLoading = false; + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error fetching keys: $e')), + ); + } + } + + Future _createKey() async { + final AsymmetricCryptoService cryptoService = Provider.of(context, listen: false); + String? label = await _showCreateKeyDialog(); + if (label == null) return; // User cancelled + + try { + await cryptoService.generateKeyPair(label: label); + await _fetchKeys(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Key "$label" created successfully.')), + ); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error creating key: $e')), + ); + } + } + + Future _showCreateKeyDialog() async { + String label = ''; + return showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text('Create New Key'), + content: TextField( + onChanged: (value) { + label = value; + }, + decoration: InputDecoration(hintText: "Enter key label"), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(null), + child: Text('Cancel'), + ), + ElevatedButton( + onPressed: () => Navigator.of(context).pop(label.trim().isEmpty ? 'Key ${Uuid().v4()}' : label.trim()), + child: Text('Create'), + ), + ], + ); + }, + ); + } + + Future _deleteKey(String alias, String label) async { + final AsymmetricCryptoService cryptoService = Provider.of(context, listen: false); + + bool confirm = await _showDeleteConfirmationDialog(label); + if (!confirm) return; + + try { + await cryptoService.deleteKeyPair(alias); + await _fetchKeys(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Key "$label" deleted successfully.')), + ); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error deleting key: $e')), + ); + } + } + + Future _showDeleteConfirmationDialog(String label) async { + return await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('Delete Key'), + content: Text('Are you sure you want to delete the key "$label"? This action cannot be undone.'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text('Cancel'), + ), + ElevatedButton( + onPressed: () => Navigator.of(context).pop(true), + style: ElevatedButton.styleFrom(foregroundColor: Colors.red), + child: Text('Delete'), + ), + ], + ), + ) ?? + false; + } + + Future _viewPublicKey(String alias, String label) async { + final AsymmetricCryptoService cryptoService = Provider.of(context, listen: false); + try { + final String publicKey = await cryptoService.getPublicKey(alias); + await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('Public Key for "$label"'), + content: SelectableText(publicKey), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('Close'), + ), + // Optionally, add a button to copy the key to clipboard + ], + ), + ); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error retrieving public key: $e')), + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Manage Keys'), + actions: [ + IconButton( + icon: Icon(Icons.add), + onPressed: _createKey, + tooltip: 'Create New Key', + ), + ], + ), + body: _isLoading + ? Center(child: CircularProgressIndicator()) + : _keys.isEmpty + ? Center(child: Text('No keys found.')) + : ListView.builder( + itemCount: _keys.length, + itemBuilder: (context, index) { + final key = _keys[index]; + return ListTile( + title: Text(key['label']), + subtitle: Text('Created at: ${key['created_at']}'), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.edit), + onPressed: () => _editKeyLabel(key['alias'], key['label']), + tooltip: 'Edit Label', + ), + IconButton( + icon: Icon(Icons.visibility), + onPressed: () => _viewPublicKey(key['alias'], key['label']), + tooltip: 'View Public Key', + ), + IconButton( + icon: Icon(Icons.delete, color: Colors.red), + onPressed: () => _deleteKey(key['alias'], key['label']), + tooltip: 'Delete Key', + ), + ], + ), + ); + }, + )); + } + + /// Updates the label of a key. + Future _editKeyLabel(String alias, String currentLabel) async { + final AsymmetricCryptoService cryptoService = Provider.of(context, listen: false); + String? newLabel = await _showEditLabelDialog(currentLabel); + if (newLabel == null) return; // User cancelled + + try { + await cryptoService.updateKeyLabel(alias, newLabel); + await _fetchKeys(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Key label updated to "$newLabel".')), + ); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error updating key label: $e')), + ); + } + } + + Future _showEditLabelDialog(String currentLabel) async { + String label = currentLabel; + return showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text('Edit Key Label'), + content: TextField( + onChanged: (value) { + label = value; + }, + decoration: InputDecoration(hintText: "Enter new key label"), + controller: TextEditingController(text: currentLabel), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(null), + child: Text('Cancel'), + ), + ElevatedButton( + onPressed: () => Navigator.of(context).pop(label.trim().isEmpty ? currentLabel : label.trim()), + child: Text('Save'), + ), + ], + ); + }, + ); + } + +} diff --git a/dialer/lib/features/settings/cryptography/show_public_key_qr.dart b/dialer/lib/features/settings/cryptography/show_public_key_qr.dart new file mode 100644 index 0000000..26af6d3 --- /dev/null +++ b/dialer/lib/features/settings/cryptography/show_public_key_qr.dart @@ -0,0 +1,49 @@ +// import 'package:flutter/material.dart'; +// import 'package:pretty_qr_code/pretty_qr_code.dart'; +// import 'widgets/key_storage.dart'; +// +// class DisplayPublicKeyQRCodePage extends StatelessWidget { +// const DisplayPublicKeyQRCodePage({super.key}); +// +// Future _loadPublicKey() async { +// final keyStorage = KeyStorage(); +// return keyStorage.getPublicKey(); +// } +// +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// backgroundColor: Colors.black, +// appBar: AppBar( +// title: const Text('Public Key in QR Code'), +// ), +// body: FutureBuilder( +// future: _loadPublicKey(), +// builder: (context, snapshot) { +// if (snapshot.connectionState == ConnectionState.waiting) { +// return const Center(child: CircularProgressIndicator()); +// } +// +// final publicKey = snapshot.data; +// if (publicKey == null) { +// return const Center( +// child: Text( +// 'No public key found.', +// style: TextStyle(color: Colors.white), +// ), +// ); +// } +// +// return Center( +// child: PrettyQr( +// data: publicKey, +// size: 250, +// roundEdges: true, +// elementColor: Colors.white, +// ), +// ); +// }, +// ), +// ); +// } +// } diff --git a/dialer/lib/features/settings/cryptography/show_public_key_text.dart b/dialer/lib/features/settings/cryptography/show_public_key_text.dart new file mode 100644 index 0000000..3a18a1c --- /dev/null +++ b/dialer/lib/features/settings/cryptography/show_public_key_text.dart @@ -0,0 +1,50 @@ +// import 'package:flutter/material.dart'; +// import 'widgets/key_storage.dart'; +// +// class DisplayPublicKeyTextPage extends StatelessWidget { +// const DisplayPublicKeyTextPage({super.key}); +// +// Future _loadPublicKey() async { +// final keyStorage = KeyStorage(); +// return await keyStorage.getPublicKey(); +// } +// +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// backgroundColor: Colors.black, +// appBar: AppBar( +// title: const Text('Public Key as Text'), +// ), +// body: FutureBuilder( +// future: _loadPublicKey(), +// builder: (context, snapshot) { +// if (snapshot.connectionState == ConnectionState.waiting) { +// return const Center(child: CircularProgressIndicator()); +// } +// +// final publicKey = snapshot.data; +// if (publicKey == null) { +// return const Center( +// child: Text( +// 'No public key found.', +// style: TextStyle(color: Colors.white), +// ), +// ); +// } +// +// return Center( +// child: Padding( +// padding: const EdgeInsets.all(16.0), +// child: SelectableText( +// publicKey, +// style: const TextStyle(color: Colors.white), +// textAlign: TextAlign.center, +// ), +// ), +// ); +// }, +// ), +// ); +// } +// } diff --git a/dialer/lib/features/settings/key/delete_key_pair.dart b/dialer/lib/features/settings/key/delete_key_pair.dart deleted file mode 100644 index 3674e9f..0000000 --- a/dialer/lib/features/settings/key/delete_key_pair.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:flutter/material.dart'; -import 'widgets/key_storage.dart'; - -class DeleteKeyPairPage extends StatelessWidget { - const DeleteKeyPairPage({super.key}); - - Future _deleteKeyPair(BuildContext context) async { - final keyStorage = KeyStorage(); - await keyStorage.deleteKeys(); - - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('The key pair has been deleted.'), - ), - ); - - Navigator.pop(context); - } - - void _showConfirmationDialog(BuildContext context) { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Confirm Deletion'), - content: const Text( - 'Are you sure you want to delete the key pair? This action is irreversible.'), - actions: [ - TextButton( - child: const Text('Cancel'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: const Text('Delete'), - onPressed: () { - Navigator.of(context).pop(); - _deleteKeyPair(context); - }, - ), - ], - ); - }, - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.black, - appBar: AppBar( - title: const Text('Delete a Key Pair'), - ), - body: Center( - child: ElevatedButton( - onPressed: () { - _showConfirmationDialog(context); - }, - child: const Text('Delete Key Pair'), - ), - ), - ); - } -} diff --git a/dialer/lib/features/settings/key/generate_new_key_pair.dart b/dialer/lib/features/settings/key/generate_new_key_pair.dart deleted file mode 100644 index 95707d6..0000000 --- a/dialer/lib/features/settings/key/generate_new_key_pair.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:pointycastle/export.dart' as crypto; -import 'dart:math'; -import 'dart:convert'; -import 'dart:typed_data'; -import 'package:asn1lib/asn1lib.dart'; -import 'widgets/key_storage.dart'; - -class GenerateNewKeyPairPage extends StatelessWidget { - const GenerateNewKeyPairPage({super.key}); - - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.black, - appBar: AppBar( - title: const Text('Generate a New Key Pair'), - ), - body: Center( - child: ElevatedButton( - onPressed: () async { - await _generateKeyPair(); - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Keys Generated'), - content: const Text('The new key pair has been generated and stored securely.'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('OK'), - ), - ], - ), - ); - }, - child: const Text('Generate a New Key Pair'), - ), - ), - ); - } -} diff --git a/dialer/lib/features/settings/key/load_backup.dart b/dialer/lib/features/settings/key/load_backup.dart deleted file mode 100644 index e69de29..0000000 diff --git a/dialer/lib/features/settings/key/manage_keys_page.dart b/dialer/lib/features/settings/key/manage_keys_page.dart deleted file mode 100644 index c1002fc..0000000 --- a/dialer/lib/features/settings/key/manage_keys_page.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:flutter/material.dart'; -import 'show_public_key_qr.dart'; -import 'show_public_key_text.dart'; -import 'generate_new_key_pair.dart'; -import 'delete_key_pair.dart'; - -class KeyManagementPage extends StatelessWidget { - const KeyManagementPage({super.key}); - - void _navigateToOption(BuildContext context, String option) { - switch (option) { - case 'Display public key as text': - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const DisplayPublicKeyTextPage()), - ); - break; - case 'Display public key as QR code': - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const DisplayPublicKeyQRCodePage()), - ); - break; - case 'Generate a new key pair': - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const GenerateNewKeyPairPage()), - ); - break; - case 'Delete a key pair': - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const DeleteKeyPairPage()), - ); - break; - default: - break; - } - } - - @override - Widget build(BuildContext context) { - final keyManagementOptions = [ - 'Display public key as text', - 'Display public key as QR code', - 'Generate a new key pair', - 'Export private key to password-encrypted file (AES 256)', - 'Delete a key pair', - ]; - - return Scaffold( - backgroundColor: Colors.black, - appBar: AppBar( - title: const Text('Key Management'), - ), - body: ListView.builder( - itemCount: keyManagementOptions.length, - itemBuilder: (context, index) { - return ListTile( - title: Text( - keyManagementOptions[index], - style: const TextStyle(color: Colors.white), - ), - trailing: const Icon(Icons.arrow_forward_ios, color: Colors.white), - onTap: () { - _navigateToOption(context, keyManagementOptions[index]); - }, - ); - }, - ), - ); - } -} diff --git a/dialer/lib/features/settings/key/show_public_key_qr.dart b/dialer/lib/features/settings/key/show_public_key_qr.dart deleted file mode 100644 index 70bb63c..0000000 --- a/dialer/lib/features/settings/key/show_public_key_qr.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:pretty_qr_code/pretty_qr_code.dart'; -import 'widgets/key_storage.dart'; - -class DisplayPublicKeyQRCodePage extends StatelessWidget { - const DisplayPublicKeyQRCodePage({super.key}); - - Future _loadPublicKey() async { - final keyStorage = KeyStorage(); - return keyStorage.getPublicKey(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.black, - appBar: AppBar( - title: const Text('Public Key in QR Code'), - ), - body: FutureBuilder( - future: _loadPublicKey(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } - - final publicKey = snapshot.data; - if (publicKey == null) { - return const Center( - child: Text( - 'No public key found.', - style: TextStyle(color: Colors.white), - ), - ); - } - - return Center( - child: PrettyQr( - data: publicKey, - size: 250, - roundEdges: true, - elementColor: Colors.white, - ), - ); - }, - ), - ); - } -} diff --git a/dialer/lib/features/settings/key/show_public_key_text.dart b/dialer/lib/features/settings/key/show_public_key_text.dart deleted file mode 100644 index 09b0261..0000000 --- a/dialer/lib/features/settings/key/show_public_key_text.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:flutter/material.dart'; -import 'widgets/key_storage.dart'; - -class DisplayPublicKeyTextPage extends StatelessWidget { - const DisplayPublicKeyTextPage({super.key}); - - Future _loadPublicKey() async { - final keyStorage = KeyStorage(); - return await keyStorage.getPublicKey(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.black, - appBar: AppBar( - title: const Text('Public Key as Text'), - ), - body: FutureBuilder( - future: _loadPublicKey(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } - - final publicKey = snapshot.data; - if (publicKey == null) { - return const Center( - child: Text( - 'No public key found.', - style: TextStyle(color: Colors.white), - ), - ); - } - - return Center( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: SelectableText( - publicKey, - style: const TextStyle(color: Colors.white), - textAlign: TextAlign.center, - ), - ), - ); - }, - ), - ); - } -} diff --git a/dialer/lib/features/settings/key/widgets/key_management.dart b/dialer/lib/features/settings/key/widgets/key_management.dart deleted file mode 100644 index e69de29..0000000 diff --git a/dialer/lib/features/settings/key/widgets/key_storage.dart b/dialer/lib/features/settings/key/widgets/key_storage.dart deleted file mode 100644 index a258112..0000000 --- a/dialer/lib/features/settings/key/widgets/key_storage.dart +++ /dev/null @@ -1,28 +0,0 @@ -// key_storage.dart - -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; - -class KeyStorage { - static const _publicKeyKey = 'public_key'; - static const _privateKeyKey = 'private_key'; - - final FlutterSecureStorage _storage = const FlutterSecureStorage(); - - Future saveKeys({required String publicKey, required String privateKey}) async { - await _storage.write(key: _publicKeyKey, value: publicKey); - await _storage.write(key: _privateKeyKey, value: privateKey); - } - - Future getPublicKey() async { - return await _storage.read(key: _publicKeyKey); - } - - Future getPrivateKey() async { - return await _storage.read(key: _privateKeyKey); - } - - Future deleteKeys() async { - await _storage.delete(key: _publicKeyKey); - await _storage.delete(key: _privateKeyKey); - } -} diff --git a/dialer/lib/features/settings/key/widgets/keystore_service.dart b/dialer/lib/features/settings/key/widgets/keystore_service.dart deleted file mode 100644 index 21e385a..0000000 --- a/dialer/lib/features/settings/key/widgets/keystore_service.dart +++ /dev/null @@ -1,15 +0,0 @@ -// keystore_service.dart - -import 'package:flutter/services.dart'; - -class KeystoreService { - static const MethodChannel _channel = MethodChannel('com.icingDialer/keystore'); - - // Generate or retrieve the symmetric key from Keystore - Future getSymmetricKey() async { - final String symmetricKey = await _channel.invokeMethod('getSymmetricKey'); - return symmetricKey; - } - -// Optional: Implement key deletion or other key management methods -} diff --git a/dialer/lib/features/settings/settings.dart b/dialer/lib/features/settings/settings.dart index 3f64e97..8ceb893 100644 --- a/dialer/lib/features/settings/settings.dart +++ b/dialer/lib/features/settings/settings.dart @@ -3,9 +3,11 @@ import 'package:flutter/material.dart'; import 'package:dialer/features/settings/call/settingsCall.dart'; import 'package:dialer/features/settings/sim/settings_accounts.dart'; -import 'package:dialer/features/settings/key/manage_keys_page.dart'; +// import 'package:dialer/features/settings/cryptography/'; import 'package:dialer/features/settings/blocked/settings_blocked.dart'; +import 'cryptography/manage_keys_page.dart'; + class SettingsPage extends StatelessWidget { const SettingsPage({super.key}); @@ -23,10 +25,10 @@ class SettingsPage extends StatelessWidget { MaterialPageRoute(builder: (context) => const SettingsAccountsPage()), ); break; - case 'Key management': + case 'Cryptography': Navigator.push( context, - MaterialPageRoute(builder: (context) => const KeyManagementPage()), + MaterialPageRoute(builder: (context) => ManageKeysPage()), ); break; case 'Blocked numbers': diff --git a/dialer/lib/features/settings/sim/settings_accounts.dart b/dialer/lib/features/settings/sim/settings_accounts.dart index 02e9fbd..f307bac 100644 --- a/dialer/lib/features/settings/sim/settings_accounts.dart +++ b/dialer/lib/features/settings/sim/settings_accounts.dart @@ -3,7 +3,7 @@ import 'choose_sim.dart'; import 'sim_parameters.dart'; class SettingsAccountsPage extends StatelessWidget { - const SettingsAccountsPage({Key? key}) : super(key: key); + const SettingsAccountsPage({super.key}); void _navigateToAccountOption(BuildContext context, String option) { switch (option) { diff --git a/dialer/lib/main.dart b/dialer/lib/main.dart index 1f760e8..3329a11 100644 --- a/dialer/lib/main.dart +++ b/dialer/lib/main.dart @@ -2,11 +2,29 @@ import 'package:dialer/features/home/home_page.dart'; import 'package:flutter/material.dart'; import 'package:dialer/features/contacts/contact_state.dart'; import 'globals.dart' as globals; +import 'package:dialer/services/cryptography/asymmetric_crypto_service.dart'; +import 'package:provider/provider.dart'; -void main() { +void main() async { const stealthFlag = String.fromEnvironment('STEALTH', defaultValue: 'false'); globals.isStealthMode = stealthFlag.toLowerCase() == 'true'; - runApp(const Dialer()); + + // Initialize CryptographyService + final AsymmetricCryptoService cryptoService = AsymmetricCryptoService(); + // Initialize default key pair + await cryptoService.initializeDefaultKeyPair(); + + runApp( + MultiProvider( + providers: [ + Provider( + create: (_) => cryptoService, + ), + // Add other providers here + ], + child: Dialer(), + ), + ); } class Dialer extends StatelessWidget { diff --git a/dialer/lib/services/cryptography/asymmetric_crypto_service.dart b/dialer/lib/services/cryptography/asymmetric_crypto_service.dart new file mode 100644 index 0000000..712d84a --- /dev/null +++ b/dialer/lib/services/cryptography/asymmetric_crypto_service.dart @@ -0,0 +1,178 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:flutter/services.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:uuid/uuid.dart'; + +class AsymmetricCryptoService { + static const MethodChannel _channel = MethodChannel('com.example.keystore'); + final FlutterSecureStorage _secureStorage = FlutterSecureStorage(); + final String _aliasPrefix = 'icing_'; + final Uuid _uuid = Uuid(); + + /// Generates an ECDSA P-256 key pair with a unique alias and stores its metadata. + Future generateKeyPair({String? label}) async { + try { + // Generate a unique identifier for the key + final String uuid = _uuid.v4(); + final String alias = '$_aliasPrefix$uuid'; + + // Invoke native method to generate the key pair + await _channel.invokeMethod('generateKeyPair', {'alias': alias}); + + // Store key metadata securely + final Map keyMetadata = { + 'alias': alias, + 'label': label ?? 'Key $uuid', + 'created_at': DateTime.now().toIso8601String(), + }; + + // Retrieve existing keys + final String? existingKeys = await _secureStorage.read(key: 'keys'); + List keysList = existingKeys != null ? jsonDecode(existingKeys) : []; + + // Add the new key + keysList.add(keyMetadata); + + // Save updated keys list + await _secureStorage.write(key: 'keys', value: jsonEncode(keysList)); + + return alias; + } on PlatformException catch (e) { + throw Exception("Failed to generate key pair: ${e.message}"); + } + } + + /// Signs data using the specified key alias. + Future signData(String alias, String data) async { + try { + final String signature = await _channel.invokeMethod('signData', { + 'alias': alias, + 'data': data, + }); + return signature; + } on PlatformException catch (e) { + throw Exception("Failed to sign data: ${e.message}"); + } + } + + /// Retrieves the public key for the specified alias. + Future getPublicKey(String alias) async { + try { + final String publicKey = await _channel.invokeMethod('getPublicKey', { + 'alias': alias, + }); + return publicKey; + } on PlatformException catch (e) { + throw Exception("Failed to retrieve public key: ${e.message}"); + } + } + + /// Deletes the key pair associated with the specified alias and removes its metadata. + Future deleteKeyPair(String alias) async { + try { + await _channel.invokeMethod('deleteKeyPair', {'alias': alias}); + + // Retrieve existing keys + final String? existingKeys = await _secureStorage.read(key: 'keys'); + if (existingKeys != null) { + List keysList = jsonDecode(existingKeys); + keysList.removeWhere((key) => key['alias'] == alias); + + // Save updated keys list + await _secureStorage.write(key: 'keys', value: jsonEncode(keysList)); + } + } on PlatformException catch (e) { + throw Exception("Failed to delete key pair: ${e.message}"); + } + } + + /// Retrieves all stored key metadata. + Future>> getAllKeys() async { + try { + final String? existingKeys = await _secureStorage.read(key: 'keys'); + if (existingKeys != null) { + List keysList = jsonDecode(existingKeys); + return keysList.cast>(); + } + return []; + } catch (e) { + throw Exception("Failed to retrieve keys: $e"); + } + } + + /// Checks if a key pair exists for the given alias. + Future keyPairExists(String alias) async { + try { + final bool exists = await _channel.invokeMethod('keyPairExists', {'alias': alias}); + return exists; + } on PlatformException catch (e) { + throw Exception("Failed to check key pair existence: ${e.message}"); + } + } + + /// Initializes the default key pair if it doesn't exist. + Future initializeDefaultKeyPair() async { + const String defaultAlias = 'icing_default'; + final List> keys = await getAllKeys(); + + // Check if default key exists in metadata + final bool defaultKeyExists = keys.any((key) => key['alias'] == defaultAlias); + + if (!defaultKeyExists) { + // Check if default key exists in Keystore + final bool exists = await keyPairExists(defaultAlias); + if (!exists) { + // Generate default key + await _channel.invokeMethod('generateKeyPair', {'alias': defaultAlias}); + + // Store metadata + final Map keyMetadata = { + 'alias': defaultAlias, + 'label': 'Default Key', + 'created_at': DateTime.now().toIso8601String(), + }; + + keys.add(keyMetadata); + await _secureStorage.write(key: 'keys', value: jsonEncode(keys)); + } + } + } + + /// Updates the label of a key with the specified alias. + /// + /// [alias]: The unique alias of the key to update. + /// [newLabel]: The new label to assign to the key. + /// + /// Throws an exception if the key is not found or the update fails. + Future updateKeyLabel(String alias, String newLabel) async { + try { + // Retrieve existing keys + final String? existingKeys = await _secureStorage.read(key: 'keys'); + if (existingKeys == null) { + throw Exception("No keys found to update."); + } + + List keysList = jsonDecode(existingKeys); + + // Find the key with the specified alias + bool keyFound = false; + for (var key in keysList) { + if (key['alias'] == alias) { + key['label'] = newLabel; + keyFound = true; + break; + } + } + + if (!keyFound) { + throw Exception("Key with alias \"$alias\" not found."); + } + + // Save the updated keys list + await _secureStorage.write(key: 'keys', value: jsonEncode(keysList)); + } catch (e) { + throw Exception("Failed to update key label: $e"); + } + } +} diff --git a/dialer/pubspec.yaml b/dialer/pubspec.yaml index 025d60f..78b0bb7 100644 --- a/dialer/pubspec.yaml +++ b/dialer/pubspec.yaml @@ -55,6 +55,8 @@ dependencies: mobile_number: path: packages/mobile_number encrypt: ^5.0.3 + uuid: ^4.5.1 + provider: ^6.1.2 dev_dependencies: flutter_test: