New generation / architecture / settings buttons

This commit is contained in:
stcb 2025-01-30 22:29:43 +02:00
parent a3532f4d36
commit 97ce022436
22 changed files with 817 additions and 357 deletions

View File

@ -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<String>("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<String>("alias")
val data = call.argument<String>("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<String>("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<String>("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<String>("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)
}
}
}

View File

@ -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<String>("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()
}
}
}

View File

@ -0,0 +1,64 @@
// import 'package:flutter/material.dart';
//
// class DeleteKeyPairPage extends StatelessWidget {
// const DeleteKeyPairPage({super.key});
//
// Future<void> _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'),
// ),
// ),
// );
// }
// }

View File

@ -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'),
// ),
// ),
// );
// }
// }

View File

@ -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<ManageKeysPage> {
List<Map<String, dynamic>> _keys = [];
bool _isLoading = true;
@override
void initState() {
super.initState();
_fetchKeys();
}
Future<void> _fetchKeys() async {
final AsymmetricCryptoService cryptoService = Provider.of<AsymmetricCryptoService>(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<void> _createKey() async {
final AsymmetricCryptoService cryptoService = Provider.of<AsymmetricCryptoService>(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<String?> _showCreateKeyDialog() async {
String label = '';
return showDialog<String>(
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<void> _deleteKey(String alias, String label) async {
final AsymmetricCryptoService cryptoService = Provider.of<AsymmetricCryptoService>(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<bool> _showDeleteConfirmationDialog(String label) async {
return await showDialog<bool>(
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<void> _viewPublicKey(String alias, String label) async {
final AsymmetricCryptoService cryptoService = Provider.of<AsymmetricCryptoService>(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<void> _editKeyLabel(String alias, String currentLabel) async {
final AsymmetricCryptoService cryptoService = Provider.of<AsymmetricCryptoService>(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<String?> _showEditLabelDialog(String currentLabel) async {
String label = currentLabel;
return showDialog<String>(
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'),
),
],
);
},
);
}
}

View File

@ -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<String?> _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<String?>(
// 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,
// ),
// );
// },
// ),
// );
// }
// }

View File

@ -0,0 +1,50 @@
// import 'package:flutter/material.dart';
// import 'widgets/key_storage.dart';
//
// class DisplayPublicKeyTextPage extends StatelessWidget {
// const DisplayPublicKeyTextPage({super.key});
//
// Future<String?> _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<String?>(
// 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,
// ),
// ),
// );
// },
// ),
// );
// }
// }

View File

@ -1,65 +0,0 @@
import 'package:flutter/material.dart';
import 'widgets/key_storage.dart';
class DeleteKeyPairPage extends StatelessWidget {
const DeleteKeyPairPage({super.key});
Future<void> _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'),
),
),
);
}
}

View File

@ -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'),
),
),
);
}
}

View File

@ -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]);
},
);
},
),
);
}
}

View File

@ -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<String?> _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<String?>(
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,
),
);
},
),
);
}
}

View File

@ -1,50 +0,0 @@
import 'package:flutter/material.dart';
import 'widgets/key_storage.dart';
class DisplayPublicKeyTextPage extends StatelessWidget {
const DisplayPublicKeyTextPage({super.key});
Future<String?> _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<String?>(
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,
),
),
);
},
),
);
}
}

View File

@ -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<void> saveKeys({required String publicKey, required String privateKey}) async {
await _storage.write(key: _publicKeyKey, value: publicKey);
await _storage.write(key: _privateKeyKey, value: privateKey);
}
Future<String?> getPublicKey() async {
return await _storage.read(key: _publicKeyKey);
}
Future<String?> getPrivateKey() async {
return await _storage.read(key: _privateKeyKey);
}
Future<void> deleteKeys() async {
await _storage.delete(key: _publicKeyKey);
await _storage.delete(key: _privateKeyKey);
}
}

View File

@ -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<String> getSymmetricKey() async {
final String symmetricKey = await _channel.invokeMethod('getSymmetricKey');
return symmetricKey;
}
// Optional: Implement key deletion or other key management methods
}

View File

@ -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':

View File

@ -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) {

View File

@ -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<AsymmetricCryptoService>(
create: (_) => cryptoService,
),
// Add other providers here
],
child: Dialer(),
),
);
}
class Dialer extends StatelessWidget {

View File

@ -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<String> 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<String, dynamic> keyMetadata = {
'alias': alias,
'label': label ?? 'Key $uuid',
'created_at': DateTime.now().toIso8601String(),
};
// Retrieve existing keys
final String? existingKeys = await _secureStorage.read(key: 'keys');
List<dynamic> 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<String> 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<String> 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<void> 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<dynamic> 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<List<Map<String, dynamic>>> getAllKeys() async {
try {
final String? existingKeys = await _secureStorage.read(key: 'keys');
if (existingKeys != null) {
List<dynamic> keysList = jsonDecode(existingKeys);
return keysList.cast<Map<String, dynamic>>();
}
return [];
} catch (e) {
throw Exception("Failed to retrieve keys: $e");
}
}
/// Checks if a key pair exists for the given alias.
Future<bool> 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<void> initializeDefaultKeyPair() async {
const String defaultAlias = 'icing_default';
final List<Map<String, dynamic>> 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<String, dynamic> 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<void> 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<dynamic> 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");
}
}
}

View File

@ -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: