This commit is contained in:
Christien Rioux 2024-02-16 09:46:42 -07:00
parent fcccacfafa
commit f936cb069e
5 changed files with 61 additions and 27 deletions

View File

@ -299,17 +299,18 @@ class AccountRepository {
Future<bool> _decryptedLogin( Future<bool> _decryptedLogin(
IdentityMaster identityMaster, SecretKey identitySecret) async { IdentityMaster identityMaster, SecretKey identitySecret) async {
final cs = await Veilid.instance // Verify identity secret works and return the valid cryptosystem
.getCryptoSystem(identityMaster.identityRecordKey.kind); final cs = await identityMaster.validateIdentitySecret(identitySecret);
final keyOk = await cs.validateKeyPair(
identityMaster.identityPublicKey, identitySecret);
if (!keyOk) {
throw Exception('Identity is corrupted');
}
// Read the identity key to get the account keys // Read the identity key to get the account keys
final accountRecordInfo = await identityMaster.readAccountFromIdentity( final accountRecordInfoList = await identityMaster.readAccountsFromIdentity(
identitySecret: identitySecret, accountKey: veilidChatAccountKey); identitySecret: identitySecret, accountKey: veilidChatAccountKey);
if (accountRecordInfoList.length > 1) {
throw IdentityException.limitExceeded;
} else if (accountRecordInfoList.isEmpty) {
throw IdentityException.noAccount;
}
final accountRecordInfo = accountRecordInfoList.single;
// Add to user logins and select it // Add to user logins and select it
final userLogins = await _userLogins.get(); final userLogins = await _userLogins.get();

View File

@ -199,7 +199,7 @@ class DHTRecord {
// See if the encrypted data returned is exactly the same // See if the encrypted data returned is exactly the same
// if so, shortcut and don't bother decrypting it // if so, shortcut and don't bother decrypting it
if (newValueData.data == encryptedNewValue) { if (newValueData.data.equals(encryptedNewValue)) {
if (isUpdated) { if (isUpdated) {
addLocalValueChange(newValue, subkey); addLocalValueChange(newValue, subkey);
} }
@ -243,7 +243,7 @@ class DHTRecord {
// The encrypted data returned should be exactly the same // The encrypted data returned should be exactly the same
// as what we are trying to set, // as what we are trying to set,
// otherwise we still need to keep trying to set the value // otherwise we still need to keep trying to set the value
} while (newValueData.data != encryptedNewValue); } while (!newValueData.data.equals(encryptedNewValue));
final isUpdated = newValueData.seq != lastSeq; final isUpdated = newValueData.seq != lastSeq;
if (isUpdated) { if (isUpdated) {

View File

@ -10,6 +10,20 @@ import '../dht_support/dht_support.dart';
part 'identity.freezed.dart'; part 'identity.freezed.dart';
part 'identity.g.dart'; part 'identity.g.dart';
// Identity errors
enum IdentityException implements Exception {
readError('identity could not be read'),
noAccount('no account record info'),
limitExceeded('too many items for the limit'),
invalid('identity is corrupted or secret is invalid');
const IdentityException(this.message);
final String message;
@override
String toString() => 'IdentityException($name): $message';
}
// AccountOwnerInfo is the key and owner info for the account dht key that is // AccountOwnerInfo is the key and owner info for the account dht key that is
// stored in the identity key // stored in the identity key
@freezed @freezed
@ -82,6 +96,12 @@ extension IdentityMasterExtension on IdentityMaster {
await (await pool.openRead(masterRecordKey)).delete(); await (await pool.openRead(masterRecordKey)).delete();
} }
Future<VeilidCryptoSystem> get identityCrypto =>
Veilid.instance.getCryptoSystem(identityRecordKey.kind);
Future<VeilidCryptoSystem> get masterCrypto =>
Veilid.instance.getCryptoSystem(masterRecordKey.kind);
KeyPair identityWriter(SecretKey secret) => KeyPair identityWriter(SecretKey secret) =>
KeyPair(key: identityPublicKey, secret: secret); KeyPair(key: identityPublicKey, secret: secret);
@ -91,7 +111,17 @@ extension IdentityMasterExtension on IdentityMaster {
TypedKey identityPublicTypedKey() => TypedKey identityPublicTypedKey() =>
TypedKey(kind: identityRecordKey.kind, value: identityPublicKey); TypedKey(kind: identityRecordKey.kind, value: identityPublicKey);
Future<AccountRecordInfo> readAccountFromIdentity( Future<VeilidCryptoSystem> validateIdentitySecret(
SecretKey identitySecret) async {
final cs = await identityCrypto;
final keyOk = await cs.validateKeyPair(identityPublicKey, identitySecret);
if (!keyOk) {
throw IdentityException.invalid;
}
return cs;
}
Future<List<AccountRecordInfo>> readAccountsFromIdentity(
{required SharedSecret identitySecret, {required SharedSecret identitySecret,
required String accountKey}) async { required String accountKey}) async {
// Read the identity key to get the account keys // Read the identity key to get the account keys
@ -100,23 +130,19 @@ extension IdentityMasterExtension on IdentityMaster {
final identityRecordCrypto = await DHTRecordCryptoPrivate.fromSecret( final identityRecordCrypto = await DHTRecordCryptoPrivate.fromSecret(
identityRecordKey.kind, identitySecret); identityRecordKey.kind, identitySecret);
late final AccountRecordInfo accountRecordInfo; late final List<AccountRecordInfo> accountRecordInfo;
await (await pool.openRead(identityRecordKey, await (await pool.openRead(identityRecordKey,
parent: masterRecordKey, crypto: identityRecordCrypto)) parent: masterRecordKey, crypto: identityRecordCrypto))
.scope((identityRec) async { .scope((identityRec) async {
final identity = await identityRec.getJson(Identity.fromJson); final identity = await identityRec.getJson(Identity.fromJson);
if (identity == null) { if (identity == null) {
// Identity could not be read or decrypted from DHT // Identity could not be read or decrypted from DHT
throw StateError('identity could not be read'); throw IdentityException.readError;
} }
final accountRecords = IMapOfSets.from(identity.accountRecords); final accountRecords = IMapOfSets.from(identity.accountRecords);
final vcAccounts = accountRecords.get(accountKey); final vcAccounts = accountRecords.get(accountKey);
if (vcAccounts.length != 1) {
// No account, or multiple accounts somehow associated with identity
throw StateError('no single account record info');
}
accountRecordInfo = vcAccounts.first; accountRecordInfo = vcAccounts.toList();
}); });
return accountRecordInfo; return accountRecordInfo;
@ -128,6 +154,7 @@ extension IdentityMasterExtension on IdentityMaster {
required SharedSecret identitySecret, required SharedSecret identitySecret,
required String accountKey, required String accountKey,
required Future<T> Function(TypedKey parent) createAccountCallback, required Future<T> Function(TypedKey parent) createAccountCallback,
int maxAccounts = 1,
}) async { }) async {
final pool = DHTRecordPool.instance; final pool = DHTRecordPool.instance;
@ -153,11 +180,14 @@ extension IdentityMasterExtension on IdentityMaster {
await identityRec.eventualUpdateJson(Identity.fromJson, await identityRec.eventualUpdateJson(Identity.fromJson,
(oldIdentity) async { (oldIdentity) async {
if (oldIdentity == null) {
throw IdentityException.readError;
}
final oldAccountRecords = final oldAccountRecords =
IMapOfSets.from(oldIdentity.accountRecords); IMapOfSets.from(oldIdentity.accountRecords);
// Only allow one account per identity for veilidchat
if (oldAccountRecords.get(accountKey).isNotEmpty) { if (oldAccountRecords.get(accountKey).length >= maxAccounts) {
throw StateError('Only one account per key in identity'); throw IdentityException.limitExceeded;
} }
final accountRecords = oldAccountRecords final accountRecords = oldAccountRecords
.add(accountKey, newAccountRecordInfo) .add(accountKey, newAccountRecordInfo)

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:async_tools/async_tools.dart';
import 'package:veilid/veilid.dart'; import 'package:veilid/veilid.dart';
Future<T> tableScope<T>( Future<T> tableScope<T>(
@ -67,25 +68,26 @@ class TableDBValue<T> extends TableDBBacked<T> {
_tableKeyName = tableKeyName, _tableKeyName = tableKeyName,
_streamController = StreamController<T>.broadcast(); _streamController = StreamController<T>.broadcast();
T? get value => _value; AsyncData<T>? get value => _value;
T get requireValue => _value!; T get requireValue => _value!.value;
Stream<T> get stream => _streamController.stream; Stream<T> get stream => _streamController.stream;
Future<T> get() async { Future<T> get() async {
final val = _value; final val = _value;
if (val != null) { if (val != null) {
return val; return val.value;
} }
final loadedValue = await load(); final loadedValue = await load();
return _value = loadedValue; _value = AsyncData(loadedValue);
return loadedValue;
} }
Future<void> set(T newVal) async { Future<void> set(T newVal) async {
_value = await store(newVal); _value = AsyncData(await store(newVal));
_streamController.add(newVal); _streamController.add(newVal);
} }
T? _value; AsyncData<T>? _value;
final String _tableName; final String _tableName;
final String _tableKeyName; final String _tableKeyName;
final T Function(Object? obj) _valueFromJson; final T Function(Object? obj) _valueFromJson;

View File

@ -9,6 +9,7 @@ export 'dht_support/dht_support.dart';
export 'src/config.dart'; export 'src/config.dart';
export 'src/identity.dart'; export 'src/identity.dart';
export 'src/json_tools.dart'; export 'src/json_tools.dart';
export 'src/memory_tools.dart';
export 'src/protobuf_tools.dart'; export 'src/protobuf_tools.dart';
export 'src/table_db.dart'; export 'src/table_db.dart';
export 'src/veilid_log.dart'; export 'src/veilid_log.dart';