diff --git a/lib/src/client.dart b/lib/src/client.dart index 2841e69..3e817a7 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -796,7 +796,7 @@ class Client { pickledOlmAccount, ); } - _userDeviceKeys = await database.getUserDeviceKeys(id); + _userDeviceKeys = await database.getUserDeviceKeys(this); _olmSessions = await database.getOlmSessions(id, _userID); _rooms = await database.getRoomList(this, onlyLeft: false); _sortRooms(); @@ -1644,6 +1644,7 @@ class Client { action: '/client/r0/keys/query', data: {'timeout': 10000, 'device_keys': outdatedLists}); + // first we parse and persist the device keys for (final rawDeviceKeyListEntry in response['device_keys'].entries) { final String userId = rawDeviceKeyListEntry.key; final oldKeys = @@ -1653,10 +1654,17 @@ class Client { final String deviceId = rawDeviceKeyEntry.key; // Set the new device key for this device - - if (!oldKeys.containsKey(deviceId)) { - final entry = DeviceKeys.fromJson(rawDeviceKeyEntry.value); - if (entry.isValid) { + final entry = DeviceKeys.fromJson(rawDeviceKeyEntry.value, this); + if (entry.isValid) { + // is this a new key or the same one as an old one? + // better store an update - the signatures might have changed! + if (!oldKeys.containsKey(deviceId) || oldKeys[deviceId].ed25519Key == entry.ed25519Key) { + if (oldKeys.containsKey(deviceId)) { + // be sure to save the verified status + entry.verified = oldKeys[deviceId].verified; + entry.blocked = oldKeys[deviceId].blocked; + entry.validSignatures = oldKeys[deviceId].validSignatures; + } _userDeviceKeys[userId].deviceKeys[deviceId] = entry; if (deviceId == deviceID && entry.ed25519Key == @@ -1664,22 +1672,26 @@ class Client { // Always trust the own device entry.verified = true; } + } else { + // This shouldn't ever happen. The same device ID has gotten + // a new public key. So we ignore the update. TODO: ask krille + // if we should instead use the new key with unknown verified / blocked status + _userDeviceKeys[userId].deviceKeys[deviceId] = oldKeys[deviceId]; } if (database != null) { dbActions.add(() => database.storeUserDeviceKey( id, userId, deviceId, - json.encode( - _userDeviceKeys[userId].deviceKeys[deviceId].toJson()), - _userDeviceKeys[userId].deviceKeys[deviceId].verified, - _userDeviceKeys[userId].deviceKeys[deviceId].blocked, + json.encode(entry.toJson()), + json.encode(entry.validSignatures), + entry.verified, + entry.blocked, )); } - } else { - _userDeviceKeys[userId].deviceKeys[deviceId] = oldKeys[deviceId]; } } + // delete old/unused entries if (database != null) { for (final oldDeviceKeyEntry in oldKeys.entries) { final deviceId = oldDeviceKeyEntry.key; @@ -1696,6 +1708,68 @@ class Client { .add(() => database.storeUserDeviceKeysInfo(id, userId, false)); } } + // next we parse and persist the cross signing keys + for (final keyType in ['master_keys', 'self_signing_keys', 'user_signing_keys']) { + if (!(response[keyType] is Map)) { + continue; + } + for (final rawDeviceKeyListEntry in response[keyType].entries) { + final String userId = rawDeviceKeyListEntry.key; + final oldKeys = Map.from(_userDeviceKeys[userId].crossSigningKeys); + _userDeviceKeys[userId].crossSigningKeys = {}; + // add the types we arne't handling atm back + for (final oldEntry in oldKeys.entries) { + if (!oldEntry.value.usage.contains(keyType.substring(0, keyType.length - '_keys'.length))) { + _userDeviceKeys[userId].crossSigningKeys[oldEntry.key] = oldEntry.value; + } + } + final entry = CrossSigningKey.fromJson(rawDeviceKeyListEntry.value, this); + if (entry.isValid) { + final publicKey = entry.publicKey; + if (!oldKeys.containsKey(publicKey) || oldKeys[publicKey].ed25519Key == entry.ed25519Key) { + if (oldKeys.containsKey(publicKey)) { + // be sure to save the verification status + entry.verified = oldKeys[publicKey].verified; + entry.blocked = oldKeys[publicKey].blocked; + entry.validSignatures = oldKeys[publicKey].validSignatures; + } + _userDeviceKeys[userId].crossSigningKeys[publicKey] = entry; + } else { + // This shouldn't ever happen. The same device ID has gotten + // a new public key. So we ignore the update. TODO: ask krille + // if we should instead use the new key with unknown verified / blocked status + _userDeviceKeys[userId].crossSigningKeys[publicKey] = oldKeys[publicKey]; + } + if (database != null) { + dbActions.add(() => database.storeUserCrossSigningKey( + id, + userId, + publicKey, + json.encode(entry.toJson()), + json.encode(entry.validSignatures), + entry.verified, + entry.blocked, + )); + } + } + // delete old/unused entries + if (database != null) { + for (final oldCrossSigningKeyEntry in oldKeys.entries) { + final publicKey = oldCrossSigningKeyEntry.key; + if (!_userDeviceKeys[userId].crossSigningKeys.containsKey(publicKey)) { + // we need to remove an old key + dbActions.add( + () => database.removeUserCrossSigningKey(id, userId, publicKey)); + } + } + } + _userDeviceKeys[userId].outdated = false; + if (database != null) { + dbActions + .add(() => database.storeUserDeviceKeysInfo(id, userId, false)); + } + } + } } await database?.transaction(() async { for (final f in dbActions) { diff --git a/lib/src/database/database.dart b/lib/src/database/database.dart index e4af62a..f402068 100644 --- a/lib/src/database/database.dart +++ b/lib/src/database/database.dart @@ -13,7 +13,7 @@ class Database extends _$Database { Database(QueryExecutor e) : super(e); @override - int get schemaVersion => 3; + int get schemaVersion => 4; int get maxFileSize => 1 * 1024 * 1024; @@ -41,6 +41,15 @@ class Database extends _$Database { if (from == 2) { await m.deleteTable('outbound_group_sessions'); await m.createTable(outboundGroupSessions); + from++; + } + if (from == 3) { + await m.createTable(userCrossSigningKeys); + await m.createIndex(userCrossSigningKeysIndex); + await m.addColumn(userDeviceKeysKey, userDeviceKeysKey.validSignatures); + // mark all keys as outdated so that the cross signing keys will be fetched + await m.issueCustomQuery('UPDATE user_device_keys SET outdated = true'); + from++; } }, ); @@ -51,15 +60,19 @@ class Database extends _$Database { return res.first; } - Future> getUserDeviceKeys(int clientId) async { - final deviceKeys = await getAllUserDeviceKeys(clientId).get(); + Future> getUserDeviceKeys(sdk.Client client) async { + final deviceKeys = await getAllUserDeviceKeys(client.id).get(); if (deviceKeys.isEmpty) { return {}; } - final deviceKeysKeys = await getAllUserDeviceKeysKeys(clientId).get(); + final deviceKeysKeys = await getAllUserDeviceKeysKeys(client.id).get(); + final crossSigningKeys = await getAllUserCrossSigningKeys(client.id).get(); final res = {}; for (final entry in deviceKeys) { - res[entry.userId] = sdk.DeviceKeysList.fromDb(entry, deviceKeysKeys.where((k) => k.userId == entry.userId).toList()); + res[entry.userId] = sdk.DeviceKeysList.fromDb(entry, + deviceKeysKeys.where((k) => k.userId == entry.userId).toList(), + crossSigningKeys.where((k) => k.userId == entry.userId).toList(), + client); } return res; } diff --git a/lib/src/database/database.g.dart b/lib/src/database/database.g.dart index 61bc1c7..0feeca1 100644 --- a/lib/src/database/database.g.dart +++ b/lib/src/database/database.g.dart @@ -699,6 +699,7 @@ class DbUserDeviceKeysKey extends DataClass final String userId; final String deviceId; final String content; + final String validSignatures; final bool verified; final bool blocked; DbUserDeviceKeysKey( @@ -706,6 +707,7 @@ class DbUserDeviceKeysKey extends DataClass @required this.userId, @required this.deviceId, @required this.content, + this.validSignatures, this.verified, this.blocked}); factory DbUserDeviceKeysKey.fromData( @@ -724,6 +726,8 @@ class DbUserDeviceKeysKey extends DataClass .mapFromDatabaseResponse(data['${effectivePrefix}device_id']), content: stringType.mapFromDatabaseResponse(data['${effectivePrefix}content']), + validSignatures: stringType + .mapFromDatabaseResponse(data['${effectivePrefix}valid_signatures']), verified: boolType.mapFromDatabaseResponse(data['${effectivePrefix}verified']), blocked: @@ -745,6 +749,9 @@ class DbUserDeviceKeysKey extends DataClass if (!nullToAbsent || content != null) { map['content'] = Variable(content); } + if (!nullToAbsent || validSignatures != null) { + map['valid_signatures'] = Variable(validSignatures); + } if (!nullToAbsent || verified != null) { map['verified'] = Variable(verified); } @@ -762,6 +769,7 @@ class DbUserDeviceKeysKey extends DataClass userId: serializer.fromJson(json['user_id']), deviceId: serializer.fromJson(json['device_id']), content: serializer.fromJson(json['content']), + validSignatures: serializer.fromJson(json['valid_signatures']), verified: serializer.fromJson(json['verified']), blocked: serializer.fromJson(json['blocked']), ); @@ -774,6 +782,7 @@ class DbUserDeviceKeysKey extends DataClass 'user_id': serializer.toJson(userId), 'device_id': serializer.toJson(deviceId), 'content': serializer.toJson(content), + 'valid_signatures': serializer.toJson(validSignatures), 'verified': serializer.toJson(verified), 'blocked': serializer.toJson(blocked), }; @@ -784,6 +793,7 @@ class DbUserDeviceKeysKey extends DataClass String userId, String deviceId, String content, + String validSignatures, bool verified, bool blocked}) => DbUserDeviceKeysKey( @@ -791,6 +801,7 @@ class DbUserDeviceKeysKey extends DataClass userId: userId ?? this.userId, deviceId: deviceId ?? this.deviceId, content: content ?? this.content, + validSignatures: validSignatures ?? this.validSignatures, verified: verified ?? this.verified, blocked: blocked ?? this.blocked, ); @@ -801,6 +812,7 @@ class DbUserDeviceKeysKey extends DataClass ..write('userId: $userId, ') ..write('deviceId: $deviceId, ') ..write('content: $content, ') + ..write('validSignatures: $validSignatures, ') ..write('verified: $verified, ') ..write('blocked: $blocked') ..write(')')) @@ -814,8 +826,10 @@ class DbUserDeviceKeysKey extends DataClass userId.hashCode, $mrjc( deviceId.hashCode, - $mrjc(content.hashCode, - $mrjc(verified.hashCode, blocked.hashCode)))))); + $mrjc( + content.hashCode, + $mrjc(validSignatures.hashCode, + $mrjc(verified.hashCode, blocked.hashCode))))))); @override bool operator ==(dynamic other) => identical(this, other) || @@ -824,6 +838,7 @@ class DbUserDeviceKeysKey extends DataClass other.userId == this.userId && other.deviceId == this.deviceId && other.content == this.content && + other.validSignatures == this.validSignatures && other.verified == this.verified && other.blocked == this.blocked); } @@ -833,6 +848,7 @@ class UserDeviceKeysKeyCompanion extends UpdateCompanion { final Value userId; final Value deviceId; final Value content; + final Value validSignatures; final Value verified; final Value blocked; const UserDeviceKeysKeyCompanion({ @@ -840,6 +856,7 @@ class UserDeviceKeysKeyCompanion extends UpdateCompanion { this.userId = const Value.absent(), this.deviceId = const Value.absent(), this.content = const Value.absent(), + this.validSignatures = const Value.absent(), this.verified = const Value.absent(), this.blocked = const Value.absent(), }); @@ -848,6 +865,7 @@ class UserDeviceKeysKeyCompanion extends UpdateCompanion { @required String userId, @required String deviceId, @required String content, + this.validSignatures = const Value.absent(), this.verified = const Value.absent(), this.blocked = const Value.absent(), }) : clientId = Value(clientId), @@ -859,6 +877,7 @@ class UserDeviceKeysKeyCompanion extends UpdateCompanion { Expression userId, Expression deviceId, Expression content, + Expression validSignatures, Expression verified, Expression blocked, }) { @@ -867,6 +886,7 @@ class UserDeviceKeysKeyCompanion extends UpdateCompanion { if (userId != null) 'user_id': userId, if (deviceId != null) 'device_id': deviceId, if (content != null) 'content': content, + if (validSignatures != null) 'valid_signatures': validSignatures, if (verified != null) 'verified': verified, if (blocked != null) 'blocked': blocked, }); @@ -877,6 +897,7 @@ class UserDeviceKeysKeyCompanion extends UpdateCompanion { Value userId, Value deviceId, Value content, + Value validSignatures, Value verified, Value blocked}) { return UserDeviceKeysKeyCompanion( @@ -884,6 +905,7 @@ class UserDeviceKeysKeyCompanion extends UpdateCompanion { userId: userId ?? this.userId, deviceId: deviceId ?? this.deviceId, content: content ?? this.content, + validSignatures: validSignatures ?? this.validSignatures, verified: verified ?? this.verified, blocked: blocked ?? this.blocked, ); @@ -904,6 +926,9 @@ class UserDeviceKeysKeyCompanion extends UpdateCompanion { if (content.present) { map['content'] = Variable(content.value); } + if (validSignatures.present) { + map['valid_signatures'] = Variable(validSignatures.value); + } if (verified.present) { map['verified'] = Variable(verified.value); } @@ -951,6 +976,16 @@ class UserDeviceKeysKey extends Table $customConstraints: 'NOT NULL'); } + final VerificationMeta _validSignaturesMeta = + const VerificationMeta('validSignatures'); + GeneratedTextColumn _validSignatures; + GeneratedTextColumn get validSignatures => + _validSignatures ??= _constructValidSignatures(); + GeneratedTextColumn _constructValidSignatures() { + return GeneratedTextColumn('valid_signatures', $tableName, true, + $customConstraints: ''); + } + final VerificationMeta _verifiedMeta = const VerificationMeta('verified'); GeneratedBoolColumn _verified; GeneratedBoolColumn get verified => _verified ??= _constructVerified(); @@ -971,7 +1006,7 @@ class UserDeviceKeysKey extends Table @override List get $columns => - [clientId, userId, deviceId, content, verified, blocked]; + [clientId, userId, deviceId, content, validSignatures, verified, blocked]; @override UserDeviceKeysKey get asDslTable => this; @override @@ -1008,6 +1043,12 @@ class UserDeviceKeysKey extends Table } else if (isInserting) { context.missing(_contentMeta); } + if (data.containsKey('valid_signatures')) { + context.handle( + _validSignaturesMeta, + validSignatures.isAcceptableOrUnknown( + data['valid_signatures'], _validSignaturesMeta)); + } if (data.containsKey('verified')) { context.handle(_verifiedMeta, verified.isAcceptableOrUnknown(data['verified'], _verifiedMeta)); @@ -1039,6 +1080,401 @@ class UserDeviceKeysKey extends Table bool get dontWriteConstraints => true; } +class DbUserCrossSigningKey extends DataClass + implements Insertable { + final int clientId; + final String userId; + final String publicKey; + final String content; + final String validSignatures; + final bool verified; + final bool blocked; + DbUserCrossSigningKey( + {@required this.clientId, + @required this.userId, + @required this.publicKey, + @required this.content, + this.validSignatures, + this.verified, + this.blocked}); + factory DbUserCrossSigningKey.fromData( + Map data, GeneratedDatabase db, + {String prefix}) { + final effectivePrefix = prefix ?? ''; + final intType = db.typeSystem.forDartType(); + final stringType = db.typeSystem.forDartType(); + final boolType = db.typeSystem.forDartType(); + return DbUserCrossSigningKey( + clientId: + intType.mapFromDatabaseResponse(data['${effectivePrefix}client_id']), + userId: + stringType.mapFromDatabaseResponse(data['${effectivePrefix}user_id']), + publicKey: stringType + .mapFromDatabaseResponse(data['${effectivePrefix}public_key']), + content: + stringType.mapFromDatabaseResponse(data['${effectivePrefix}content']), + validSignatures: stringType + .mapFromDatabaseResponse(data['${effectivePrefix}valid_signatures']), + verified: + boolType.mapFromDatabaseResponse(data['${effectivePrefix}verified']), + blocked: + boolType.mapFromDatabaseResponse(data['${effectivePrefix}blocked']), + ); + } + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (!nullToAbsent || clientId != null) { + map['client_id'] = Variable(clientId); + } + if (!nullToAbsent || userId != null) { + map['user_id'] = Variable(userId); + } + if (!nullToAbsent || publicKey != null) { + map['public_key'] = Variable(publicKey); + } + if (!nullToAbsent || content != null) { + map['content'] = Variable(content); + } + if (!nullToAbsent || validSignatures != null) { + map['valid_signatures'] = Variable(validSignatures); + } + if (!nullToAbsent || verified != null) { + map['verified'] = Variable(verified); + } + if (!nullToAbsent || blocked != null) { + map['blocked'] = Variable(blocked); + } + return map; + } + + factory DbUserCrossSigningKey.fromJson(Map json, + {ValueSerializer serializer}) { + serializer ??= moorRuntimeOptions.defaultSerializer; + return DbUserCrossSigningKey( + clientId: serializer.fromJson(json['client_id']), + userId: serializer.fromJson(json['user_id']), + publicKey: serializer.fromJson(json['public_key']), + content: serializer.fromJson(json['content']), + validSignatures: serializer.fromJson(json['valid_signatures']), + verified: serializer.fromJson(json['verified']), + blocked: serializer.fromJson(json['blocked']), + ); + } + @override + Map toJson({ValueSerializer serializer}) { + serializer ??= moorRuntimeOptions.defaultSerializer; + return { + 'client_id': serializer.toJson(clientId), + 'user_id': serializer.toJson(userId), + 'public_key': serializer.toJson(publicKey), + 'content': serializer.toJson(content), + 'valid_signatures': serializer.toJson(validSignatures), + 'verified': serializer.toJson(verified), + 'blocked': serializer.toJson(blocked), + }; + } + + DbUserCrossSigningKey copyWith( + {int clientId, + String userId, + String publicKey, + String content, + String validSignatures, + bool verified, + bool blocked}) => + DbUserCrossSigningKey( + clientId: clientId ?? this.clientId, + userId: userId ?? this.userId, + publicKey: publicKey ?? this.publicKey, + content: content ?? this.content, + validSignatures: validSignatures ?? this.validSignatures, + verified: verified ?? this.verified, + blocked: blocked ?? this.blocked, + ); + @override + String toString() { + return (StringBuffer('DbUserCrossSigningKey(') + ..write('clientId: $clientId, ') + ..write('userId: $userId, ') + ..write('publicKey: $publicKey, ') + ..write('content: $content, ') + ..write('validSignatures: $validSignatures, ') + ..write('verified: $verified, ') + ..write('blocked: $blocked') + ..write(')')) + .toString(); + } + + @override + int get hashCode => $mrjf($mrjc( + clientId.hashCode, + $mrjc( + userId.hashCode, + $mrjc( + publicKey.hashCode, + $mrjc( + content.hashCode, + $mrjc(validSignatures.hashCode, + $mrjc(verified.hashCode, blocked.hashCode))))))); + @override + bool operator ==(dynamic other) => + identical(this, other) || + (other is DbUserCrossSigningKey && + other.clientId == this.clientId && + other.userId == this.userId && + other.publicKey == this.publicKey && + other.content == this.content && + other.validSignatures == this.validSignatures && + other.verified == this.verified && + other.blocked == this.blocked); +} + +class UserCrossSigningKeysCompanion + extends UpdateCompanion { + final Value clientId; + final Value userId; + final Value publicKey; + final Value content; + final Value validSignatures; + final Value verified; + final Value blocked; + const UserCrossSigningKeysCompanion({ + this.clientId = const Value.absent(), + this.userId = const Value.absent(), + this.publicKey = const Value.absent(), + this.content = const Value.absent(), + this.validSignatures = const Value.absent(), + this.verified = const Value.absent(), + this.blocked = const Value.absent(), + }); + UserCrossSigningKeysCompanion.insert({ + @required int clientId, + @required String userId, + @required String publicKey, + @required String content, + this.validSignatures = const Value.absent(), + this.verified = const Value.absent(), + this.blocked = const Value.absent(), + }) : clientId = Value(clientId), + userId = Value(userId), + publicKey = Value(publicKey), + content = Value(content); + static Insertable custom({ + Expression clientId, + Expression userId, + Expression publicKey, + Expression content, + Expression validSignatures, + Expression verified, + Expression blocked, + }) { + return RawValuesInsertable({ + if (clientId != null) 'client_id': clientId, + if (userId != null) 'user_id': userId, + if (publicKey != null) 'public_key': publicKey, + if (content != null) 'content': content, + if (validSignatures != null) 'valid_signatures': validSignatures, + if (verified != null) 'verified': verified, + if (blocked != null) 'blocked': blocked, + }); + } + + UserCrossSigningKeysCompanion copyWith( + {Value clientId, + Value userId, + Value publicKey, + Value content, + Value validSignatures, + Value verified, + Value blocked}) { + return UserCrossSigningKeysCompanion( + clientId: clientId ?? this.clientId, + userId: userId ?? this.userId, + publicKey: publicKey ?? this.publicKey, + content: content ?? this.content, + validSignatures: validSignatures ?? this.validSignatures, + verified: verified ?? this.verified, + blocked: blocked ?? this.blocked, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (clientId.present) { + map['client_id'] = Variable(clientId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (publicKey.present) { + map['public_key'] = Variable(publicKey.value); + } + if (content.present) { + map['content'] = Variable(content.value); + } + if (validSignatures.present) { + map['valid_signatures'] = Variable(validSignatures.value); + } + if (verified.present) { + map['verified'] = Variable(verified.value); + } + if (blocked.present) { + map['blocked'] = Variable(blocked.value); + } + return map; + } +} + +class UserCrossSigningKeys extends Table + with TableInfo { + final GeneratedDatabase _db; + final String _alias; + UserCrossSigningKeys(this._db, [this._alias]); + final VerificationMeta _clientIdMeta = const VerificationMeta('clientId'); + GeneratedIntColumn _clientId; + GeneratedIntColumn get clientId => _clientId ??= _constructClientId(); + GeneratedIntColumn _constructClientId() { + return GeneratedIntColumn('client_id', $tableName, false, + $customConstraints: 'NOT NULL REFERENCES clients(client_id)'); + } + + final VerificationMeta _userIdMeta = const VerificationMeta('userId'); + GeneratedTextColumn _userId; + GeneratedTextColumn get userId => _userId ??= _constructUserId(); + GeneratedTextColumn _constructUserId() { + return GeneratedTextColumn('user_id', $tableName, false, + $customConstraints: 'NOT NULL'); + } + + final VerificationMeta _publicKeyMeta = const VerificationMeta('publicKey'); + GeneratedTextColumn _publicKey; + GeneratedTextColumn get publicKey => _publicKey ??= _constructPublicKey(); + GeneratedTextColumn _constructPublicKey() { + return GeneratedTextColumn('public_key', $tableName, false, + $customConstraints: 'NOT NULL'); + } + + final VerificationMeta _contentMeta = const VerificationMeta('content'); + GeneratedTextColumn _content; + GeneratedTextColumn get content => _content ??= _constructContent(); + GeneratedTextColumn _constructContent() { + return GeneratedTextColumn('content', $tableName, false, + $customConstraints: 'NOT NULL'); + } + + final VerificationMeta _validSignaturesMeta = + const VerificationMeta('validSignatures'); + GeneratedTextColumn _validSignatures; + GeneratedTextColumn get validSignatures => + _validSignatures ??= _constructValidSignatures(); + GeneratedTextColumn _constructValidSignatures() { + return GeneratedTextColumn('valid_signatures', $tableName, true, + $customConstraints: ''); + } + + final VerificationMeta _verifiedMeta = const VerificationMeta('verified'); + GeneratedBoolColumn _verified; + GeneratedBoolColumn get verified => _verified ??= _constructVerified(); + GeneratedBoolColumn _constructVerified() { + return GeneratedBoolColumn('verified', $tableName, true, + $customConstraints: 'DEFAULT false', + defaultValue: const CustomExpression('false')); + } + + final VerificationMeta _blockedMeta = const VerificationMeta('blocked'); + GeneratedBoolColumn _blocked; + GeneratedBoolColumn get blocked => _blocked ??= _constructBlocked(); + GeneratedBoolColumn _constructBlocked() { + return GeneratedBoolColumn('blocked', $tableName, true, + $customConstraints: 'DEFAULT false', + defaultValue: const CustomExpression('false')); + } + + @override + List get $columns => [ + clientId, + userId, + publicKey, + content, + validSignatures, + verified, + blocked + ]; + @override + UserCrossSigningKeys get asDslTable => this; + @override + String get $tableName => _alias ?? 'user_cross_signing_keys'; + @override + final String actualTableName = 'user_cross_signing_keys'; + @override + VerificationContext validateIntegrity( + Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('client_id')) { + context.handle(_clientIdMeta, + clientId.isAcceptableOrUnknown(data['client_id'], _clientIdMeta)); + } else if (isInserting) { + context.missing(_clientIdMeta); + } + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id'], _userIdMeta)); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('public_key')) { + context.handle(_publicKeyMeta, + publicKey.isAcceptableOrUnknown(data['public_key'], _publicKeyMeta)); + } else if (isInserting) { + context.missing(_publicKeyMeta); + } + if (data.containsKey('content')) { + context.handle(_contentMeta, + content.isAcceptableOrUnknown(data['content'], _contentMeta)); + } else if (isInserting) { + context.missing(_contentMeta); + } + if (data.containsKey('valid_signatures')) { + context.handle( + _validSignaturesMeta, + validSignatures.isAcceptableOrUnknown( + data['valid_signatures'], _validSignaturesMeta)); + } + if (data.containsKey('verified')) { + context.handle(_verifiedMeta, + verified.isAcceptableOrUnknown(data['verified'], _verifiedMeta)); + } + if (data.containsKey('blocked')) { + context.handle(_blockedMeta, + blocked.isAcceptableOrUnknown(data['blocked'], _blockedMeta)); + } + return context; + } + + @override + Set get $primaryKey => {}; + @override + DbUserCrossSigningKey map(Map data, {String tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; + return DbUserCrossSigningKey.fromData(data, _db, prefix: effectivePrefix); + } + + @override + UserCrossSigningKeys createAlias(String alias) { + return UserCrossSigningKeys(_db, alias); + } + + @override + List get customConstraints => + const ['UNIQUE(client_id, user_id, public_key)']; + @override + bool get dontWriteConstraints => true; +} + class DbOlmSessions extends DataClass implements Insertable { final int clientId; final String identityKey; @@ -4680,6 +5116,13 @@ abstract class _$Database extends GeneratedDatabase { Index get userDeviceKeysKeyIndex => _userDeviceKeysKeyIndex ??= Index( 'user_device_keys_key_index', 'CREATE INDEX user_device_keys_key_index ON user_device_keys_key(client_id);'); + UserCrossSigningKeys _userCrossSigningKeys; + UserCrossSigningKeys get userCrossSigningKeys => + _userCrossSigningKeys ??= UserCrossSigningKeys(this); + Index _userCrossSigningKeysIndex; + Index get userCrossSigningKeysIndex => _userCrossSigningKeysIndex ??= Index( + 'user_cross_signing_keys_index', + 'CREATE INDEX user_cross_signing_keys_index ON user_cross_signing_keys(client_id);'); OlmSessions _olmSessions; OlmSessions get olmSessions => _olmSessions ??= OlmSessions(this); Index _olmSessionsIndex; @@ -4823,6 +5266,7 @@ abstract class _$Database extends GeneratedDatabase { userId: row.readString('user_id'), deviceId: row.readString('device_id'), content: row.readString('content'), + validSignatures: row.readString('valid_signatures'), verified: row.readBool('verified'), blocked: row.readBool('blocked'), ); @@ -4835,6 +5279,25 @@ abstract class _$Database extends GeneratedDatabase { readsFrom: {userDeviceKeysKey}).map(_rowToDbUserDeviceKeysKey); } + DbUserCrossSigningKey _rowToDbUserCrossSigningKey(QueryRow row) { + return DbUserCrossSigningKey( + clientId: row.readInt('client_id'), + userId: row.readString('user_id'), + publicKey: row.readString('public_key'), + content: row.readString('content'), + validSignatures: row.readString('valid_signatures'), + verified: row.readBool('verified'), + blocked: row.readBool('blocked'), + ); + } + + Selectable getAllUserCrossSigningKeys(int client_id) { + return customSelect( + 'SELECT * FROM user_cross_signing_keys WHERE client_id = :client_id', + variables: [Variable.withInt(client_id)], + readsFrom: {userCrossSigningKeys}).map(_rowToDbUserCrossSigningKey); + } + DbOlmSessions _rowToDbOlmSessions(QueryRow row) { return DbOlmSessions( clientId: row.readInt('client_id'), @@ -5036,15 +5499,22 @@ abstract class _$Database extends GeneratedDatabase { ); } - Future storeUserDeviceKey(int client_id, String user_id, - String device_id, String content, bool verified, bool blocked) { + Future storeUserDeviceKey( + int client_id, + String user_id, + String device_id, + String content, + String valid_signatures, + bool verified, + bool blocked) { return customInsert( - 'INSERT OR REPLACE INTO user_device_keys_key (client_id, user_id, device_id, content, verified, blocked) VALUES (:client_id, :user_id, :device_id, :content, :verified, :blocked)', + 'INSERT OR REPLACE INTO user_device_keys_key (client_id, user_id, device_id, content, valid_signatures, verified, blocked) VALUES (:client_id, :user_id, :device_id, :content, :valid_signatures, :verified, :blocked)', variables: [ Variable.withInt(client_id), Variable.withString(user_id), Variable.withString(device_id), Variable.withString(content), + Variable.withString(valid_signatures), Variable.withBool(verified), Variable.withBool(blocked) ], @@ -5066,6 +5536,73 @@ abstract class _$Database extends GeneratedDatabase { ); } + Future setVerifiedUserCrossSigningKey( + bool verified, int client_id, String user_id, String public_key) { + return customUpdate( + 'UPDATE user_cross_signing_keys SET verified = :verified WHERE client_id = :client_id AND user_id = :user_id AND public_key = :public_key', + variables: [ + Variable.withBool(verified), + Variable.withInt(client_id), + Variable.withString(user_id), + Variable.withString(public_key) + ], + updates: {userCrossSigningKeys}, + updateKind: UpdateKind.update, + ); + } + + Future setBlockedUserCrossSigningKey( + bool blocked, int client_id, String user_id, String public_key) { + return customUpdate( + 'UPDATE user_cross_signing_keys SET blocked = :blocked WHERE client_id = :client_id AND user_id = :user_id AND public_key = :public_key', + variables: [ + Variable.withBool(blocked), + Variable.withInt(client_id), + Variable.withString(user_id), + Variable.withString(public_key) + ], + updates: {userCrossSigningKeys}, + updateKind: UpdateKind.update, + ); + } + + Future storeUserCrossSigningKey( + int client_id, + String user_id, + String public_key, + String content, + String valid_signatures, + bool verified, + bool blocked) { + return customInsert( + 'INSERT OR REPLACE INTO user_cross_signing_keys (client_id, user_id, public_key, content, valid_signatures, verified, blocked) VALUES (:client_id, :user_id, :public_key, :content, :valid_signatures, :verified, :blocked)', + variables: [ + Variable.withInt(client_id), + Variable.withString(user_id), + Variable.withString(public_key), + Variable.withString(content), + Variable.withString(valid_signatures), + Variable.withBool(verified), + Variable.withBool(blocked) + ], + updates: {userCrossSigningKeys}, + ); + } + + Future removeUserCrossSigningKey( + int client_id, String user_id, String public_key) { + return customUpdate( + 'DELETE FROM user_cross_signing_keys WHERE client_id = :client_id AND user_id = :user_id AND public_key = :public_key', + variables: [ + Variable.withInt(client_id), + Variable.withString(user_id), + Variable.withString(public_key) + ], + updates: {userCrossSigningKeys}, + updateKind: UpdateKind.delete, + ); + } + Future insertClient( String name, String homeserver_url, @@ -5472,6 +6009,8 @@ abstract class _$Database extends GeneratedDatabase { userDeviceKeysIndex, userDeviceKeysKey, userDeviceKeysKeyIndex, + userCrossSigningKeys, + userCrossSigningKeysIndex, olmSessions, olmSessionsIndex, outboundGroupSessions, diff --git a/lib/src/database/database.moor b/lib/src/database/database.moor index ebe66ea..b26ea41 100644 --- a/lib/src/database/database.moor +++ b/lib/src/database/database.moor @@ -26,12 +26,25 @@ CREATE TABLE user_device_keys_key ( user_id TEXT NOT NULL, device_id TEXT NOT NULL, content TEXT NOT NULL, + valid_signatures TEXT, verified BOOLEAN DEFAULT false, blocked BOOLEAN DEFAULT false, UNIQUE(client_id, user_id, device_id) ) as DbUserDeviceKeysKey; CREATE INDEX user_device_keys_key_index ON user_device_keys_key(client_id); +CREATE TABLE user_cross_signing_keys ( + client_id INTEGER NOT NULL REFERENCES clients(client_id), + user_id TEXT NOT NULL, + public_key TEXT NOT NULL, + content TEXT NOT NULL, + valid_signatures TEXT, + verified BOOLEAN DEFAULT false, + blocked BOOLEAN DEFAULT false, + UNIQUE(client_id, user_id, public_key) +) as DbUserCrossSigningKey; +CREATE INDEX user_cross_signing_keys_index ON user_cross_signing_keys(client_id); + CREATE TABLE olm_sessions ( client_id INTEGER NOT NULL REFERENCES clients(client_id), identity_key TEXT NOT NULL, @@ -154,6 +167,7 @@ updateClientKeys: UPDATE clients SET olm_account = :olm_account WHERE client_id storePrevBatch: UPDATE clients SET prev_batch = :prev_batch WHERE client_id = :client_id; getAllUserDeviceKeys: SELECT * FROM user_device_keys WHERE client_id = :client_id; getAllUserDeviceKeysKeys: SELECT * FROM user_device_keys_key WHERE client_id = :client_id; +getAllUserCrossSigningKeys: SELECT * FROM user_cross_signing_keys WHERE client_id = :client_id; getAllOlmSessions: SELECT * FROM olm_sessions WHERE client_id = :client_id; storeOlmSession: INSERT OR REPLACE INTO olm_sessions (client_id, identity_key, session_id, pickle) VALUES (:client_id, :identitiy_key, :session_id, :pickle); getAllOutboundGroupSessions: SELECT * FROM outbound_group_sessions WHERE client_id = :client_id; @@ -168,8 +182,12 @@ updateInboundGroupSessionIndexes: UPDATE inbound_group_sessions SET indexes = :i storeUserDeviceKeysInfo: INSERT OR REPLACE INTO user_device_keys (client_id, user_id, outdated) VALUES (:client_id, :user_id, :outdated); setVerifiedUserDeviceKey: UPDATE user_device_keys_key SET verified = :verified WHERE client_id = :client_id AND user_id = :user_id AND device_id = :device_id; setBlockedUserDeviceKey: UPDATE user_device_keys_key SET blocked = :blocked WHERE client_id = :client_id AND user_id = :user_id AND device_id = :device_id; -storeUserDeviceKey: INSERT OR REPLACE INTO user_device_keys_key (client_id, user_id, device_id, content, verified, blocked) VALUES (:client_id, :user_id, :device_id, :content, :verified, :blocked); +storeUserDeviceKey: INSERT OR REPLACE INTO user_device_keys_key (client_id, user_id, device_id, content, valid_signatures, verified, blocked) VALUES (:client_id, :user_id, :device_id, :content, :valid_signatures, :verified, :blocked); removeUserDeviceKey: DELETE FROM user_device_keys_key WHERE client_id = :client_id AND user_id = :user_id AND device_id = :device_id; +setVerifiedUserCrossSigningKey: UPDATE user_cross_signing_keys SET verified = :verified WHERE client_id = :client_id AND user_id = :user_id AND public_key = :public_key; +setBlockedUserCrossSigningKey: UPDATE user_cross_signing_keys SET blocked = :blocked WHERE client_id = :client_id AND user_id = :user_id AND public_key = :public_key; +storeUserCrossSigningKey: INSERT OR REPLACE INTO user_cross_signing_keys (client_id, user_id, public_key, content, valid_signatures, verified, blocked) VALUES (:client_id, :user_id, :public_key, :content, :valid_signatures, :verified, :blocked); +removeUserCrossSigningKey: DELETE FROM user_cross_signing_keys WHERE client_id = :client_id AND user_id = :user_id AND public_key = :public_key; insertClient: INSERT INTO clients (name, homeserver_url, token, user_id, device_id, device_name, prev_batch, olm_account) VALUES (:name, :homeserver_url, :token, :user_id, :device_id, :device_name, :prev_batch, :olm_account); ensureRoomExists: INSERT OR IGNORE INTO rooms (client_id, room_id, membership) VALUES (:client_id, :room_id, :membership); setRoomPrevBatch: UPDATE rooms SET prev_batch = :prev_batch WHERE client_id = :client_id AND room_id = :room_id; diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart index 8cacbdc..162eb59 100644 --- a/lib/src/utils/device_keys_list.dart +++ b/lib/src/utils/device_keys_list.dart @@ -1,36 +1,50 @@ import 'dart:convert'; +import 'package:canonical_json/canonical_json.dart'; +import 'package:olm/olm.dart' as olm; import '../client.dart'; -import '../database/database.dart' show DbUserDeviceKey, DbUserDeviceKeysKey; +import '../database/database.dart' show DbUserDeviceKey, DbUserDeviceKeysKey, DbUserCrossSigningKey; import '../event.dart'; import 'key_verification.dart'; class DeviceKeysList { + Client client; String userId; bool outdated = true; Map deviceKeys = {}; + Map crossSigningKeys = {}; - DeviceKeysList.fromDb(DbUserDeviceKey dbEntry, List childEntries) { + DeviceKeysList.fromDb(DbUserDeviceKey dbEntry, List childEntries, List crossSigningEntries, Client cl) { + client = cl; userId = dbEntry.userId; outdated = dbEntry.outdated; deviceKeys = {}; for (final childEntry in childEntries) { - final entry = DeviceKeys.fromDb(childEntry); + final entry = DeviceKeys.fromDb(childEntry, client); if (entry.isValid) { deviceKeys[childEntry.deviceId] = entry; } else { outdated = true; } } + for (final crossSigningEntry in crossSigningEntries) { + final entry = CrossSigningKey.fromDb(crossSigningEntry, client); + if (entry.isValid) { + crossSigningKeys[crossSigningEntry.publicKey] = entry; + } else { + outdated = true; + } + } } - DeviceKeysList.fromJson(Map json) { + DeviceKeysList.fromJson(Map json, Client cl) { + client = cl; userId = json['user_id']; outdated = json['outdated']; deviceKeys = {}; for (final rawDeviceKeyEntry in json['device_keys'].entries) { deviceKeys[rawDeviceKeyEntry.key] = - DeviceKeys.fromJson(rawDeviceKeyEntry.value); + DeviceKeys.fromJson(rawDeviceKeyEntry.value, client); } } @@ -54,27 +68,199 @@ class DeviceKeysList { DeviceKeysList(this.userId); } -class DeviceKeys { +abstract class _SignedKey { + Client client; String userId; - String deviceId; - List algorithms; + String identifier; + Map content; Map keys; Map signatures; - Map unsigned; + Map validSignatures; bool verified; bool blocked; + String get ed25519Key => keys['ed25519:$identifier']; + + bool get crossVerified { + try { + return hasValidSignatureChain(); + } catch (err, stacktrace) { + print('[Cross Signing] Error during trying to determine signature chain: ' + err.toString()); + print(stacktrace); + return false; + } + } + + String _getSigningContent() { + final data = Map.from(content); + data.remove('verified'); + data.remove('blocked'); + data.remove('unsigned'); + data.remove('signatures'); + return String.fromCharCodes(canonicalJson.encode(data)); + } + + bool _verifySignature(String pubKey, String signature) { + final olmutil = olm.Utility(); + var valid = false; + try { + olmutil.ed25519_verify(pubKey, _getSigningContent(), signature); + valid = true; + } finally { + olmutil.free(); + } + return valid; + } + + bool hasValidSignatureChain({Set visited}) { + if (visited == null) { + visited = Set(); + } + final setKey = '${userId};${identifier}'; + if (visited.contains(setKey)) { + return false; // prevent recursion + } + visited.add(setKey); + for (final signatureEntries in signatures.entries) { + final otherUserId = signatureEntries.key; + if (!(signatureEntries.value is Map) || !client.userDeviceKeys.containsKey(otherUserId)) { + continue; + } + for (final signatureEntry in signatureEntries.value.entries) { + final fullKeyId = signatureEntry.key; + final signature = signatureEntry.value; + if (!(fullKeyId is String) || !(signature is String)) { + continue; + } + final keyId = fullKeyId.substring('ed25519:'.length); + _SignedKey key; + if (client.userDeviceKeys[otherUserId].deviceKeys.containsKey(keyId)) { + key = client.userDeviceKeys[otherUserId].deviceKeys[keyId]; + } else if (client.userDeviceKeys[otherUserId].crossSigningKeys.containsKey(keyId)) { + key = client.userDeviceKeys[otherUserId].crossSigningKeys[keyId]; + } else { + continue; + } + if (key.blocked) { + continue; // we can't be bothered about this keys signatures + } + var haveValidSignature = false; + var gotSignatureFromCache = false; + if (validSignatures != null && validSignatures.containsKey(otherUserId) && validSignatures[otherUserId].containsKey(fullKeyId)) { + if (validSignatures[otherUserId][fullKeyId] == true) { + haveValidSignature = true; + gotSignatureFromCache = true; + } else if (validSignatures[otherUserId][fullKeyId] == false) { + gotSignatureFromCache = true; + } + } + if (!gotSignatureFromCache) { + // validate the signature manually + haveValidSignature = _verifySignature(key.ed25519Key, signature); + } + if (!haveValidSignature) { + // no valid signature, this key is useless + continue; + } + + if (key.verified) { + return true; // we verified this key and it is valid...all checks out! + } + // or else we just recurse into that key and chack if it works out + final haveChain = key.hasValidSignatureChain(visited: visited); + if (haveChain) { + return true; + } + } + } + return false; + } +} + +class CrossSigningKey extends _SignedKey { + String get publicKey => identifier; + List usage; + + bool get isValid => userId != null && publicKey != null && keys != null && ed25519Key != null; + + Future setVerified(bool newVerified) { + verified = newVerified; + return client.database?.setVerifiedUserCrossSigningKey(newVerified, client.id, userId, publicKey); + } + + Future setBlocked(bool newBlocked) { + blocked = newBlocked; + return client.database?.setBlockedUserCrossSigningKey(newBlocked, client.id, userId, publicKey); + } + + CrossSigningKey.fromDb(DbUserCrossSigningKey dbEntry, Client cl) { + client = cl; + final json = Event.getMapFromPayload(dbEntry.content); + content = Map.from(json); + userId = dbEntry.userId; + identifier = dbEntry.publicKey; + usage = json['usage'].cast(); + keys = json['keys'] != null ? Map.from(json['keys']) : null; + signatures = json['signatures'] != null ? Map.from(json['signatures']) : null; + validSignatures = null; + if (dbEntry.validSignatures != null) { + final validSignaturesContent = Event.getMapFromPayload(dbEntry.validSignatures); + if (validSignaturesContent is Map) { + validSignatures = validSignaturesContent.cast(); + } + } + verified = dbEntry.verified; + blocked = dbEntry.blocked; + } + + CrossSigningKey.fromJson(Map json, Client cl) { + client = cl; + content = Map.from(json); + userId = json['user_id']; + usage = json['usage'].cast(); + keys = json['keys'] != null ? Map.from(json['keys']) : null; + signatures = json['signatures'] != null + ? Map.from(json['signatures']) + : null; + validSignatures = null; + verified = json['verified'] ?? false; + blocked = json['blocked'] ?? false; + if (keys != null) { + identifier = keys.values.first; + } + } + + Map toJson() { + final data = Map.from(content); + data['user_id'] = userId; + data['usage'] = usage; + if (keys != null) { + data['keys'] = keys; + } + if (signatures != null) { + data['signatures'] = signatures; + } + data['verified'] = verified; + data['blocked'] = blocked; + return data; + } +} + +class DeviceKeys extends _SignedKey { + String get deviceId => identifier; + List algorithms; + Map unsigned; + String get curve25519Key => keys['curve25519:$deviceId']; - String get ed25519Key => keys['ed25519:$deviceId']; - bool get isValid => userId != null && deviceId != null && curve25519Key != null && ed25519Key != null; + bool get isValid => userId != null && deviceId != null && keys != null && curve25519Key != null && ed25519Key != null; - Future setVerified(bool newVerified, Client client) { + Future setVerified(bool newVerified) { verified = newVerified; return client.database?.setVerifiedUserDeviceKey(newVerified, client.id, userId, deviceId); } - Future setBlocked(bool newBlocked, Client client) { + Future setBlocked(bool newBlocked) { blocked = newBlocked; for (var room in client.rooms) { if (!room.encrypted) continue; @@ -85,36 +271,36 @@ class DeviceKeys { return client.database?.setBlockedUserDeviceKey(newBlocked, client.id, userId, deviceId); } - DeviceKeys({ - this.userId, - this.deviceId, - this.algorithms, - this.keys, - this.signatures, - this.unsigned, - this.verified, - this.blocked, - }); - - DeviceKeys.fromDb(DbUserDeviceKeysKey dbEntry) { - final content = Event.getMapFromPayload(dbEntry.content); + DeviceKeys.fromDb(DbUserDeviceKeysKey dbEntry, Client cl) { + client = cl; + final json = Event.getMapFromPayload(dbEntry.content); + content = Map.from(json); userId = dbEntry.userId; - deviceId = dbEntry.deviceId; - algorithms = content['algorithms'].cast(); - keys = content['keys'] != null ? Map.from(content['keys']) : null; - signatures = content['signatures'] != null - ? Map.from(content['signatures']) + identifier = dbEntry.deviceId; + algorithms = json['algorithms'].cast(); + keys = json['keys'] != null ? Map.from(json['keys']) : null; + signatures = json['signatures'] != null + ? Map.from(json['signatures']) : null; - unsigned = content['unsigned'] != null - ? Map.from(content['unsigned']) + unsigned = json['unsigned'] != null + ? Map.from(json['unsigned']) : null; + validSignatures = null; + if (dbEntry.validSignatures != null) { + final validSignaturesContent = Event.getMapFromPayload(dbEntry.validSignatures); + if (validSignaturesContent is Map) { + validSignatures = validSignaturesContent.cast(); + } + } verified = dbEntry.verified; blocked = dbEntry.blocked; } - DeviceKeys.fromJson(Map json) { + DeviceKeys.fromJson(Map json, Client cl) { + client = cl; + content = Map.from(json); userId = json['user_id']; - deviceId = json['device_id']; + identifier = json['device_id']; algorithms = json['algorithms'].cast(); keys = json['keys'] != null ? Map.from(json['keys']) : null; signatures = json['signatures'] != null @@ -128,7 +314,7 @@ class DeviceKeys { } Map toJson() { - final data = {}; + final data = Map.from(content); data['user_id'] = userId; data['device_id'] = deviceId; data['algorithms'] = algorithms; @@ -146,7 +332,7 @@ class DeviceKeys { return data; } - KeyVerification startVerification(Client client) { + KeyVerification startVerification() { final request = KeyVerification(client: client, userId: userId, deviceId: deviceId); request.start(); client.addKeyVerificationRequest(request); diff --git a/lib/src/utils/key_verification.dart b/lib/src/utils/key_verification.dart index 5735d50..b58d575 100644 --- a/lib/src/utils/key_verification.dart +++ b/lib/src/utils/key_verification.dart @@ -271,7 +271,7 @@ class KeyVerification { return []; } - Future verifyKeys(Map keys, Future Function(String, DeviceKeys) verifier) async { + Future verifyKeys(Map keys, Future Function(String, dynamic) verifier) async { final verifiedDevices = []; if (!client.userDeviceKeys.containsKey(userId)) { @@ -288,14 +288,23 @@ class KeyVerification { return; } verifiedDevices.add(verifyDeviceId); - } else { - // TODO: we would check here if what we are verifying is actually a - // cross-signing key and not a "normal" device key + } else if (client.userDeviceKeys[userId].crossSigningKeys.containsKey(verifyDeviceId)) { + // this is a cross signing key! + if (!(await verifier(keyInfo, client.userDeviceKeys[userId].crossSigningKeys[verifyDeviceId]))) { + await cancel('m.key_mismatch'); + return; + } + verifiedDevices.add(verifyDeviceId); } } // okay, we reached this far, so all the devices are verified! for (final verifyDeviceId in verifiedDevices) { - await client.userDeviceKeys[userId].deviceKeys[verifyDeviceId].setVerified(true, client); + if (client.userDeviceKeys[userId].deviceKeys.containsKey(verifyDeviceId)) { + await client.userDeviceKeys[userId].deviceKeys[verifyDeviceId].setVerified(true); + } else if (client.userDeviceKeys[userId].crossSigningKeys.containsKey(verifyDeviceId)) { + await client.userDeviceKeys[userId].crossSigningKeys[verifyDeviceId].setVerified(true); + // TODO: sign the other persons master key + } } } @@ -645,8 +654,13 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { mac[entry.key] = entry.value; } } - await request.verifyKeys(mac, (String mac, DeviceKeys device) async { - return mac == _calculateMac(device.ed25519Key, baseInfo + 'ed25519:' + device.deviceId); + await request.verifyKeys(mac, (String mac, dynamic device) async { + if (device is DeviceKeys) { + return mac == _calculateMac(device.ed25519Key, baseInfo + 'ed25519:' + device.deviceId); + } else if (device is CrossSigningKey) { + return mac == _calculateMac(device.ed25519Key, baseInfo + 'ed25519:' + device.publicKey); + } + return false; }); await request.send('m.key.verification.done', {}); if (request.state != KeyVerificationState.error) { diff --git a/lib/src/utils/room_key_request.dart b/lib/src/utils/room_key_request.dart index d839daa..65dc521 100644 --- a/lib/src/utils/room_key_request.dart +++ b/lib/src/utils/room_key_request.dart @@ -22,7 +22,7 @@ class RoomKeyRequest extends ToDeviceEvent { for (final key in session.forwardingCurve25519KeyChain) { forwardedKeys.add(key); } - await requestingDevice?.setVerified(true, client); + await requestingDevice?.setVerified(true); var message = session.content; message['forwarding_curve25519_key_chain'] = forwardedKeys;