feature: Upload to online key backup
This commit is contained in:
parent
8899f4c677
commit
99d536b14f
|
@ -63,6 +63,8 @@ class Encryption {
|
|||
|
||||
Future<void> init(String olmAccount) async {
|
||||
await olmManager.init(olmAccount);
|
||||
_backgroundTasksRunning = true;
|
||||
_backgroundTasks(); // start the background tasks
|
||||
}
|
||||
|
||||
void handleDeviceOneTimeKeysCount(Map<String, int> countJson) {
|
||||
|
@ -307,10 +309,25 @@ class Encryption {
|
|||
return await olmManager.encryptToDeviceMessage(deviceKeys, type, payload);
|
||||
}
|
||||
|
||||
// this method is responsible for all background tasks, such as uploading online key backups
|
||||
bool _backgroundTasksRunning = true;
|
||||
void _backgroundTasks() {
|
||||
if (!_backgroundTasksRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
keyManager.backgroundTasks();
|
||||
|
||||
if (_backgroundTasksRunning) {
|
||||
Timer(Duration(seconds: 10), _backgroundTasks);
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
keyManager.dispose();
|
||||
olmManager.dispose();
|
||||
keyVerificationManager.dispose();
|
||||
_backgroundTasksRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,9 @@ import './utils/outbound_group_session.dart';
|
|||
import './utils/session_key.dart';
|
||||
import '../famedlysdk.dart';
|
||||
import '../matrix_api.dart';
|
||||
import '../src/database/database.dart';
|
||||
import '../src/utils/logs.dart';
|
||||
import '../src/utils/run_in_background.dart';
|
||||
|
||||
const MEGOLM_KEY = 'm.megolm_backup.v1';
|
||||
|
||||
|
@ -71,18 +73,14 @@ class KeyManager {
|
|||
|
||||
void setInboundGroupSession(String roomId, String sessionId, String senderKey,
|
||||
Map<String, dynamic> content,
|
||||
{bool forwarded = false, Map<String, String> senderClaimedKeys}) {
|
||||
{bool forwarded = false,
|
||||
Map<String, String> senderClaimedKeys,
|
||||
bool uploaded = false}) {
|
||||
senderClaimedKeys ??= <String, String>{};
|
||||
if (!senderClaimedKeys.containsKey('ed25519')) {
|
||||
DeviceKeys device;
|
||||
for (final user in client.userDeviceKeys.values) {
|
||||
device = user.deviceKeys.values.firstWhere(
|
||||
(e) => e.curve25519Key == senderKey,
|
||||
orElse: () => null);
|
||||
if (device != null) {
|
||||
senderClaimedKeys['ed25519'] = device.ed25519Key;
|
||||
break;
|
||||
}
|
||||
final device = client.getUserDeviceKeysByCurve25519Key(senderKey);
|
||||
if (device != null) {
|
||||
senderClaimedKeys['ed25519'] = device.ed25519Key;
|
||||
}
|
||||
}
|
||||
final oldSession =
|
||||
|
@ -109,6 +107,8 @@ class KeyManager {
|
|||
content: content,
|
||||
inboundGroupSession: inboundGroupSession,
|
||||
indexes: {},
|
||||
roomId: roomId,
|
||||
sessionId: sessionId,
|
||||
key: client.userID,
|
||||
senderKey: senderKey,
|
||||
senderClaimedKeys: senderClaimedKeys,
|
||||
|
@ -132,7 +132,8 @@ class KeyManager {
|
|||
_inboundGroupSessions[roomId] = <String, SessionKey>{};
|
||||
}
|
||||
_inboundGroupSessions[roomId][sessionId] = newSession;
|
||||
client.database?.storeInboundGroupSession(
|
||||
client.database
|
||||
?.storeInboundGroupSession(
|
||||
client.id,
|
||||
roomId,
|
||||
sessionId,
|
||||
|
@ -141,9 +142,13 @@ class KeyManager {
|
|||
json.encode({}),
|
||||
senderKey,
|
||||
json.encode(senderClaimedKeys),
|
||||
);
|
||||
// Note to self: When adding key-backup that needs to be unawaited(), else
|
||||
// we might accidentally end up with http requests inside of the sync loop
|
||||
)
|
||||
?.then((_) {
|
||||
if (uploaded) {
|
||||
client.database
|
||||
.markInboundGroupSessionAsUploaded(client.id, roomId, sessionId);
|
||||
}
|
||||
});
|
||||
// TODO: somehow try to decrypt last message again
|
||||
final room = client.getRoomById(roomId);
|
||||
if (room != null) {
|
||||
|
@ -410,7 +415,8 @@ class KeyManager {
|
|||
forwarded: true,
|
||||
senderClaimedKeys: decrypted['sender_claimed_keys'] != null
|
||||
? Map<String, String>.from(decrypted['sender_claimed_keys'])
|
||||
: null);
|
||||
: null,
|
||||
uploaded: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -492,6 +498,79 @@ class KeyManager {
|
|||
}
|
||||
}
|
||||
|
||||
bool _isUploadingKeys = false;
|
||||
Future<void> backgroundTasks() async {
|
||||
if (_isUploadingKeys || client.database == null) {
|
||||
return;
|
||||
}
|
||||
_isUploadingKeys = true;
|
||||
try {
|
||||
if (!(await isCached())) {
|
||||
return; // we can't backup anyways
|
||||
}
|
||||
final dbSessions =
|
||||
await client.database.getInboundGroupSessionsToUpload().get();
|
||||
if (dbSessions.isEmpty) {
|
||||
return; // nothing to do
|
||||
}
|
||||
final privateKey =
|
||||
base64.decode(await encryption.ssss.getCached(MEGOLM_KEY));
|
||||
// decryption is needed to calculate the public key and thus see if the claimed information is in fact valid
|
||||
final decryption = olm.PkDecryption();
|
||||
final info = await client.getRoomKeysBackup();
|
||||
String backupPubKey;
|
||||
try {
|
||||
backupPubKey = decryption.init_with_private_key(privateKey);
|
||||
|
||||
if (backupPubKey == null ||
|
||||
info.algorithm != RoomKeysAlgorithmType.v1Curve25519AesSha2 ||
|
||||
info.authData['public_key'] != backupPubKey) {
|
||||
return;
|
||||
}
|
||||
final args = _GenerateUploadKeysArgs(
|
||||
pubkey: backupPubKey,
|
||||
dbSessions: <_DbInboundGroupSessionBundle>[],
|
||||
userId: client.userID,
|
||||
);
|
||||
// we need to calculate verified beforehand, as else we pass a closure to an isolate
|
||||
// with 500 keys they do, however, noticably block the UI, which is why we give brief async suspentions in here
|
||||
// so that the event loop can progress
|
||||
var i = 0;
|
||||
for (final dbSession in dbSessions) {
|
||||
final device =
|
||||
client.getUserDeviceKeysByCurve25519Key(dbSession.senderKey);
|
||||
args.dbSessions.add(_DbInboundGroupSessionBundle(
|
||||
dbSession: dbSession,
|
||||
verified: device?.verified ?? false,
|
||||
));
|
||||
i++;
|
||||
if (i > 10) {
|
||||
await Future.delayed(Duration(milliseconds: 1));
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
final roomKeys =
|
||||
await runInBackground<RoomKeys, _GenerateUploadKeysArgs>(
|
||||
_generateUploadKeys, args);
|
||||
Logs.info('[Key Manager] Uploading ${dbSessions.length} room keys...');
|
||||
// upload the payload...
|
||||
await client.storeRoomKeys(info.version, roomKeys);
|
||||
// and now finally mark all the keys as uploaded
|
||||
// no need to optimze this, as we only run it so seldomly and almost never with many keys at once
|
||||
for (final dbSession in dbSessions) {
|
||||
await client.database.markInboundGroupSessionAsUploaded(
|
||||
client.id, dbSession.roomId, dbSession.sessionId);
|
||||
}
|
||||
} finally {
|
||||
decryption.free();
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logs.error('[Key Manager] Error uploading room keys: ' + e.toString(), s);
|
||||
} finally {
|
||||
_isUploadingKeys = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle an incoming to_device event that is related to key sharing
|
||||
Future<void> handleToDeviceEvent(ToDeviceEvent event) async {
|
||||
if (event.type == 'm.room_key_request') {
|
||||
|
@ -725,3 +804,67 @@ class RoomKeyRequest extends ToDeviceEvent {
|
|||
keyManager.incomingShareRequests.remove(request.requestId);
|
||||
}
|
||||
}
|
||||
|
||||
RoomKeys _generateUploadKeys(_GenerateUploadKeysArgs args) {
|
||||
final enc = olm.PkEncryption();
|
||||
try {
|
||||
enc.set_recipient_key(args.pubkey);
|
||||
// first we generate the payload to upload all the session keys in this chunk
|
||||
final roomKeys = RoomKeys();
|
||||
for (final dbSession in args.dbSessions) {
|
||||
final sess = SessionKey.fromDb(dbSession.dbSession, args.userId);
|
||||
if (!sess.isValid) {
|
||||
continue;
|
||||
}
|
||||
// create the room if it doesn't exist
|
||||
if (!roomKeys.rooms.containsKey(sess.roomId)) {
|
||||
roomKeys.rooms[sess.roomId] = RoomKeysRoom();
|
||||
}
|
||||
// generate the encrypted content
|
||||
final payload = <String, dynamic>{
|
||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||
'forwarding_curve25519_key_chain': sess.forwardingCurve25519KeyChain,
|
||||
'sender_key': sess.senderKey,
|
||||
'sender_clencaimed_keys': sess.senderClaimedKeys,
|
||||
'session_key': sess.inboundGroupSession
|
||||
.export_session(sess.inboundGroupSession.first_known_index()),
|
||||
};
|
||||
// encrypt the content
|
||||
final encrypted = enc.encrypt(json.encode(payload));
|
||||
// fetch the device, if available...
|
||||
//final device = args.client.getUserDeviceKeysByCurve25519Key(sess.senderKey);
|
||||
// aaaand finally add the session key to our payload
|
||||
roomKeys.rooms[sess.roomId].sessions[sess.sessionId] = RoomKeysSingleKey(
|
||||
firstMessageIndex: sess.inboundGroupSession.first_known_index(),
|
||||
forwardedCount: sess.forwardingCurve25519KeyChain.length,
|
||||
isVerified: dbSession.verified, //device?.verified ?? false,
|
||||
sessionData: {
|
||||
'ephemeral': encrypted.ephemeral,
|
||||
'ciphertext': encrypted.ciphertext,
|
||||
'mac': encrypted.mac,
|
||||
},
|
||||
);
|
||||
}
|
||||
return roomKeys;
|
||||
} catch (e, s) {
|
||||
Logs.error('[Key Manager] Error generating payload ' + e.toString(), s);
|
||||
rethrow;
|
||||
} finally {
|
||||
enc.free();
|
||||
}
|
||||
}
|
||||
|
||||
class _DbInboundGroupSessionBundle {
|
||||
_DbInboundGroupSessionBundle({this.dbSession, this.verified});
|
||||
|
||||
DbInboundGroupSession dbSession;
|
||||
bool verified;
|
||||
}
|
||||
|
||||
class _GenerateUploadKeysArgs {
|
||||
_GenerateUploadKeysArgs({this.pubkey, this.dbSessions, this.userId});
|
||||
|
||||
String pubkey;
|
||||
List<_DbInboundGroupSessionBundle> dbSessions;
|
||||
String userId;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import 'package:password_hash/password_hash.dart';
|
|||
|
||||
import '../famedlysdk.dart';
|
||||
import '../matrix_api.dart';
|
||||
import '../src/database/database.dart';
|
||||
import '../src/utils/logs.dart';
|
||||
import 'encryption.dart';
|
||||
|
||||
|
@ -48,8 +49,15 @@ class SSSS {
|
|||
Client get client => encryption.client;
|
||||
final pendingShareRequests = <String, _ShareRequest>{};
|
||||
final _validators = <String, Future<bool> Function(String)>{};
|
||||
final Map<String, DbSSSSCache> _cache = <String, DbSSSSCache>{};
|
||||
SSSS(this.encryption);
|
||||
|
||||
// for testing
|
||||
Future<void> clearCache() async {
|
||||
await client.database?.clearSSSSCache(client.id);
|
||||
_cache.clear();
|
||||
}
|
||||
|
||||
static _DerivedKeys deriveKeys(Uint8List key, String name) {
|
||||
final zerosalt = Uint8List(8);
|
||||
final prk = Hmac(sha256, zerosalt).convert(key);
|
||||
|
@ -173,16 +181,22 @@ class SSSS {
|
|||
if (client.database == null) {
|
||||
return null;
|
||||
}
|
||||
// check if it is still valid
|
||||
final keys = keyIdsFromType(type);
|
||||
final isValid = (dbEntry) =>
|
||||
keys.contains(dbEntry.keyId) &&
|
||||
client.accountData[type].content['encrypted'][dbEntry.keyId]
|
||||
['ciphertext'] ==
|
||||
dbEntry.ciphertext;
|
||||
if (_cache.containsKey(type) && isValid(_cache[type])) {
|
||||
return _cache[type].content;
|
||||
}
|
||||
final ret = await client.database.getSSSSCache(client.id, type);
|
||||
if (ret == null) {
|
||||
return null;
|
||||
}
|
||||
// check if it is still valid
|
||||
final keys = keyIdsFromType(type);
|
||||
if (keys.contains(ret.keyId) &&
|
||||
client.accountData[type].content['encrypted'][ret.keyId]
|
||||
['ciphertext'] ==
|
||||
ret.ciphertext) {
|
||||
if (isValid(ret)) {
|
||||
_cache[type] = ret;
|
||||
return ret.content;
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -37,12 +37,16 @@ class SessionKey {
|
|||
Map<String, String> senderClaimedKeys;
|
||||
String senderKey;
|
||||
bool get isValid => inboundGroupSession != null;
|
||||
String roomId;
|
||||
String sessionId;
|
||||
|
||||
SessionKey(
|
||||
{this.content,
|
||||
this.inboundGroupSession,
|
||||
this.key,
|
||||
this.indexes,
|
||||
this.roomId,
|
||||
this.sessionId,
|
||||
String senderKey,
|
||||
Map<String, String> senderClaimedKeys}) {
|
||||
_setSenderKey(senderKey);
|
||||
|
@ -59,6 +63,8 @@ class SessionKey {
|
|||
indexes = parsedIndexes != null
|
||||
? Map<String, int>.from(parsedIndexes)
|
||||
: <String, int>{};
|
||||
roomId = dbEntry.roomId;
|
||||
sessionId = dbEntry.sessionId;
|
||||
_setSenderKey(dbEntry.senderKey);
|
||||
_setSenderClaimedKeys(Map<String, String>.from(parsedSenderClaimedKeys));
|
||||
|
||||
|
|
|
@ -2062,7 +2062,7 @@ class MatrixApi {
|
|||
return RoomKeysRoom.fromJson(ret);
|
||||
}
|
||||
|
||||
/// Deletes room ekys for a room
|
||||
/// Deletes room keys for a room
|
||||
/// https://matrix.org/docs/spec/client_server/unstable#delete-matrix-client-r0-room-keys-keys-roomid
|
||||
Future<RoomKeysUpdateResponse> deleteRoomKeysRoom(
|
||||
String roomId, String version) async {
|
||||
|
|
|
@ -22,6 +22,12 @@ class RoomKeysSingleKey {
|
|||
bool isVerified;
|
||||
Map<String, dynamic> sessionData;
|
||||
|
||||
RoomKeysSingleKey(
|
||||
{this.firstMessageIndex,
|
||||
this.forwardedCount,
|
||||
this.isVerified,
|
||||
this.sessionData});
|
||||
|
||||
RoomKeysSingleKey.fromJson(Map<String, dynamic> json) {
|
||||
firstMessageIndex = json['first_message_index'];
|
||||
forwardedCount = json['forwarded_count'];
|
||||
|
@ -42,6 +48,10 @@ class RoomKeysSingleKey {
|
|||
class RoomKeysRoom {
|
||||
Map<String, RoomKeysSingleKey> sessions;
|
||||
|
||||
RoomKeysRoom({this.sessions}) {
|
||||
sessions ??= <String, RoomKeysSingleKey>{};
|
||||
}
|
||||
|
||||
RoomKeysRoom.fromJson(Map<String, dynamic> json) {
|
||||
sessions = (json['sessions'] as Map)
|
||||
.map((k, v) => MapEntry(k, RoomKeysSingleKey.fromJson(v)));
|
||||
|
@ -57,6 +67,10 @@ class RoomKeysRoom {
|
|||
class RoomKeys {
|
||||
Map<String, RoomKeysRoom> rooms;
|
||||
|
||||
RoomKeys({this.rooms}) {
|
||||
rooms ??= <String, RoomKeysRoom>{};
|
||||
}
|
||||
|
||||
RoomKeys.fromJson(Map<String, dynamic> json) {
|
||||
rooms = (json['rooms'] as Map)
|
||||
.map((k, v) => MapEntry(k, RoomKeysRoom.fromJson(v)));
|
||||
|
|
|
@ -615,6 +615,7 @@ class Client extends MatrixApi {
|
|||
return;
|
||||
}
|
||||
|
||||
encryption?.dispose();
|
||||
encryption =
|
||||
Encryption(client: this, enableE2eeRecovery: enableE2eeRecovery);
|
||||
await encryption.init(olmAccount);
|
||||
|
@ -1165,6 +1166,18 @@ class Client extends MatrixApi {
|
|||
Map<String, DeviceKeysList> get userDeviceKeys => _userDeviceKeys;
|
||||
Map<String, DeviceKeysList> _userDeviceKeys = {};
|
||||
|
||||
/// Gets user device keys by its curve25519 key. Returns null if it isn't found
|
||||
DeviceKeys getUserDeviceKeysByCurve25519Key(String senderKey) {
|
||||
for (final user in userDeviceKeys.values) {
|
||||
final device = user.deviceKeys.values
|
||||
.firstWhere((e) => e.curve25519Key == senderKey, orElse: () => null);
|
||||
if (device != null) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<Set<String>> _getUserIdsInEncryptedRooms() async {
|
||||
var userIds = <String>{};
|
||||
for (var i = 0; i < rooms.length; i++) {
|
||||
|
@ -1493,6 +1506,8 @@ class Client extends MatrixApi {
|
|||
}
|
||||
if (closeDatabase) await database?.close();
|
||||
database = null;
|
||||
encryption?.dispose();
|
||||
encryption = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5873,6 +5873,27 @@ abstract class _$Database extends GeneratedDatabase {
|
|||
);
|
||||
}
|
||||
|
||||
Selectable<DbInboundGroupSession> getInboundGroupSessionsToUpload() {
|
||||
return customSelect(
|
||||
'SELECT * FROM inbound_group_sessions WHERE uploaded = false LIMIT 500',
|
||||
variables: [],
|
||||
readsFrom: {inboundGroupSessions}).map(_rowToDbInboundGroupSession);
|
||||
}
|
||||
|
||||
Future<int> markInboundGroupSessionAsUploaded(
|
||||
int client_id, String room_id, String session_id) {
|
||||
return customUpdate(
|
||||
'UPDATE inbound_group_sessions SET uploaded = true WHERE client_id = :client_id AND room_id = :room_id AND session_id = :session_id',
|
||||
variables: [
|
||||
Variable.withInt(client_id),
|
||||
Variable.withString(room_id),
|
||||
Variable.withString(session_id)
|
||||
],
|
||||
updates: {inboundGroupSessions},
|
||||
updateKind: UpdateKind.update,
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> storeUserDeviceKeysInfo(
|
||||
int client_id, String user_id, bool outdated) {
|
||||
return customInsert(
|
||||
|
|
|
@ -191,6 +191,8 @@ dbGetInboundGroupSessionKeys: SELECT * FROM inbound_group_sessions WHERE client_
|
|||
getAllInboundGroupSessions: SELECT * FROM inbound_group_sessions WHERE client_id = :client_id;
|
||||
storeInboundGroupSession: INSERT OR REPLACE INTO inbound_group_sessions (client_id, room_id, session_id, pickle, content, indexes, sender_key, sender_claimed_keys) VALUES (:client_id, :room_id, :session_id, :pickle, :content, :indexes, :sender_key, :sender_claimed_keys);
|
||||
updateInboundGroupSessionIndexes: UPDATE inbound_group_sessions SET indexes = :indexes WHERE client_id = :client_id AND room_id = :room_id AND session_id = :session_id;
|
||||
getInboundGroupSessionsToUpload: SELECT * FROM inbound_group_sessions WHERE uploaded = false LIMIT 500;
|
||||
markInboundGroupSessionAsUploaded: UPDATE inbound_group_sessions SET uploaded = true WHERE client_id = :client_id AND room_id = :room_id AND session_id = :session_id;
|
||||
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;
|
||||
|
|
|
@ -1,3 +1,21 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:ansicolor/ansicolor.dart';
|
||||
|
||||
abstract class Logs {
|
||||
|
|
30
lib/src/utils/run_in_background.dart
Normal file
30
lib/src/utils/run_in_background.dart
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:isolate/isolate.dart';
|
||||
import 'dart:async';
|
||||
|
||||
Future<T> runInBackground<T, U>(
|
||||
FutureOr<T> Function(U arg) function, U arg) async {
|
||||
final isolate = await IsolateRunner.spawn();
|
||||
try {
|
||||
return await isolate.run(function, arg);
|
||||
} finally {
|
||||
await isolate.close();
|
||||
}
|
||||
}
|
|
@ -281,6 +281,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.4"
|
||||
isolate:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: isolate
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -609,7 +616,7 @@ packages:
|
|||
name: test_coverage
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.1"
|
||||
version: "0.4.3"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -22,10 +22,11 @@ dependencies:
|
|||
olm: ^1.2.1
|
||||
matrix_file_e2ee: ^1.0.4
|
||||
ansicolor: ^1.0.2
|
||||
isolate: ^2.0.3
|
||||
|
||||
dev_dependencies:
|
||||
test: ^1.0.0
|
||||
test_coverage: ^0.4.1
|
||||
test_coverage: ^0.4.3
|
||||
moor_generator: ^3.0.0
|
||||
build_runner: ^1.5.2
|
||||
pedantic: ^1.9.0
|
||||
|
|
4
test.sh
4
test.sh
|
@ -1,6 +1,6 @@
|
|||
#!/bin/sh -e
|
||||
pub run test -p vm
|
||||
pub run test_coverage
|
||||
# pub run test -p vm
|
||||
pub run test_coverage --print-test-output
|
||||
pub global activate remove_from_coverage
|
||||
pub global run remove_from_coverage:remove_from_coverage -f coverage/lcov.info -r '\.g\.dart$'
|
||||
genhtml -o coverage coverage/lcov.info || true
|
||||
|
|
|
@ -207,7 +207,7 @@ void main() {
|
|||
|
||||
test('ask SSSS start', () async {
|
||||
client1.userDeviceKeys[client1.userID].masterKey.setDirectVerified(true);
|
||||
await client1.database.clearSSSSCache(client1.id);
|
||||
await client1.encryption.ssss.clearCache();
|
||||
final req1 =
|
||||
await client1.userDeviceKeys[client2.userID].startVerification();
|
||||
expect(req1.state, KeyVerificationState.askSSSS);
|
||||
|
@ -288,7 +288,7 @@ void main() {
|
|||
|
||||
// alright, they match
|
||||
client1.userDeviceKeys[client1.userID].masterKey.setDirectVerified(true);
|
||||
await client1.database.clearSSSSCache(client1.id);
|
||||
await client1.encryption.ssss.clearCache();
|
||||
|
||||
// send mac
|
||||
FakeMatrixApi.calledEndpoints.clear();
|
||||
|
@ -312,7 +312,7 @@ void main() {
|
|||
|
||||
client1.encryption.ssss = MockSSSS(client1.encryption);
|
||||
(client1.encryption.ssss as MockSSSS).requestedSecrets = false;
|
||||
await client1.database.clearSSSSCache(client1.id);
|
||||
await client1.encryption.ssss.clearCache();
|
||||
await req1.maybeRequestSSSSSecrets();
|
||||
await Future.delayed(Duration(milliseconds: 10));
|
||||
expect((client1.encryption.ssss as MockSSSS).requestedSecrets, true);
|
||||
|
|
|
@ -16,12 +16,16 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/src/utils/logs.dart';
|
||||
import 'package:famedlysdk/matrix_api.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
|
||||
import '../fake_client.dart';
|
||||
import '../fake_matrix_api.dart';
|
||||
|
||||
void main() {
|
||||
group('Online Key Backup', () {
|
||||
|
@ -67,6 +71,49 @@ void main() {
|
|||
true);
|
||||
});
|
||||
|
||||
test('upload key', () async {
|
||||
final session = olm.OutboundGroupSession();
|
||||
session.create();
|
||||
final inbound = olm.InboundGroupSession();
|
||||
inbound.create(session.session_key());
|
||||
final senderKey = client.identityKey;
|
||||
final roomId = '!someroom:example.org';
|
||||
final sessionId = inbound.session_id();
|
||||
// set a payload...
|
||||
var sessionPayload = <String, dynamic>{
|
||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||
'room_id': roomId,
|
||||
'forwarding_curve25519_key_chain': [client.identityKey],
|
||||
'session_id': sessionId,
|
||||
'session_key': inbound.export_session(1),
|
||||
'sender_key': senderKey,
|
||||
'sender_claimed_ed25519_key': client.fingerprintKey,
|
||||
};
|
||||
FakeMatrixApi.calledEndpoints.clear();
|
||||
client.encryption.keyManager.setInboundGroupSession(
|
||||
roomId, sessionId, senderKey, sessionPayload,
|
||||
forwarded: true);
|
||||
var dbSessions =
|
||||
await client.database.getInboundGroupSessionsToUpload().get();
|
||||
expect(dbSessions.isNotEmpty, true);
|
||||
await client.encryption.keyManager.backgroundTasks();
|
||||
final payload = FakeMatrixApi
|
||||
.calledEndpoints['/client/unstable/room_keys/keys?version=5'].first;
|
||||
dbSessions =
|
||||
await client.database.getInboundGroupSessionsToUpload().get();
|
||||
expect(dbSessions.isEmpty, true);
|
||||
|
||||
final onlineKeys = RoomKeys.fromJson(json.decode(payload));
|
||||
client.encryption.keyManager.clearInboundGroupSessions();
|
||||
var ret = client.encryption.keyManager
|
||||
.getInboundGroupSession(roomId, sessionId, senderKey);
|
||||
expect(ret, null);
|
||||
await client.encryption.keyManager.loadFromResponse(onlineKeys);
|
||||
ret = client.encryption.keyManager
|
||||
.getInboundGroupSession(roomId, sessionId, senderKey);
|
||||
expect(ret != null, true);
|
||||
});
|
||||
|
||||
test('dispose client', () async {
|
||||
await client.dispose(closeDatabase: true);
|
||||
});
|
||||
|
|
|
@ -248,7 +248,7 @@ void main() {
|
|||
client.encryption.ssss.open('m.cross_signing.self_signing');
|
||||
handle.unlock(recoveryKey: SSSS_KEY);
|
||||
|
||||
await client.database.clearSSSSCache(client.id);
|
||||
await client.encryption.ssss.clearCache();
|
||||
client.encryption.ssss.pendingShareRequests.clear();
|
||||
await client.encryption.ssss.request('best animal', [key]);
|
||||
var event = ToDeviceEvent(
|
||||
|
@ -272,7 +272,7 @@ void main() {
|
|||
'm.megolm_backup.v1'
|
||||
]) {
|
||||
final secret = await handle.getStored(type);
|
||||
await client.database.clearSSSSCache(client.id);
|
||||
await client.encryption.ssss.clearCache();
|
||||
client.encryption.ssss.pendingShareRequests.clear();
|
||||
await client.encryption.ssss.request(type, [key]);
|
||||
event = ToDeviceEvent(
|
||||
|
@ -294,7 +294,7 @@ void main() {
|
|||
// test different fail scenarios
|
||||
|
||||
// not encrypted
|
||||
await client.database.clearSSSSCache(client.id);
|
||||
await client.encryption.ssss.clearCache();
|
||||
client.encryption.ssss.pendingShareRequests.clear();
|
||||
await client.encryption.ssss.request('best animal', [key]);
|
||||
event = ToDeviceEvent(
|
||||
|
@ -309,7 +309,7 @@ void main() {
|
|||
expect(await client.encryption.ssss.getCached('best animal'), null);
|
||||
|
||||
// unknown request id
|
||||
await client.database.clearSSSSCache(client.id);
|
||||
await client.encryption.ssss.clearCache();
|
||||
client.encryption.ssss.pendingShareRequests.clear();
|
||||
await client.encryption.ssss.request('best animal', [key]);
|
||||
event = ToDeviceEvent(
|
||||
|
@ -327,7 +327,7 @@ void main() {
|
|||
expect(await client.encryption.ssss.getCached('best animal'), null);
|
||||
|
||||
// not from a device we sent the request to
|
||||
await client.database.clearSSSSCache(client.id);
|
||||
await client.encryption.ssss.clearCache();
|
||||
client.encryption.ssss.pendingShareRequests.clear();
|
||||
await client.encryption.ssss.request('best animal', [key]);
|
||||
event = ToDeviceEvent(
|
||||
|
@ -345,7 +345,7 @@ void main() {
|
|||
expect(await client.encryption.ssss.getCached('best animal'), null);
|
||||
|
||||
// secret not a string
|
||||
await client.database.clearSSSSCache(client.id);
|
||||
await client.encryption.ssss.clearCache();
|
||||
client.encryption.ssss.pendingShareRequests.clear();
|
||||
await client.encryption.ssss.request('best animal', [key]);
|
||||
event = ToDeviceEvent(
|
||||
|
@ -363,7 +363,7 @@ void main() {
|
|||
expect(await client.encryption.ssss.getCached('best animal'), null);
|
||||
|
||||
// validator doesn't check out
|
||||
await client.database.clearSSSSCache(client.id);
|
||||
await client.encryption.ssss.clearCache();
|
||||
client.encryption.ssss.pendingShareRequests.clear();
|
||||
await client.encryption.ssss.request('m.megolm_backup.v1', [key]);
|
||||
event = ToDeviceEvent(
|
||||
|
@ -386,7 +386,7 @@ void main() {
|
|||
final key =
|
||||
client.userDeviceKeys[client.userID].deviceKeys['OTHERDEVICE'];
|
||||
key.setDirectVerified(true);
|
||||
await client.database.clearSSSSCache(client.id);
|
||||
await client.encryption.ssss.clearCache();
|
||||
client.encryption.ssss.pendingShareRequests.clear();
|
||||
await client.encryption.ssss.maybeRequestAll([key]);
|
||||
expect(client.encryption.ssss.pendingShareRequests.length, 3);
|
||||
|
|
|
@ -132,6 +132,8 @@ void main() {
|
|||
await client.checkServer('https://fakeserver.notexisting');
|
||||
expect(user1.canChangePowerLevel, false);
|
||||
});
|
||||
client.dispose();
|
||||
test('dispose client', () async {
|
||||
await client.dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue