encryption and dht work

This commit is contained in:
Christien Rioux 2023-09-24 22:35:54 -04:00
parent 7831deabd3
commit c63eee26fd
9 changed files with 246 additions and 159 deletions

View File

@ -83,15 +83,21 @@
"copy_invitation": "Copy Invitation", "copy_invitation": "Copy Invitation",
"invitation_copied": "Invitation Copied" "invitation_copied": "Invitation Copied"
}, },
"contact_invite": {
"message_from_contact": "Message from contact",
"validating": "Validating...",
"failed_to_accept": "Failed to accept contact invite",
"failed_to_reject": "Failed to reject contact invite",
"invalid_invitation": "Invalid invitation",
"protected_with_pin": "Contact invite is protected with a PIN",
"protected_with_password": "Contact invite is protected with a password",
"invalid_pin": "Invalid PIN",
"invalid_password": "Invalid password"
},
"paste_invite_dialog": { "paste_invite_dialog": {
"title": "Paste Contact Invite", "title": "Paste Contact Invite",
"paste_invite_here": "Paste your contact invite here:", "paste_invite_here": "Paste your contact invite here:",
"paste": "Paste", "paste": "Paste"
"message_from_contact": "Message from contact",
"validating": "Validating...",
"invalid_invitation": "Invalid invitation",
"failed_to_accept": "Failed to accept contact invite",
"failed_to_reject": "Failed to reject contact invite"
}, },
"enter_pin_dialog": { "enter_pin_dialog": {
"enter_pin": "Enter PIN", "enter_pin": "Enter PIN",

View File

@ -33,9 +33,6 @@ class PasteInviteDialog extends ConsumerStatefulWidget {
class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> { class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
final _pasteTextController = TextEditingController(); final _pasteTextController = TextEditingController();
EncryptionKeyType _encryptionKeyType = EncryptionKeyType.none;
String _encryptionKey = '';
Timestamp? _expiration;
ValidContactInvitation? _validInvitation; ValidContactInvitation? _validInvitation;
bool _validatingPaste = false; bool _validatingPaste = false;
bool _isAccepting = false; bool _isAccepting = false;
@ -45,61 +42,6 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
super.initState(); super.initState();
} }
// Future<void> _onNoneEncryptionSelected(bool selected) async {
// setState(() {
// if (selected) {
// _encryptionKeyType = EncryptionKeyType.none;
// }
// });
// }
// Future<void> _onPinEncryptionSelected(bool selected) async {
// final description = translate('receive_invite_dialog.pin_description');
// final pin = await showDialog<String>(
// context: context,
// builder: (context) => EnterPinDialog(description: description));
// if (pin == null) {
// return;
// }
// // ignore: use_build_context_synchronously
// if (!context.mounted) {
// return;
// }
// final matchpin = await showDialog<String>(
// context: context,
// builder: (context) => EnterPinDialog(
// matchPin: pin,
// description: description,
// ));
// if (matchpin == null) {
// return;
// } else if (pin == matchpin) {
// setState(() {
// _encryptionKeyType = EncryptionKeyType.pin;
// _encryptionKey = pin;
// });
// } else {
// // ignore: use_build_context_synchronously
// if (!context.mounted) {
// return;
// }
// showErrorToast(
// context, translate('receive_invite_dialog.pin_does_not_match'));
// setState(() {
// _encryptionKeyType = EncryptionKeyType.none;
// _encryptionKey = '';
// });
// }
// }
// Future<void> _onPasswordEncryptionSelected(bool selected) async {
// setState(() {
// if (selected) {
// _encryptionKeyType = EncryptionKeyType.password;
// }
// });
// }
Future<void> _onAccept() async { Future<void> _onAccept() async {
final navigator = Navigator.of(context); final navigator = Navigator.of(context);
@ -133,7 +75,7 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
..invalidate(fetchContactListProvider); ..invalidate(fetchContactListProvider);
} else { } else {
if (context.mounted) { if (context.mounted) {
showErrorToast(context, 'paste_invite_dialog.failed_to_accept'); showErrorToast(context, 'contact_invite.failed_to_accept');
} }
} }
} }
@ -163,7 +105,7 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
// do nothing right now // do nothing right now
} else { } else {
if (context.mounted) { if (context.mounted) {
showErrorToast(context, 'paste_invite_dialog.failed_to_reject'); showErrorToast(context, 'contact_invite.failed_to_reject');
} }
} }
} }
@ -203,23 +145,74 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
final inviteDataBase64 = lines.sublist(firstline, lastline).join(); final inviteDataBase64 = lines.sublist(firstline, lastline).join();
final inviteData = base64UrlNoPadDecode(inviteDataBase64); final inviteData = base64UrlNoPadDecode(inviteDataBase64);
final activeAccountInfo =
await ref.read(fetchActiveAccountProvider.future);
if (activeAccountInfo == null) {
setState(() {
_validatingPaste = false;
_validInvitation = null;
});
return;
}
setState(() { setState(() {
_validatingPaste = true; _validatingPaste = true;
_validInvitation = null; _validInvitation = null;
}); });
final validatedContactInvitation = await validateContactInvitation( final validatedContactInvitation = await validateContactInvitation(
inviteData, (encryptionKeyType, encryptedSecret) async { activeAccountInfo: activeAccountInfo,
switch (encryptionKeyType) { inviteData: inviteData,
case EncryptionKeyType.none: getEncryptionKeyCallback:
return SecretKey.fromBytes(encryptedSecret); (cs, encryptionKeyType, encryptedSecret) async {
case EncryptionKeyType.pin: String encryptionKey;
//xxx switch (encryptionKeyType) {
return SecretKey.fromBytes(encryptedSecret); case EncryptionKeyType.none:
case EncryptionKeyType.password: encryptionKey = '';
//xxx case EncryptionKeyType.pin:
return SecretKey.fromBytes(encryptedSecret); final description =
} translate('contact_invite.protected_with_pin');
}); if (!context.mounted) {
return null;
}
final pin = await showDialog<String>(
context: context,
builder: (context) =>
EnterPinDialog(description: description));
if (pin == null) {
return null;
}
encryptionKey = pin;
case EncryptionKeyType.password:
final description =
translate('contact_invite.protected_with_pin');
if (!context.mounted) {
return null;
}
final password = await showDialog<String>(
context: context,
builder: (context) =>
EnterPinDialog(description: description));
if (password == null) {
return null;
}
encryptionKey = password;
}
return decryptSecretFromBytes(
secretBytes: encryptedSecret,
cryptoKind: cs.kind(),
encryptionKeyType: encryptionKeyType,
encryptionKey: encryptionKey);
});
// Check if validation was cancelled
if (validatedContactInvitation == null) {
setState(() {
_validatingPaste = false;
_validInvitation = null;
});
return;
}
// Verify expiration // Verify expiration
// xxx // xxx
@ -227,11 +220,29 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
_validatingPaste = false; _validatingPaste = false;
_validInvitation = validatedContactInvitation; _validInvitation = validatedContactInvitation;
}); });
} on ContactInviteInvalidKeyException catch (e) {
String errorText;
switch (e.type) {
case EncryptionKeyType.none:
errorText = translate('contact_invite.invalid_invitation');
case EncryptionKeyType.password:
errorText = translate('contact_invite.invalid_pin');
case EncryptionKeyType.pin:
errorText = translate('contact_invite.invalid_password');
}
if (context.mounted) {
showErrorToast(context, errorText);
}
setState(() {
_validatingPaste = false;
_validInvitation = null;
});
} on Exception catch (_) { } on Exception catch (_) {
setState(() { setState(() {
_validatingPaste = false; _validatingPaste = false;
_validInvitation = null; _validInvitation = null;
}); });
rethrow;
} }
} }
@ -277,7 +288,7 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
)).paddingLTRB(0, 0, 0, 8), )).paddingLTRB(0, 0, 0, 8),
if (_validatingPaste) if (_validatingPaste)
Column(children: [ Column(children: [
Text(translate('paste_invite_dialog.validating')) Text(translate('contact_invite.validating'))
.paddingLTRB(0, 0, 0, 8), .paddingLTRB(0, 0, 0, 8),
buildProgressIndicator(context), buildProgressIndicator(context),
]).paddingAll(16).toCenter(), ]).paddingAll(16).toCenter(),
@ -285,7 +296,7 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
!_validatingPaste && !_validatingPaste &&
_pasteTextController.text.isNotEmpty) _pasteTextController.text.isNotEmpty)
Column(children: [ Column(children: [
Text(translate('paste_invite_dialog.invalid_invitation')), Text(translate('contact_invite.invalid_invitation')),
const Icon(Icons.error) const Icon(Icons.error)
]).paddingAll(16).toCenter(), ]).paddingAll(16).toCenter(),
if (_validInvitation != null && !_validatingPaste) if (_validInvitation != null && !_validatingPaste)

