fix(useragent): unsafe, but working implementation of ml-dsa
This commit is contained in:
@@ -18,7 +18,6 @@ sealed class CalloutEvent with _$CalloutEvent {
|
||||
required CalloutData data,
|
||||
}) = CalloutEventAdded;
|
||||
|
||||
const factory CalloutEvent.cancelled({
|
||||
required String id,
|
||||
}) = CalloutEventCancelled;
|
||||
const factory CalloutEvent.cancelled({required String id}) =
|
||||
CalloutEventCancelled;
|
||||
}
|
||||
|
||||
@@ -14,11 +14,8 @@ Future<void> showCallout(BuildContext context, WidgetRef ref, String id) async {
|
||||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||
barrierColor: Colors.transparent,
|
||||
transitionDuration: const Duration(milliseconds: 320),
|
||||
pageBuilder: (_, animation, _) => _CalloutOverlay(
|
||||
id: id,
|
||||
data: data,
|
||||
animation: animation,
|
||||
),
|
||||
pageBuilder: (_, animation, _) =>
|
||||
_CalloutOverlay(id: id, data: data, animation: animation),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,22 +32,25 @@ class _CalloutOverlay extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
ref.listen(
|
||||
calloutManagerProvider.select((map) => map.containsKey(id)),
|
||||
(wasPresent, isPresent) {
|
||||
if (wasPresent == true && !isPresent && context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
);
|
||||
ref.listen(calloutManagerProvider.select((map) => map.containsKey(id)), (
|
||||
wasPresent,
|
||||
isPresent,
|
||||
) {
|
||||
if (wasPresent == true && !isPresent && context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
});
|
||||
|
||||
final content = switch (data) {
|
||||
ConnectApprovalData(:final pubkey, :final clientInfo) => SdkConnectCallout(
|
||||
pubkey: pubkey,
|
||||
clientInfo: clientInfo,
|
||||
onAccept: () => ref.read(calloutManagerProvider.notifier).sendDecision(id, true),
|
||||
onDecline: () => ref.read(calloutManagerProvider.notifier).sendDecision(id, false),
|
||||
),
|
||||
ConnectApprovalData(:final pubkey, :final clientInfo) =>
|
||||
SdkConnectCallout(
|
||||
pubkey: pubkey,
|
||||
clientInfo: clientInfo,
|
||||
onAccept: () =>
|
||||
ref.read(calloutManagerProvider.notifier).sendDecision(id, true),
|
||||
onDecline: () =>
|
||||
ref.read(calloutManagerProvider.notifier).sendDecision(id, false),
|
||||
),
|
||||
};
|
||||
|
||||
final barrierAnim = CurvedAnimation(
|
||||
|
||||
@@ -14,7 +14,8 @@ Future<void> showCalloutList(BuildContext context, WidgetRef ref) async {
|
||||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||
barrierColor: Colors.transparent,
|
||||
transitionDuration: const Duration(milliseconds: 280),
|
||||
pageBuilder: (_, animation, __) => _CalloutListOverlay(animation: animation),
|
||||
pageBuilder: (_, animation, __) =>
|
||||
_CalloutListOverlay(animation: animation),
|
||||
);
|
||||
|
||||
if (selectedId != null && context.mounted) {
|
||||
@@ -51,7 +52,9 @@ class _CalloutListOverlay extends ConsumerWidget {
|
||||
child: AnimatedBuilder(
|
||||
animation: barrierAnim,
|
||||
builder: (_, __) => ColoredBox(
|
||||
color: Colors.black.withValues(alpha: 0.35 * barrierAnim.value),
|
||||
color: Colors.black.withValues(
|
||||
alpha: 0.35 * barrierAnim.value,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -50,7 +50,9 @@ class ArbiterUrl {
|
||||
try {
|
||||
return base64Url.decode(base64Url.normalize(cert));
|
||||
} on FormatException catch (error) {
|
||||
throw FormatException("Invalid base64 in 'cert' query parameter: ${error.message}");
|
||||
throw FormatException(
|
||||
"Invalid base64 in 'cert' query parameter: ${error.message}",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,9 @@ Future<Connection> connectAndAuthorize(
|
||||
final solutionResponse = await connection.ask(
|
||||
UserAgentRequest(
|
||||
auth: ua_auth.Request(
|
||||
challengeSolution: ua_auth.AuthChallengeSolution(signature: signature),
|
||||
challengeSolution: ua_auth.AuthChallengeSolution(
|
||||
signature: signature,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -85,7 +85,9 @@ class Connection {
|
||||
if (response.hasId()) {
|
||||
final completer = _pendingRequests.remove(response.id);
|
||||
if (completer == null) {
|
||||
talker.warning('Received response for unknown request id ${response.id}');
|
||||
talker.warning(
|
||||
'Received response for unknown request id ${response.id}',
|
||||
);
|
||||
return;
|
||||
}
|
||||
completer.complete(response);
|
||||
|
||||
@@ -9,9 +9,7 @@ Future<List<WalletEntry>> listEvmWallets(Connection connection) async {
|
||||
UserAgentRequest(evm: ua_evm.Request(walletList: Empty())),
|
||||
);
|
||||
if (!response.hasEvm()) {
|
||||
throw Exception(
|
||||
'Expected EVM response, got ${response.whichPayload()}',
|
||||
);
|
||||
throw Exception('Expected EVM response, got ${response.whichPayload()}');
|
||||
}
|
||||
|
||||
final evmResponse = response.evm;
|
||||
@@ -37,9 +35,7 @@ Future<void> createEvmWallet(Connection connection) async {
|
||||
UserAgentRequest(evm: ua_evm.Request(walletCreate: Empty())),
|
||||
);
|
||||
if (!response.hasEvm()) {
|
||||
throw Exception(
|
||||
'Expected EVM response, got ${response.whichPayload()}',
|
||||
);
|
||||
throw Exception('Expected EVM response, got ${response.whichPayload()}');
|
||||
}
|
||||
|
||||
final evmResponse = response.evm;
|
||||
|
||||
@@ -10,9 +10,7 @@ Future<List<GrantEntry>> listEvmGrants(Connection connection) async {
|
||||
UserAgentRequest(evm: ua_evm.Request(grantList: request)),
|
||||
);
|
||||
if (!response.hasEvm()) {
|
||||
throw Exception(
|
||||
'Expected EVM response, got ${response.whichPayload()}',
|
||||
);
|
||||
throw Exception('Expected EVM response, got ${response.whichPayload()}');
|
||||
}
|
||||
|
||||
final evmResponse = response.evm;
|
||||
@@ -50,9 +48,7 @@ Future<int> createEvmGrant(
|
||||
final resp = await connection.ask(request);
|
||||
|
||||
if (!resp.hasEvm()) {
|
||||
throw Exception(
|
||||
'Expected EVM response, got ${resp.whichPayload()}',
|
||||
);
|
||||
throw Exception('Expected EVM response, got ${resp.whichPayload()}');
|
||||
}
|
||||
|
||||
final evmResponse = resp.evm;
|
||||
@@ -70,15 +66,11 @@ Future<int> createEvmGrant(
|
||||
Future<void> deleteEvmGrant(Connection connection, int grantId) async {
|
||||
final response = await connection.ask(
|
||||
UserAgentRequest(
|
||||
evm: ua_evm.Request(
|
||||
grantDelete: EvmGrantDeleteRequest(grantId: grantId),
|
||||
),
|
||||
evm: ua_evm.Request(grantDelete: EvmGrantDeleteRequest(grantId: grantId)),
|
||||
),
|
||||
);
|
||||
if (!response.hasEvm()) {
|
||||
throw Exception(
|
||||
'Expected EVM response, got ${response.whichPayload()}',
|
||||
);
|
||||
throw Exception('Expected EVM response, got ${response.whichPayload()}');
|
||||
}
|
||||
|
||||
final evmResponse = response.evm;
|
||||
|
||||
@@ -8,9 +8,7 @@ Future<Set<int>> readClientWalletAccess(
|
||||
required int clientId,
|
||||
}) async {
|
||||
final response = await connection.ask(
|
||||
UserAgentRequest(
|
||||
sdkClient: ua_sdk.Request(listWalletAccess: Empty()),
|
||||
),
|
||||
UserAgentRequest(sdkClient: ua_sdk.Request(listWalletAccess: Empty())),
|
||||
);
|
||||
if (!response.hasSdkClient()) {
|
||||
throw Exception(
|
||||
@@ -33,9 +31,7 @@ Future<List<ua_sdk.WalletAccessEntry>> listAllWalletAccesses(
|
||||
Connection connection,
|
||||
) async {
|
||||
final response = await connection.ask(
|
||||
UserAgentRequest(
|
||||
sdkClient: ua_sdk.Request(listWalletAccess: Empty()),
|
||||
),
|
||||
UserAgentRequest(sdkClient: ua_sdk.Request(listWalletAccess: Empty())),
|
||||
);
|
||||
if (!response.hasSdkClient()) {
|
||||
throw Exception(
|
||||
@@ -81,9 +77,7 @@ Future<void> writeClientWalletAccess(
|
||||
UserAgentRequest(
|
||||
sdkClient: ua_sdk.Request(
|
||||
revokeWalletAccess: ua_sdk.RevokeWalletAccess(
|
||||
accesses: [
|
||||
for (final walletId in toRevoke) walletId,
|
||||
],
|
||||
accesses: [for (final walletId in toRevoke) walletId],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -17,9 +17,9 @@ class StoredServerInfo {
|
||||
final int port;
|
||||
final String caCertFingerprint;
|
||||
|
||||
factory StoredServerInfo.fromJson(Map<String, dynamic> json) => _$StoredServerInfoFromJson(json);
|
||||
factory StoredServerInfo.fromJson(Map<String, dynamic> json) =>
|
||||
_$StoredServerInfoFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$StoredServerInfoToJson(this);
|
||||
|
||||
}
|
||||
|
||||
abstract class ServerInfoStorage {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:arbiter/features/connection/connection.dart';
|
||||
import 'package:arbiter/proto/user_agent/vault/bootstrap.pb.dart' as ua_bootstrap;
|
||||
import 'package:arbiter/proto/user_agent/vault/bootstrap.pb.dart'
|
||||
as ua_bootstrap;
|
||||
import 'package:arbiter/proto/user_agent/vault/unseal.pb.dart' as ua_unseal;
|
||||
import 'package:arbiter/proto/user_agent/vault/vault.pb.dart' as ua_vault;
|
||||
import 'package:arbiter/proto/user_agent.pb.dart';
|
||||
@@ -27,9 +28,7 @@ Future<ua_bootstrap.BootstrapResult> bootstrapVault(
|
||||
),
|
||||
);
|
||||
if (!response.hasVault()) {
|
||||
throw Exception(
|
||||
'Expected vault response, got ${response.whichPayload()}',
|
||||
);
|
||||
throw Exception('Expected vault response, got ${response.whichPayload()}');
|
||||
}
|
||||
|
||||
final vaultResponse = response.vault;
|
||||
|
||||
71
useragent/lib/features/identity/hazmat_mldsa.dart
Normal file
71
useragent/lib/features/identity/hazmat_mldsa.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:arbiter/src/rust/api.dart';
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:arbiter/features/identity/pk_manager.dart';
|
||||
|
||||
final storage = FlutterSecureStorage(
|
||||
aOptions: AndroidOptions.biometric(
|
||||
enforceBiometrics: true,
|
||||
biometricPromptTitle: 'Authentication Required',
|
||||
),
|
||||
mOptions: MacOsOptions(
|
||||
accessibility: KeychainAccessibility.unlocked_this_device,
|
||||
label: "Arbiter",
|
||||
description: "Confirm your identity to access vault",
|
||||
synchronizable: false,
|
||||
accessControlFlags: [AccessControlFlag.userPresence],
|
||||
usesDataProtectionKeychain: true,
|
||||
),
|
||||
);
|
||||
|
||||
class HazmatMldsa extends KeyHandle {
|
||||
final MldsaKey _key;
|
||||
|
||||
HazmatMldsa({required MldsaKey key}) : _key = key;
|
||||
|
||||
@override
|
||||
Future<List<int>> getPublicKey() async {
|
||||
final publicKey = await _key.getPublicKey();
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<int>> sign(List<int> data) async {
|
||||
final signature = await _key.sign(message: data);
|
||||
return signature;
|
||||
}
|
||||
}
|
||||
|
||||
class HazmatMLDSAManager extends KeyManager {
|
||||
static const _storageKey = "ed25519_identity";
|
||||
|
||||
@override
|
||||
Future<KeyHandle> create() async {
|
||||
final storedKey = await get();
|
||||
if (storedKey != null) {
|
||||
return storedKey;
|
||||
}
|
||||
|
||||
final newKeypair = await MldsaKey.generate();
|
||||
final keyBytes = await newKeypair.toBytes();
|
||||
|
||||
await storage.write(key: _storageKey, value: base64Encode(keyBytes));
|
||||
|
||||
return HazmatMldsa(key: newKeypair);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<KeyHandle?> get() async {
|
||||
final storedKeyPair = await storage.read(key: _storageKey);
|
||||
if (storedKeyPair == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final keyBytes = base64Decode(storedKeyPair);
|
||||
final key = await MldsaKey.fromBytes(bytes: keyBytes);
|
||||
|
||||
return HazmatMldsa(key: key);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,6 @@
|
||||
enum KeyAlgorithm {
|
||||
rsa, ecdsa, ed25519
|
||||
}
|
||||
|
||||
// The API to handle without storing the private key in memory.
|
||||
// The API to handle without storing the private key in memory.
|
||||
//The implementation will use platform-specific secure storage and signing capabilities.
|
||||
abstract class KeyHandle {
|
||||
KeyAlgorithm get alg;
|
||||
Future<List<int>> sign(List<int> data);
|
||||
Future<List<int>> getPublicKey();
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:arbiter/features/identity/pk_manager.dart';
|
||||
|
||||
final storage = FlutterSecureStorage(
|
||||
aOptions: AndroidOptions.biometric(
|
||||
enforceBiometrics: true,
|
||||
biometricPromptTitle: 'Authentication Required',
|
||||
),
|
||||
mOptions: MacOsOptions(
|
||||
accessibility: KeychainAccessibility.unlocked_this_device,
|
||||
label: "Arbiter",
|
||||
description: "Confirm your identity to access vault",
|
||||
synchronizable: false,
|
||||
accessControlFlags: [
|
||||
AccessControlFlag.userPresence,
|
||||
],
|
||||
usesDataProtectionKeychain: true,
|
||||
),
|
||||
);
|
||||
|
||||
final processor = Ed25519();
|
||||
|
||||
class SimpleEd25519 extends KeyHandle {
|
||||
final SimpleKeyPair _keyPair;
|
||||
|
||||
SimpleEd25519({required SimpleKeyPair keyPair}) : _keyPair = keyPair;
|
||||
|
||||
@override
|
||||
KeyAlgorithm get alg => KeyAlgorithm.ed25519;
|
||||
|
||||
@override
|
||||
Future<List<int>> getPublicKey() async {
|
||||
final publicKey = await _keyPair.extractPublicKey();
|
||||
return publicKey.bytes;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<int>> sign(List<int> data) async {
|
||||
final signature = await processor.sign(data, keyPair: _keyPair);
|
||||
return signature.bytes;
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleEd25519Manager extends KeyManager {
|
||||
static const _storageKey = "ed25519_identity";
|
||||
static const _storagePublicKey = "ed25519_public_key";
|
||||
|
||||
@override
|
||||
Future<KeyHandle> create() async {
|
||||
final storedKey = await get();
|
||||
if (storedKey != null) {
|
||||
return storedKey;
|
||||
}
|
||||
|
||||
final newKey = await processor.newKeyPair();
|
||||
final rawKey = await newKey.extract();
|
||||
|
||||
final keyData = base64Encode(rawKey.bytes);
|
||||
await storage.write(key: _storageKey, value: keyData);
|
||||
|
||||
final publicKeyData = base64Encode(rawKey.publicKey.bytes);
|
||||
await storage.write(key: _storagePublicKey, value: publicKeyData);
|
||||
|
||||
return SimpleEd25519(keyPair: newKey);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<KeyHandle?> get() async {
|
||||
final storedKeyPair = await storage.read(key: _storageKey);
|
||||
if (storedKeyPair == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final publicKeyData = await storage.read(key: _storagePublicKey);
|
||||
final publicKeyRaw = base64Decode(publicKeyData!);
|
||||
final publicKey = SimplePublicKey(
|
||||
publicKeyRaw,
|
||||
type: processor.keyPairType,
|
||||
);
|
||||
|
||||
final keyBytes = base64Decode(storedKeyPair);
|
||||
final keypair = SimpleKeyPairData(
|
||||
keyBytes,
|
||||
publicKey: publicKey,
|
||||
type: processor.keyPairType,
|
||||
);
|
||||
|
||||
return SimpleEd25519(keyPair: keypair);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user