Files
arbiter/useragent/lib/features/connection/auth.dart

109 lines
3.4 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'package:arbiter/features/connection/connection.dart';
import 'package:arbiter/features/connection/server_info_storage.dart';
import 'package:arbiter/features/identity/pk_manager.dart';
import 'package:arbiter/proto/arbiter.pbgrpc.dart';
import 'package:arbiter/proto/user_agent.pb.dart';
import 'package:grpc/grpc.dart';
import 'package:mtcore/markettakers.dart';
Future<Connection> connectAndAuthorize(
StoredServerInfo serverInfo,
KeyHandle key, {
String? bootstrapToken,
}) async {
try {
final connection = await _connect(serverInfo);
talker.info(
'Connected to server at ${serverInfo.address}:${serverInfo.port}',
);
final pubkey = await key.getPublicKey();
final req = AuthChallengeRequest(
pubkey: pubkey,
bootstrapToken: bootstrapToken,
keyType: switch (key.alg) {
KeyAlgorithm.rsa => KeyType.KEY_TYPE_RSA,
KeyAlgorithm.ecdsa => KeyType.KEY_TYPE_ECDSA_SECP256K1,
KeyAlgorithm.ed25519 => KeyType.KEY_TYPE_ED25519,
},
);
final response = await connection.request(
UserAgentRequest(authChallengeRequest: req),
);
talker.info(
"Sent auth challenge request with pubkey ${base64Encode(pubkey)}",
);
talker.info('Received response from server, checking auth flow...');
if (response.hasAuthResult()) {
if (response.authResult != AuthResult.AUTH_RESULT_SUCCESS) {
throw Exception('Authentication failed: ${response.authResult}');
}
talker.info('Authentication successful, connection established');
return connection;
}
if (!response.hasAuthChallenge()) {
throw Exception(
'Expected AuthChallengeResponse, got ${response.whichPayload()}',
);
}
final challenge = _formatChallenge(response.authChallenge, pubkey);
talker.info(
'Received auth challenge, signing with key ${base64Encode(pubkey)}',
);
final signature = await key.sign(challenge);
final solutionResponse = await connection.request(
UserAgentRequest(authChallengeSolution: AuthChallengeSolution(signature: signature)),
);
talker.info('Sent auth challenge solution, waiting for server response...');
if (!solutionResponse.hasAuthResult()) {
throw Exception(
'Expected AuthChallengeSolutionResponse, got ${solutionResponse.whichPayload()}',
);
}
if (solutionResponse.authResult != AuthResult.AUTH_RESULT_SUCCESS) {
throw Exception('Authentication failed: ${solutionResponse.authResult}');
}
talker.info('Authentication successful, connection established');
return connection;
} catch (e) {
throw Exception('Failed to connect to server: $e');
}
}
Future<Connection> _connect(StoredServerInfo serverInfo) async {
final channel = ClientChannel(
serverInfo.address,
port: serverInfo.port,
options: ChannelOptions(
connectTimeout: const Duration(seconds: 10),
credentials: ChannelCredentials.secure(
onBadCertificate: (cert, host) {
return true;
},
),
),
);
final client = ArbiterServiceClient(channel);
final tx = StreamController<UserAgentRequest>();
final rx = client.userAgent(tx.stream);
return Connection(channel: channel, tx: tx, rx: rx);
}
List<int> _formatChallenge(AuthChallenge challenge, List<int> pubkey) {
final encodedPubkey = base64Encode(pubkey);
final payload = "${challenge.nonce}:$encodedPubkey";
return utf8.encode(payload);
}