View File

@ -196,30 +196,42 @@ class ScanInviteDialogState extends ConsumerState<ScanInviteDialog> {
final inviteDataBase64 = lines.sublist(firstline, lastline).join(); final inviteDataBase64 = lines.sublist(firstline, lastline).join();
final inviteData = base64UrlNoPadDecode(inviteDataBase64); final inviteData = base64UrlNoPadDecode(inviteDataBase64);
final activeAccountInfo =
await ref.read(fetchActiveAccountProvider.future);
if (activeAccountInfo == null) {
setState(() {
_validatingPaste = false;
_validInvitation = null;
});
return;
}
setState(() { setState(() {
_validatingPaste = true; _validatingPaste = true;
_validInvitation = null; _validInvitation = null;
}); });
final validatedContactInvitation = await validateContactInvitation( // final validatedContactInvitation = await validateContactInvitation(
inviteData, (encryptionKeyType, encryptedSecret) async { // activeAccountInfo: activeAccountInfo,
switch (encryptionKeyType) { // inviteData: inviteData,
case EncryptionKeyType.none: // getEncryptionKeyCallback: (encryptionKeyType, encryptedSecret) async {
return SecretKey.fromBytes(encryptedSecret); // switch (encryptionKeyType) {
case EncryptionKeyType.pin: // case EncryptionKeyType.none:
//xxx // return SecretKey.fromBytes(encryptedSecret);
return SecretKey.fromBytes(encryptedSecret); // case EncryptionKeyType.pin:
case EncryptionKeyType.password: // //xxx
//xxx // return SecretKey.fromBytes(encryptedSecret);
return SecretKey.fromBytes(encryptedSecret); // case EncryptionKeyType.password:
} // //xxx
}); // return SecretKey.fromBytes(encryptedSecret);
// }
// });
// Verify expiration // Verify expiration
// xxx // xxx
setState(() { // setState(() {
_validatingPaste = false; // _validatingPaste = false;
_validInvitation = validatedContactInvitation; // _validInvitation = validatedContactInvitation;
}); // });
} on Exception catch (_) { } on Exception catch (_) {
setState(() { setState(() {
_validatingPaste = false; _validatingPaste = false;

View File

@ -9,6 +9,7 @@ import 'package:go_router/go_router.dart';
import '../components/default_app_bar.dart'; import '../components/default_app_bar.dart';
import '../components/signal_strength_meter.dart'; import '../components/signal_strength_meter.dart';
import '../entities/entities.dart';
import '../providers/local_accounts.dart'; import '../providers/local_accounts.dart';
import '../providers/logins.dart'; import '../providers/logins.dart';
import '../providers/window_control.dart'; import '../providers/window_control.dart';
@ -59,9 +60,9 @@ class NewAccountPageState extends ConsumerState<NewAccountPage> {
title: title); title: title);
// Log in the new account by default with no pin // Log in the new account by default with no pin
final ok = await logins final ok = await logins.login(localAccount.identityMaster.masterRecordKey,
.loginWithNone(localAccount.identityMaster.masterRecordKey); EncryptionKeyType.none, '');
assert(ok == true, 'login with none should never fail'); assert(ok, 'login with none should never fail');
} on Exception catch (_) { } on Exception catch (_) {
await imws.delete(); await imws.delete();
rethrow; rethrow;

View File

@ -24,6 +24,11 @@ import 'conversation.dart';
part 'contact_invite.g.dart'; part 'contact_invite.g.dart';
class ContactInviteInvalidKeyException implements Exception {
const ContactInviteInvalidKeyException(this.type) : super();
final EncryptionKeyType type;
}
class AcceptedContact { class AcceptedContact {
AcceptedContact({ AcceptedContact({
required this.profile, required this.profile,
@ -238,8 +243,8 @@ Future<Uint8List> createContactInvitation(
..chatRecordKey = localConversation.key.toProto() ..chatRecordKey = localConversation.key.toProto()
..expiration = expiration?.toInt64() ?? Int64.ZERO; ..expiration = expiration?.toInt64() ?? Int64.ZERO;
final crprivbytes = crpriv.writeToBuffer(); final crprivbytes = crpriv.writeToBuffer();
final encryptedContactRequestPrivate = await cs.encryptNoAuthWithNonce( final encryptedContactRequestPrivate =
crprivbytes, contactRequestWriter.secret); await cs.encryptAeadWithNonce(crprivbytes, contactRequestWriter.secret);
// Create ContactRequest and embed contactrequestprivate // Create ContactRequest and embed contactrequestprivate
final creq = ContactRequest() final creq = ContactRequest()
@ -315,11 +320,18 @@ class ValidContactInvitation {
KeyPair writer; KeyPair writer;
} }
typedef GetEncryptionKeyCallback = Future<SecretKey> Function( typedef GetEncryptionKeyCallback = Future<SecretKey?> Function(
EncryptionKeyType encryptionKeyType, Uint8List encryptedSecret); 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 = final signedContactInvitation =
proto.SignedContactInvitation.fromBuffer(inviteData); proto.SignedContactInvitation.fromBuffer(inviteData);
@ -334,19 +346,27 @@ Future<ValidContactInvitation> validateContactInvitation(Uint8List inviteData,
late final ValidContactInvitation out; late final ValidContactInvitation out;
final pool = await DHTRecordPool.instance(); final pool = await DHTRecordPool.instance();
await (await pool.openRead(contactRequestInboxKey)) final cs = await pool.veilid.getCryptoSystem(contactRequestInboxKey.kind);
.deleteScope((contactRequestInbox) async {
// 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 = final contactRequest =
await contactRequestInbox.getProtobuf(proto.ContactRequest.fromBuffer); await contactRequestInbox.getProtobuf(proto.ContactRequest.fromBuffer);
// Decrypt contact request private // Decrypt contact request private
final encryptionKeyType = final encryptionKeyType =
EncryptionKeyType.fromProto(contactRequest!.encryptionKeyType); EncryptionKeyType.fromProto(contactRequest!.encryptionKeyType);
final writerSecret = await getEncryptionKeyCallback( final writerSecret = await getEncryptionKeyCallback(cs, encryptionKeyType,
encryptionKeyType, Uint8List.fromList(contactInvitation.writerSecret)); Uint8List.fromList(contactInvitation.writerSecret));
if (writerSecret == null) {
return null;
}
final cs = await pool.veilid.getCryptoSystem(contactRequestInboxKey.kind); final contactRequestPrivateBytes = await cs.decryptAeadWithNonce(
final contactRequestPrivateBytes = await cs.decryptNoAuthWithNonce(
Uint8List.fromList(contactRequest.private), writerSecret); Uint8List.fromList(contactRequest.private), writerSecret);
final contactRequestPrivate = final contactRequestPrivate =
proto.ContactRequestPrivate.fromBuffer(contactRequestPrivateBytes); proto.ContactRequestPrivate.fromBuffer(contactRequestPrivateBytes);
@ -360,8 +380,12 @@ Future<ValidContactInvitation> validateContactInvitation(Uint8List inviteData,
// Verify // Verify
final signature = proto.SignatureProto.fromProto( final signature = proto.SignatureProto.fromProto(
signedContactInvitation.identitySignature); signedContactInvitation.identitySignature);
await cs.verify(contactIdentityMaster.identityPublicKey, try {
contactInvitationBytes, signature); await cs.verify(contactIdentityMaster.identityPublicKey,
contactInvitationBytes, signature);
} on Exception catch (_) {
throw ContactInviteInvalidKeyException(encryptionKeyType);
}
final writer = KeyPair( final writer = KeyPair(
key: proto.CryptoKeyProto.fromProto(contactRequestPrivate.writerKey), key: proto.CryptoKeyProto.fromProto(contactRequestPrivate.writerKey),
@ -385,10 +409,18 @@ Future<AcceptedContact?> acceptContactInvitation(
ValidContactInvitation validContactInvitation) async { ValidContactInvitation validContactInvitation) async {
final pool = await DHTRecordPool.instance(); final pool = await DHTRecordPool.instance();
try { 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, return (await pool.openWrite(validContactInvitation.contactRequestInboxKey,
validContactInvitation.writer)) validContactInvitation.writer,
parent: accountRecordKey))
// ignore: prefer_expression_function_bodies // ignore: prefer_expression_function_bodies
.deleteScope((contactRequestInbox) async { .maybeDeleteScope(!ownKey, (contactRequestInbox) async {
// Create local conversation key for this // Create local conversation key for this
// contact and send via contact response // contact and send via contact response
return createConversation( return createConversation(
@ -441,9 +473,18 @@ Future<AcceptedContact?> acceptContactInvitation(
Future<bool> rejectContactInvitation(ActiveAccountInfo activeAccountInfo, Future<bool> rejectContactInvitation(ActiveAccountInfo activeAccountInfo,
ValidContactInvitation validContactInvitation) async { ValidContactInvitation validContactInvitation) async {
final pool = await DHTRecordPool.instance(); 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, return (await pool.openWrite(validContactInvitation.contactRequestInboxKey,
validContactInvitation.writer)) validContactInvitation.writer,
.deleteScope((contactRequestInbox) async { parent: accountRecordKey))
.maybeDeleteScope(!ownKey, (contactRequestInbox) async {
final cs = await pool.veilid final cs = await pool.veilid
.getCryptoSystem(validContactInvitation.contactRequestInboxKey.kind); .getCryptoSystem(validContactInvitation.contactRequestInboxKey.kind);

View File

@ -5,6 +5,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../entities/entities.dart'; import '../entities/entities.dart';
import '../log/loggy.dart'; import '../log/loggy.dart';
import '../tools/tools.dart';
import '../veilid_support/veilid_support.dart'; import '../veilid_support/veilid_support.dart';
import 'local_accounts.dart'; import 'local_accounts.dart';
@ -52,7 +53,7 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
state = AsyncValue.data(updated); state = AsyncValue.data(updated);
} }
Future<bool> _loginCommon( Future<bool> _decryptedLogin(
IdentityMaster identityMaster, SecretKey identitySecret) async { IdentityMaster identityMaster, SecretKey identitySecret) async {
final veilid = await eventualVeilid.future; final veilid = await eventualVeilid.future;
final cs = final cs =
@ -89,7 +90,8 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
return true; return true;
} }
Future<bool> loginWithNone(TypedKey accountMasterRecordKey) async { Future<bool> login(TypedKey accountMasterRecordKey,
EncryptionKeyType encryptionKeyType, String encryptionKey) async {
final localAccounts = ref.read(localAccountsProvider).requireValue; final localAccounts = ref.read(localAccountsProvider).requireValue;
// Get account, throws if not found // Get account, throws if not found
@ -99,42 +101,19 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
// Log in with this local account // Log in with this local account
// Derive key from password // Derive key from password
if (localAccount.encryptionKeyType != EncryptionKeyType.none) { if (localAccount.encryptionKeyType != encryptionKeyType) {
throw Exception('Wrong authentication type'); throw Exception('Wrong authentication type');
} }
final identitySecret = final identitySecret = await decryptSecretFromBytes(
SecretKey.fromBytes(localAccount.identitySecretBytes); secretBytes: localAccount.identitySecretBytes,
cryptoKind: localAccount.identityMaster.identityRecordKey.kind,
encryptionKeyType: localAccount.encryptionKeyType,
encryptionKey: encryptionKey,
);
// Validate this secret with the identity public key and log in // Validate this secret with the identity public key and log in
return _loginCommon(localAccount.identityMaster, identitySecret); return _decryptedLogin(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);
} }
Future<void> logout(TypedKey? accountMasterRecordKey) async { Future<void> logout(TypedKey? accountMasterRecordKey) async {

View File

@ -9,16 +9,37 @@ Future<Uint8List> encryptSecretToBytes(
String encryptionKey = ''}) async { String encryptionKey = ''}) async {
final veilid = await eventualVeilid.future; final veilid = await eventualVeilid.future;
late final Uint8List identitySecretBytes; late final Uint8List secretBytes;
switch (encryptionKeyType) { switch (encryptionKeyType) {
case EncryptionKeyType.none: case EncryptionKeyType.none:
identitySecretBytes = secret.decode(); secretBytes = secret.decode();
case EncryptionKeyType.pin: case EncryptionKeyType.pin:
case EncryptionKeyType.password: case EncryptionKeyType.password:
final cs = await veilid.getCryptoSystem(cryptoKind); final cs = await veilid.getCryptoSystem(cryptoKind);
identitySecretBytes = secretBytes =
await cs.encryptNoAuthWithPassword(secret.decode(), encryptionKey); await cs.encryptAeadWithPassword(secret.decode(), encryptionKey);
} }
return identitySecretBytes; return secretBytes;
}
Future<SecretKey> decryptSecretFromBytes(
{required Uint8List secretBytes,
required CryptoKind cryptoKind,
EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
String encryptionKey = ''}) async {
final veilid = await eventualVeilid.future;
late final SecretKey secret;
switch (encryptionKeyType) {
case EncryptionKeyType.none:
secret = SecretKey.fromBytes(secretBytes);
case EncryptionKeyType.pin:
case EncryptionKeyType.password:
final cs = await veilid.getCryptoSystem(cryptoKind);
secret = SecretKey.fromBytes(
await cs.decryptAeadWithPassword(secretBytes, encryptionKey));
}
return secret;
} }

View File

@ -91,6 +91,15 @@ class DHTRecord {
} }
} }
Future<T> maybeDeleteScope<T>(
bool delete, Future<T> Function(DHTRecord) scopeFunction) async {
if (delete) {
return deleteScope(scopeFunction);
} else {
return scope(scopeFunction);
}
}
Future<Uint8List?> get( Future<Uint8List?> get(
{int subkey = -1, {int subkey = -1,
bool forceRefresh = false, bool forceRefresh = false,

View File

@ -120,12 +120,12 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
while (currentDeps.isNotEmpty) { while (currentDeps.isNotEmpty) {
final nextDep = currentDeps.removeLast(); final nextDep = currentDeps.removeLast();
// Ensure we get the exclusive lock on this record
await _recordOpened(nextDep);
// Remove this child from its parent // Remove this child from its parent
await _removeDependency(nextDep); await _removeDependency(nextDep);
// Ensure all records are closed before delete
assert(!_opened.containsKey(nextDep), 'should not delete opened record');
allDeps.add(nextDep); allDeps.add(nextDep);
final childDeps = final childDeps =
_state.childrenByParent[nextDep.toJson()]?.toList() ?? []; _state.childrenByParent[nextDep.toJson()]?.toList() ?? [];
@ -136,6 +136,7 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
final allFutures = <Future<void>>[]; final allFutures = <Future<void>>[];
for (final dep in allDeps) { for (final dep in allDeps) {
allFutures.add(_routingContext.deleteDHTRecord(dep)); allFutures.add(_routingContext.deleteDHTRecord(dep));
recordClosed(dep);
} }
await Future.wait(allFutures); await Future.wait(allFutures);
} }
@ -324,4 +325,10 @@ class DHTRecordPool with AsyncTableDBBacked<DHTRecordPoolAllocations> {
defaultSubkey: defaultSubkey, defaultSubkey: defaultSubkey,
crypto: crypto, crypto: crypto,
); );
/// Get the parent of a DHTRecord key if it exists
TypedKey? getParentRecord(TypedKey child) {
final childJson = child.toJson();
return _state.parentByChild[childJson];
}
} }