mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-07-23 22:51:00 -04:00
encryption and dht work
This commit is contained in:
parent
7831deabd3
commit
c63eee26fd
9 changed files with 246 additions and 159 deletions
|
@ -24,6 +24,11 @@ import 'conversation.dart';
|
|||
|
||||
part 'contact_invite.g.dart';
|
||||
|
||||
class ContactInviteInvalidKeyException implements Exception {
|
||||
const ContactInviteInvalidKeyException(this.type) : super();
|
||||
final EncryptionKeyType type;
|
||||
}
|
||||
|
||||
class AcceptedContact {
|
||||
AcceptedContact({
|
||||
required this.profile,
|
||||
|
@ -238,8 +243,8 @@ Future<Uint8List> createContactInvitation(
|
|||
..chatRecordKey = localConversation.key.toProto()
|
||||
..expiration = expiration?.toInt64() ?? Int64.ZERO;
|
||||
final crprivbytes = crpriv.writeToBuffer();
|
||||
final encryptedContactRequestPrivate = await cs.encryptNoAuthWithNonce(
|
||||
crprivbytes, contactRequestWriter.secret);
|
||||
final encryptedContactRequestPrivate =
|
||||
await cs.encryptAeadWithNonce(crprivbytes, contactRequestWriter.secret);
|
||||
|
||||
// Create ContactRequest and embed contactrequestprivate
|
||||
final creq = ContactRequest()
|
||||
|
@ -315,11 +320,18 @@ class ValidContactInvitation {
|
|||
KeyPair writer;
|
||||
}
|
||||
|
||||
typedef GetEncryptionKeyCallback = Future<SecretKey> Function(
|
||||
EncryptionKeyType encryptionKeyType, Uint8List encryptedSecret);
|
||||
typedef GetEncryptionKeyCallback = Future<SecretKey?> Function(
|
||||
VeilidCryptoSystem cs,
|
||||
EncryptionKeyType encryptionKeyType,
|
||||
Uint8List encryptedSecret);
|
||||
|
||||
Future<ValidContactInvitation?> validateContactInvitation(
|
||||
{required ActiveAccountInfo activeAccountInfo,
|
||||
required Uint8List inviteData,
|
||||
required GetEncryptionKeyCallback getEncryptionKeyCallback}) async {
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
Future<ValidContactInvitation> validateContactInvitation(Uint8List inviteData,
|
||||
GetEncryptionKeyCallback getEncryptionKeyCallback) async {
|
||||
final signedContactInvitation =
|
||||
proto.SignedContactInvitation.fromBuffer(inviteData);
|
||||
|
||||
|
@ -334,19 +346,27 @@ Future<ValidContactInvitation> validateContactInvitation(Uint8List inviteData,
|
|||
late final ValidContactInvitation out;
|
||||
|
||||
final pool = await DHTRecordPool.instance();
|
||||
await (await pool.openRead(contactRequestInboxKey))
|
||||
.deleteScope((contactRequestInbox) async {
|
||||
final cs = await pool.veilid.getCryptoSystem(contactRequestInboxKey.kind);
|
||||
|
||||
// See if we're chatting to ourselves, if so, don't delete it here
|
||||
final ownKey = pool.getParentRecord(contactRequestInboxKey) != null;
|
||||
|
||||
await (await pool.openRead(contactRequestInboxKey, parent: accountRecordKey))
|
||||
.maybeDeleteScope(!ownKey, (contactRequestInbox) async {
|
||||
//
|
||||
final contactRequest =
|
||||
await contactRequestInbox.getProtobuf(proto.ContactRequest.fromBuffer);
|
||||
|
||||
// Decrypt contact request private
|
||||
final encryptionKeyType =
|
||||
EncryptionKeyType.fromProto(contactRequest!.encryptionKeyType);
|
||||
final writerSecret = await getEncryptionKeyCallback(
|
||||
encryptionKeyType, Uint8List.fromList(contactInvitation.writerSecret));
|
||||
final writerSecret = await getEncryptionKeyCallback(cs, encryptionKeyType,
|
||||
Uint8List.fromList(contactInvitation.writerSecret));
|
||||
if (writerSecret == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final cs = await pool.veilid.getCryptoSystem(contactRequestInboxKey.kind);
|
||||
final contactRequestPrivateBytes = await cs.decryptNoAuthWithNonce(
|
||||
final contactRequestPrivateBytes = await cs.decryptAeadWithNonce(
|
||||
Uint8List.fromList(contactRequest.private), writerSecret);
|
||||
final contactRequestPrivate =
|
||||
proto.ContactRequestPrivate.fromBuffer(contactRequestPrivateBytes);
|
||||
|
@ -360,8 +380,12 @@ Future<ValidContactInvitation> validateContactInvitation(Uint8List inviteData,
|
|||
// Verify
|
||||
final signature = proto.SignatureProto.fromProto(
|
||||
signedContactInvitation.identitySignature);
|
||||
await cs.verify(contactIdentityMaster.identityPublicKey,
|
||||
contactInvitationBytes, signature);
|
||||
try {
|
||||
await cs.verify(contactIdentityMaster.identityPublicKey,
|
||||
contactInvitationBytes, signature);
|
||||
} on Exception catch (_) {
|
||||
throw ContactInviteInvalidKeyException(encryptionKeyType);
|
||||
}
|
||||
|
||||
final writer = KeyPair(
|
||||
key: proto.CryptoKeyProto.fromProto(contactRequestPrivate.writerKey),
|
||||
|
@ -385,10 +409,18 @@ Future<AcceptedContact?> acceptContactInvitation(
|
|||
ValidContactInvitation validContactInvitation) async {
|
||||
final pool = await DHTRecordPool.instance();
|
||||
try {
|
||||
// Ensure we don't delete this if we're trying to chat to self
|
||||
final ownKey =
|
||||
pool.getParentRecord(validContactInvitation.contactRequestInboxKey) !=
|
||||
null;
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
return (await pool.openWrite(validContactInvitation.contactRequestInboxKey,
|
||||
validContactInvitation.writer))
|
||||
validContactInvitation.writer,
|
||||
parent: accountRecordKey))
|
||||
// ignore: prefer_expression_function_bodies
|
||||
.deleteScope((contactRequestInbox) async {
|
||||
.maybeDeleteScope(!ownKey, (contactRequestInbox) async {
|
||||
// Create local conversation key for this
|
||||
// contact and send via contact response
|
||||
return createConversation(
|
||||
|
@ -441,9 +473,18 @@ Future<AcceptedContact?> acceptContactInvitation(
|
|||
Future<bool> rejectContactInvitation(ActiveAccountInfo activeAccountInfo,
|
||||
ValidContactInvitation validContactInvitation) async {
|
||||
final pool = await DHTRecordPool.instance();
|
||||
|
||||
// Ensure we don't delete this if we're trying to chat to self
|
||||
final ownKey =
|
||||
pool.getParentRecord(validContactInvitation.contactRequestInboxKey) !=
|
||||
null;
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
return (await pool.openWrite(validContactInvitation.contactRequestInboxKey,
|
||||
validContactInvitation.writer))
|
||||
.deleteScope((contactRequestInbox) async {
|
||||
validContactInvitation.writer,
|
||||
parent: accountRecordKey))
|
||||
.maybeDeleteScope(!ownKey, (contactRequestInbox) async {
|
||||
final cs = await pool.veilid
|
||||
.getCryptoSystem(validContactInvitation.contactRequestInboxKey.kind);
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|||
|
||||
import '../entities/entities.dart';
|
||||
import '../log/loggy.dart';
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import 'local_accounts.dart';
|
||||
|
||||
|
@ -52,7 +53,7 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
|
|||
state = AsyncValue.data(updated);
|
||||
}
|
||||
|
||||
Future<bool> _loginCommon(
|
||||
Future<bool> _decryptedLogin(
|
||||
IdentityMaster identityMaster, SecretKey identitySecret) async {
|
||||
final veilid = await eventualVeilid.future;
|
||||
final cs =
|
||||
|
@ -89,7 +90,8 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
|
|||
return true;
|
||||
}
|
||||
|
||||
Future<bool> loginWithNone(TypedKey accountMasterRecordKey) async {
|
||||
Future<bool> login(TypedKey accountMasterRecordKey,
|
||||
EncryptionKeyType encryptionKeyType, String encryptionKey) async {
|
||||
final localAccounts = ref.read(localAccountsProvider).requireValue;
|
||||
|
||||
// Get account, throws if not found
|
||||
|
@ -99,42 +101,19 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
|
|||
// Log in with this local account
|
||||
|
||||
// Derive key from password
|
||||
if (localAccount.encryptionKeyType != EncryptionKeyType.none) {
|
||||
if (localAccount.encryptionKeyType != encryptionKeyType) {
|
||||
throw Exception('Wrong authentication type');
|
||||
}
|
||||
|
||||
final identitySecret =
|
||||
SecretKey.fromBytes(localAccount.identitySecretBytes);
|
||||
final identitySecret = await decryptSecretFromBytes(
|
||||
secretBytes: localAccount.identitySecretBytes,
|
||||
cryptoKind: localAccount.identityMaster.identityRecordKey.kind,
|
||||
encryptionKeyType: localAccount.encryptionKeyType,
|
||||
encryptionKey: encryptionKey,
|
||||
);
|
||||
|
||||
// Validate this secret with the identity public key and log in
|
||||
return _loginCommon(localAccount.identityMaster, identitySecret);
|
||||
}
|
||||
|
||||
Future<bool> loginWithPasswordOrPin(
|
||||
TypedKey accountMasterRecordKey, String encryptionKey) async {
|
||||
final veilid = await eventualVeilid.future;
|
||||
final localAccounts = ref.read(localAccountsProvider).requireValue;
|
||||
|
||||
// Get account, throws if not found
|
||||
final localAccount = localAccounts.firstWhere(
|
||||
(la) => la.identityMaster.masterRecordKey == accountMasterRecordKey);
|
||||
|
||||
// Log in with this local account
|
||||
|
||||
// Derive key from password
|
||||
if (localAccount.encryptionKeyType != EncryptionKeyType.password ||
|
||||
localAccount.encryptionKeyType != EncryptionKeyType.pin) {
|
||||
throw Exception('Wrong authentication type');
|
||||
}
|
||||
final cs = await veilid
|
||||
.getCryptoSystem(localAccount.identityMaster.identityRecordKey.kind);
|
||||
|
||||
final identitySecret = SecretKey.fromBytes(
|
||||
await cs.decryptNoAuthWithPassword(
|
||||
localAccount.identitySecretBytes, encryptionKey));
|
||||
|
||||
// Validate this secret with the identity public key and log in
|
||||
return _loginCommon(localAccount.identityMaster, identitySecret);
|
||||
return _decryptedLogin(localAccount.identityMaster, identitySecret);
|
||||
}
|
||||
|
||||
Future<void> logout(TypedKey? accountMasterRecordKey) async {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue