feat(useragent): bootstrap / unseal flow implementattion
This commit is contained in:
@@ -5,6 +5,7 @@ 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:cryptography/cryptography.dart';
|
||||
import 'package:grpc/grpc.dart';
|
||||
import 'package:mtcore/markettakers.dart';
|
||||
|
||||
@@ -25,9 +26,11 @@ class Connection {
|
||||
}
|
||||
|
||||
Future<UserAgentResponse> receive() async {
|
||||
await _rx.moveNext();
|
||||
final hasValue = await _rx.moveNext();
|
||||
if (!hasValue) {
|
||||
throw Exception('Connection closed while waiting for server response.');
|
||||
}
|
||||
return _rx.current;
|
||||
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
@@ -64,6 +67,112 @@ List<int> formatChallenge(AuthChallenge challenge, List<int> pubkey) {
|
||||
return utf8.encode(payload);
|
||||
}
|
||||
|
||||
const _vaultKeyAssociatedData = 'arbiter.vault.password';
|
||||
|
||||
Future<BootstrapResult> bootstrapVault(
|
||||
Connection connection,
|
||||
String password,
|
||||
) async {
|
||||
final encryptedKey = await _encryptVaultKeyMaterial(connection, password);
|
||||
|
||||
await connection.send(
|
||||
UserAgentRequest(
|
||||
bootstrapEncryptedKey: BootstrapEncryptedKey(
|
||||
nonce: encryptedKey.nonce,
|
||||
ciphertext: encryptedKey.ciphertext,
|
||||
associatedData: encryptedKey.associatedData,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final response = await connection.receive();
|
||||
if (!response.hasBootstrapResult()) {
|
||||
throw Exception(
|
||||
'Expected bootstrap result, got ${response.whichPayload()}',
|
||||
);
|
||||
}
|
||||
|
||||
return response.bootstrapResult;
|
||||
}
|
||||
|
||||
Future<UnsealResult> unsealVault(Connection connection, String password) async {
|
||||
final encryptedKey = await _encryptVaultKeyMaterial(connection, password);
|
||||
|
||||
await connection.send(
|
||||
UserAgentRequest(
|
||||
unsealEncryptedKey: UnsealEncryptedKey(
|
||||
nonce: encryptedKey.nonce,
|
||||
ciphertext: encryptedKey.ciphertext,
|
||||
associatedData: encryptedKey.associatedData,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final response = await connection.receive();
|
||||
if (!response.hasUnsealResult()) {
|
||||
throw Exception('Expected unseal result, got ${response.whichPayload()}');
|
||||
}
|
||||
|
||||
return response.unsealResult;
|
||||
}
|
||||
|
||||
Future<_EncryptedVaultKey> _encryptVaultKeyMaterial(
|
||||
Connection connection,
|
||||
String password,
|
||||
) async {
|
||||
final keyExchange = X25519();
|
||||
final cipher = Xchacha20.poly1305Aead();
|
||||
final clientKeyPair = await keyExchange.newKeyPair();
|
||||
final clientPublicKey = await clientKeyPair.extractPublicKey();
|
||||
|
||||
await connection.send(
|
||||
UserAgentRequest(
|
||||
unsealStart: UnsealStart(clientPubkey: clientPublicKey.bytes),
|
||||
),
|
||||
);
|
||||
|
||||
final handshakeResponse = await connection.receive();
|
||||
if (!handshakeResponse.hasUnsealStartResponse()) {
|
||||
throw Exception(
|
||||
'Expected unseal handshake response, got ${handshakeResponse.whichPayload()}',
|
||||
);
|
||||
}
|
||||
|
||||
final serverPublicKey = SimplePublicKey(
|
||||
handshakeResponse.unsealStartResponse.serverPubkey,
|
||||
type: KeyPairType.x25519,
|
||||
);
|
||||
final sharedSecret = await keyExchange.sharedSecretKey(
|
||||
keyPair: clientKeyPair,
|
||||
remotePublicKey: serverPublicKey,
|
||||
);
|
||||
|
||||
final secretBox = await cipher.encrypt(
|
||||
utf8.encode(password),
|
||||
secretKey: sharedSecret,
|
||||
nonce: cipher.newNonce(),
|
||||
aad: utf8.encode(_vaultKeyAssociatedData),
|
||||
);
|
||||
|
||||
return _EncryptedVaultKey(
|
||||
nonce: secretBox.nonce,
|
||||
ciphertext: [...secretBox.cipherText, ...secretBox.mac.bytes],
|
||||
associatedData: utf8.encode(_vaultKeyAssociatedData),
|
||||
);
|
||||
}
|
||||
|
||||
class _EncryptedVaultKey {
|
||||
const _EncryptedVaultKey({
|
||||
required this.nonce,
|
||||
required this.ciphertext,
|
||||
required this.associatedData,
|
||||
});
|
||||
|
||||
final List<int> nonce;
|
||||
final List<int> ciphertext;
|
||||
final List<int> associatedData;
|
||||
}
|
||||
|
||||
Future<Connection> connectAndAuthorize(
|
||||
StoredServerInfo serverInfo,
|
||||
KeyHandle key, {
|
||||
@@ -90,12 +199,14 @@ Future<Connection> connectAndAuthorize(
|
||||
"Sent auth challenge request with pubkey ${base64Encode(pubkey)}",
|
||||
);
|
||||
|
||||
|
||||
final response = await connection.receive();
|
||||
|
||||
talker.info(
|
||||
'Received response from server, checking for auth challenge...',
|
||||
);
|
||||
talker.info('Received response from server, checking auth flow...');
|
||||
|
||||
if (response.hasAuthOk()) {
|
||||
talker.info('Authentication successful, connection established');
|
||||
return connection;
|
||||
}
|
||||
|
||||
if (!response.hasAuthChallenge()) {
|
||||
throw Exception(
|
||||
|
||||
Reference in New Issue
Block a user