From fe94df97db24e8b7135850fad46a1020ba458306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Tue, 30 Sep 2025 10:07:19 +0200 Subject: [PATCH] refactor: Upgrade to vodozemac cryptoutils --- .github/workflows/versions.env | 4 +- README.md | 5 +- doc/end-to-end-encryption.md | 6 - doc/get-started.md | 1 - lib/encryption/ssss.dart | 44 +- lib/encryption/utils/key_verification.dart | 5 +- lib/src/utils/crypto/crypto.dart | 2 - lib/src/utils/crypto/encrypted_file.dart | 10 +- lib/src/utils/crypto/ffi.dart | 164 ---- lib/src/utils/crypto/js.dart | 77 -- lib/src/utils/crypto/native.dart | 120 --- lib/src/utils/crypto/subtle.dart | 119 --- lib/src/utils/native_implementations.dart | 10 +- pubspec.yaml | 8 +- scripts/prepare_vodozemac.sh | 4 +- test/encryption/ssss_test.dart | 844 +++++++++++---------- test/matrix_file_test.dart | 4 + 17 files changed, 481 insertions(+), 946 deletions(-) delete mode 100644 lib/src/utils/crypto/ffi.dart delete mode 100644 lib/src/utils/crypto/js.dart delete mode 100644 lib/src/utils/crypto/native.dart delete mode 100644 lib/src/utils/crypto/subtle.dart diff --git a/.github/workflows/versions.env b/.github/workflows/versions.env index 3fb81f74..0af8eef5 100644 --- a/.github/workflows/versions.env +++ b/.github/workflows/versions.env @@ -1,2 +1,2 @@ -flutter_version=3.27.4 -dart_version=3.6.2 \ No newline at end of file +flutter_version=3.35.4 +dart_version=3.9.2 \ No newline at end of file diff --git a/README.md b/README.md index 59b84879..0f951101 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,13 @@ Matrix (matrix.org) SDK written in dart. For E2EE, vodozemac must be provided. -Additionally, OpenSSL (libcrypto) must be provided on native platforms for E2EE. - -For flutter apps you can easily import it with the [flutter_vodozemac](https://pub.dev/packages/flutter_vodozemac) and the [flutter_openssl_crypto](https://pub.dev/packages/flutter_openssl_crypto) packages. +For flutter apps you can easily import it with the [flutter_vodozemac](https://pub.dev/packages/flutter_vodozemac) package. ```sh flutter pub add matrix # Optional: For end to end encryption: flutter pub add flutter_vodozemac -flutter pub add flutter_openssl_crypto ``` ## Get started diff --git a/doc/end-to-end-encryption.md b/doc/end-to-end-encryption.md index 8879535d..0f434ecc 100644 --- a/doc/end-to-end-encryption.md +++ b/doc/end-to-end-encryption.md @@ -6,12 +6,6 @@ For Flutter you can use [flutter_vodozemac](https://pub.dev/packages/flutter_vod flutter pub add flutter_vodozemac ``` -You also need [flutter_openssl_crypto](https://pub.dev/packages/flutter_openssl_crypto). - -```sh -flutter pub add flutter_openssl_crypto -``` - Now before you create your `Client`, init vodozemac: ```dart diff --git a/doc/get-started.md b/doc/get-started.md index f1113728..38ffc581 100644 --- a/doc/get-started.md +++ b/doc/get-started.md @@ -12,7 +12,6 @@ In your `pubspec.yaml` file add the following dependencies: # (Optional) For end to end encryption, please head on the # encryption guide and add these dependencies: flutter_vodozemac: - flutter_openssl_crypto: ``` ## Step 2: Create the client diff --git a/lib/encryption/ssss.dart b/lib/encryption/ssss.dart index 6e67abbc..efd50dbf 100644 --- a/lib/encryption/ssss.dart +++ b/lib/encryption/ssss.dart @@ -23,7 +23,7 @@ import 'dart:typed_data'; import 'package:base58check/base58.dart'; import 'package:collection/collection.dart'; -import 'package:crypto/crypto.dart'; +import 'package:vodozemac/vodozemac.dart'; import 'package:matrix/encryption/encryption.dart'; import 'package:matrix/encryption/utils/base64_unpadded.dart'; @@ -74,16 +74,18 @@ class SSSS { static DerivedKeys deriveKeys(Uint8List key, String name) { final zerosalt = Uint8List(8); - final prk = Hmac(sha256, zerosalt).convert(key); + final prk = CryptoUtils.hmac(key: zerosalt, input: key); final b = Uint8List(1); b[0] = 1; - final aesKey = Hmac(sha256, prk.bytes).convert(utf8.encode(name) + b); + final aesKey = CryptoUtils.hmac(key: prk, input: utf8.encode(name) + b); b[0] = 2; - final hmacKey = - Hmac(sha256, prk.bytes).convert(aesKey.bytes + utf8.encode(name) + b); + final hmacKey = CryptoUtils.hmac( + key: prk, + input: aesKey + utf8.encode(name) + b, + ); return DerivedKeys( - aesKey: Uint8List.fromList(aesKey.bytes), - hmacKey: Uint8List.fromList(hmacKey.bytes), + aesKey: Uint8List.fromList(aesKey), + hmacKey: Uint8List.fromList(hmacKey), ); } @@ -105,14 +107,15 @@ class SSSS { final keys = deriveKeys(key, name); final plain = Uint8List.fromList(utf8.encode(data)); - final ciphertext = await uc.aesCtr.encrypt(plain, keys.aesKey, iv); + final ciphertext = + CryptoUtils.aesCtr(input: plain, key: keys.aesKey, iv: iv); - final hmac = Hmac(sha256, keys.hmacKey).convert(ciphertext); + final hmac = CryptoUtils.hmac(key: keys.hmacKey, input: ciphertext); return EncryptedContent( iv: base64.encode(iv), ciphertext: base64.encode(ciphertext), - mac: base64.encode(hmac.bytes), + mac: base64.encode(hmac), ); } @@ -124,13 +127,16 @@ class SSSS { final keys = deriveKeys(key, name); final cipher = base64decodeUnpadded(data.ciphertext); final hmac = base64 - .encode(Hmac(sha256, keys.hmacKey).convert(cipher).bytes) + .encode(CryptoUtils.hmac(key: keys.hmacKey, input: cipher)) .replaceAll(RegExp(r'=+$'), ''); if (hmac != data.mac.replaceAll(RegExp(r'=+$'), '')) { throw Exception('Bad MAC'); } - final decipher = await uc.aesCtr - .encrypt(cipher, keys.aesKey, base64decodeUnpadded(data.iv)); + final decipher = CryptoUtils.aesCtr( + input: cipher, + key: keys.aesKey, + iv: base64decodeUnpadded(data.iv), + ); return String.fromCharCodes(decipher); } @@ -184,12 +190,10 @@ class SSSS { if (info.salt == null) { throw InvalidPassphraseException('Passphrase info without salt'); } - return await uc.pbkdf2( - Uint8List.fromList(utf8.encode(passphrase)), - Uint8List.fromList(utf8.encode(info.salt!)), - uc.sha512, - info.iterations!, - info.bits ?? 256, + return CryptoUtils.pbkdf2( + passphrase: Uint8List.fromList(utf8.encode(passphrase)), + salt: Uint8List.fromList(utf8.encode(info.salt!)), + iterations: info.iterations!, ); } @@ -742,7 +746,7 @@ class OpenSSSS { info: keyData.passphrase!, ), ), - ).timeout(Duration(seconds: 10)); + ).timeout(Duration(minutes: 2)); } else if (recoveryKey != null) { privateKey = SSSS.decodeRecoveryKey(recoveryKey); } else { diff --git a/lib/encryption/utils/key_verification.dart b/lib/encryption/utils/key_verification.dart index 33963621..068137bc 100644 --- a/lib/encryption/utils/key_verification.dart +++ b/lib/encryption/utils/key_verification.dart @@ -21,7 +21,6 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:canonical_json/canonical_json.dart'; -import 'package:crypto/crypto.dart' as crypto; import 'package:typed_data/typed_data.dart'; import 'package:vodozemac/vodozemac.dart' as vod; @@ -1559,8 +1558,8 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { Future _makeCommitment(String pubKey, String canonicalJson) async { if (hash == 'sha256') { final bytes = utf8.encoder.convert(pubKey + canonicalJson); - final digest = crypto.sha256.convert(bytes); - return encodeBase64Unpadded(digest.bytes); + final digest = vod.CryptoUtils.sha256(input: bytes); + return encodeBase64Unpadded(digest); } throw Exception('Unknown hash method'); } diff --git a/lib/src/utils/crypto/crypto.dart b/lib/src/utils/crypto/crypto.dart index c8137934..9f400c23 100644 --- a/lib/src/utils/crypto/crypto.dart +++ b/lib/src/utils/crypto/crypto.dart @@ -16,8 +16,6 @@ * along with this program. If not, see . */ -export 'native.dart' if (dart.library.js_interop) 'js.dart'; - import 'dart:math'; import 'dart:typed_data'; diff --git a/lib/src/utils/crypto/encrypted_file.dart b/lib/src/utils/crypto/encrypted_file.dart index c5b35488..1f639541 100644 --- a/lib/src/utils/crypto/encrypted_file.dart +++ b/lib/src/utils/crypto/encrypted_file.dart @@ -19,6 +19,8 @@ import 'dart:convert'; import 'dart:typed_data'; +import 'package:vodozemac/vodozemac.dart'; + import 'package:matrix/encryption/utils/base64_unpadded.dart'; import 'package:matrix/src/utils/crypto/crypto.dart'; @@ -38,8 +40,8 @@ class EncryptedFile { Future encryptFile(Uint8List input) async { final key = secureRandomBytes(32); final iv = secureRandomBytes(16); - final data = await aesCtr.encrypt(input, key, iv); - final hash = await sha256(data); + final data = CryptoUtils.aesCtr(input: input, key: key, iv: iv); + final hash = CryptoUtils.sha256(input: data); return EncryptedFile( data: data, k: base64Url.encode(key).replaceAll('=', ''), @@ -51,12 +53,12 @@ Future encryptFile(Uint8List input) async { /// you would likely want to use [NativeImplementations] and /// [Client.nativeImplementations] instead Future decryptFileImplementation(EncryptedFile input) async { - if (base64.encode(await sha256(input.data)) != + if (base64.encode(CryptoUtils.sha256(input: input.data)) != base64.normalize(input.sha256)) { return null; } final key = base64decodeUnpadded(base64.normalize(input.k)); final iv = base64decodeUnpadded(base64.normalize(input.iv)); - return await aesCtr.encrypt(input.data, key, iv); + return CryptoUtils.aesCtr(input: input.data, key: key, iv: iv); } diff --git a/lib/src/utils/crypto/ffi.dart b/lib/src/utils/crypto/ffi.dart deleted file mode 100644 index 8cba110f..00000000 --- a/lib/src/utils/crypto/ffi.dart +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Famedly Matrix SDK - * Copyright (C) 2019, 2020, 2021 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 . - */ - -import 'dart:ffi'; -import 'dart:io'; - -final libcrypto = () { - if (Platform.isIOS) { - return DynamicLibrary.process(); - } else if (Platform.isAndroid) { - return DynamicLibrary.open('libcrypto.so'); - } else if (Platform.isWindows) { - return DynamicLibrary.open('libcrypto.dll'); - } else if (Platform.isMacOS) { - try { - return DynamicLibrary.open('libcrypto.3.dylib'); - } catch (_) { - return DynamicLibrary.open('libcrypto.1.1.dylib'); - } - } else { - try { - return DynamicLibrary.open('libcrypto.so.3'); - } catch (_) { - return DynamicLibrary.open('libcrypto.so.1.1'); - } - } -}(); - -final PKCS5_PBKDF2_HMAC = libcrypto.lookupFunction< - IntPtr Function( - Pointer pass, - IntPtr passlen, - Pointer salt, - IntPtr saltlen, - IntPtr iter, - Pointer digest, - IntPtr keylen, - Pointer out, - ), - int Function( - Pointer pass, - int passlen, - Pointer salt, - int saltlen, - int iter, - Pointer digest, - int keylen, - Pointer out, - )>('PKCS5_PBKDF2_HMAC'); - -final EVP_sha1 = libcrypto.lookupFunction Function(), - Pointer Function()>('EVP_sha1'); - -final EVP_sha256 = libcrypto.lookupFunction Function(), - Pointer Function()>('EVP_sha256'); - -final EVP_sha512 = libcrypto.lookupFunction Function(), - Pointer Function()>('EVP_sha512'); - -final EVP_aes_128_ctr = libcrypto.lookupFunction Function(), - Pointer Function()>('EVP_aes_128_ctr'); - -final EVP_aes_256_ctr = libcrypto.lookupFunction Function(), - Pointer Function()>('EVP_aes_256_ctr'); - -final EVP_CIPHER_CTX_new = libcrypto.lookupFunction< - Pointer Function(), - Pointer Function()>('EVP_CIPHER_CTX_new'); - -final EVP_EncryptInit_ex = libcrypto.lookupFunction< - Pointer Function( - Pointer ctx, - Pointer alg, - Pointer some, - Pointer key, - Pointer iv, - ), - Pointer Function( - Pointer ctx, - Pointer alg, - Pointer some, - Pointer key, - Pointer iv, - )>('EVP_EncryptInit_ex'); - -final EVP_EncryptUpdate = libcrypto.lookupFunction< - Pointer Function( - Pointer ctx, - Pointer output, - Pointer outputLen, - Pointer input, - IntPtr inputLen, - ), - Pointer Function( - Pointer ctx, - Pointer output, - Pointer outputLen, - Pointer input, - int inputLen, - )>('EVP_EncryptUpdate'); - -final EVP_EncryptFinal_ex = libcrypto.lookupFunction< - Pointer Function( - Pointer ctx, - Pointer data, - Pointer len, - ), - Pointer Function( - Pointer ctx, - Pointer data, - Pointer len, - )>('EVP_EncryptFinal_ex'); - -final EVP_CIPHER_CTX_free = libcrypto.lookupFunction< - Pointer Function(Pointer ctx), - Pointer Function( - Pointer ctx, - )>('EVP_CIPHER_CTX_free'); - -final EVP_Digest = libcrypto.lookupFunction< - IntPtr Function( - Pointer data, - IntPtr len, - Pointer hash, - Pointer hsize, - Pointer alg, - Pointer engine, - ), - int Function( - Pointer data, - int len, - Pointer hash, - Pointer hsize, - Pointer alg, - Pointer engine, - )>('EVP_Digest'); - -final EVP_MD_size = () { - // EVP_MD_size was renamed to EVP_MD_get_size in Openssl3.0. - // There is an alias macro, but those don't exist in libraries. - // Try loading the new name first, then fall back to the old one if not found. - try { - return libcrypto.lookupFunction ctx), - int Function(Pointer ctx)>('EVP_MD_get_size'); - } catch (e) { - return libcrypto.lookupFunction ctx), - int Function(Pointer ctx)>('EVP_MD_size'); - } -}(); diff --git a/lib/src/utils/crypto/js.dart b/lib/src/utils/crypto/js.dart deleted file mode 100644 index aa2dd2c8..00000000 --- a/lib/src/utils/crypto/js.dart +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) 2020 Famedly GmbH -// SPDX-License-Identifier: AGPL-3.0-or-later - -import 'dart:typed_data'; - -import 'package:matrix/src/utils/crypto/subtle.dart' as subtle; -import 'package:matrix/src/utils/crypto/subtle.dart'; - -abstract class Hash { - Hash._(this.name); - String name; - - Future call(Uint8List input) async => - Uint8List.view(await digest(name, input)); -} - -final Hash sha1 = _Sha1(); -final Hash sha256 = _Sha256(); -final Hash sha512 = _Sha512(); - -class _Sha1 extends Hash { - _Sha1() : super._('SHA-1'); -} - -class _Sha256 extends Hash { - _Sha256() : super._('SHA-256'); -} - -class _Sha512 extends Hash { - _Sha512() : super._('SHA-512'); -} - -abstract class Cipher { - Cipher._(this.name); - String name; - Object params(Uint8List iv); - Future encrypt( - Uint8List input, - Uint8List key, - Uint8List iv, - ) async { - final subtleKey = await importKey('raw', key, name, false, ['encrypt']); - return (await subtle.encrypt(params(iv), subtleKey, input)).asUint8List(); - } -} - -final Cipher aesCtr = _AesCtr(); - -class _AesCtr extends Cipher { - _AesCtr() : super._('AES-CTR'); - - @override - Object params(Uint8List iv) => - AesCtrParams(name: name, counter: iv, length: 64); -} - -Future pbkdf2( - Uint8List passphrase, - Uint8List salt, - Hash hash, - int iterations, - int bits, -) async { - final raw = - await importKey('raw', passphrase, 'PBKDF2', false, ['deriveBits']); - final res = await deriveBits( - Pbkdf2Params( - name: 'PBKDF2', - hash: hash.name, - salt: salt, - iterations: iterations, - ), - raw, - bits, - ); - return Uint8List.view(res); -} diff --git a/lib/src/utils/crypto/native.dart b/lib/src/utils/crypto/native.dart deleted file mode 100644 index c70cded9..00000000 --- a/lib/src/utils/crypto/native.dart +++ /dev/null @@ -1,120 +0,0 @@ -// ignore_for_file: deprecated_member_use -// ignoring the elementAt deprecation because this would make the SDK -// incompatible with older flutter versions than 3.19.0 or dart 3.3.0 - -import 'dart:async'; -import 'dart:ffi'; -import 'dart:typed_data'; - -import 'package:ffi/ffi.dart'; - -import 'package:matrix/src/utils/crypto/ffi.dart'; - -abstract class Hash { - Hash._(this.ptr); - Pointer ptr; - - FutureOr call(Uint8List data) { - final outSize = EVP_MD_size(ptr); - final mem = malloc.call(outSize + data.length); - final dataMem = mem.elementAt(outSize); - try { - dataMem.asTypedList(data.length).setAll(0, data); - EVP_Digest(dataMem, data.length, mem, nullptr, ptr, nullptr); - return Uint8List.fromList(mem.asTypedList(outSize)); - } finally { - malloc.free(mem); - } - } -} - -final Hash sha1 = _Sha1(); -final Hash sha256 = _Sha256(); -final Hash sha512 = _Sha512(); - -class _Sha1 extends Hash { - _Sha1() : super._(EVP_sha1()); -} - -class _Sha256 extends Hash { - _Sha256() : super._(EVP_sha256()); -} - -class _Sha512 extends Hash { - _Sha512() : super._(EVP_sha512()); -} - -abstract class Cipher { - Cipher._(); - Pointer getAlg(int keysize); - FutureOr encrypt(Uint8List input, Uint8List key, Uint8List iv) { - final alg = getAlg(key.length * 8); - final mem = malloc - .call(sizeOf() + key.length + iv.length + input.length); - final lenMem = mem.cast(); - final keyMem = mem.elementAt(sizeOf()); - final ivMem = keyMem.elementAt(key.length); - final dataMem = ivMem.elementAt(iv.length); - try { - keyMem.asTypedList(key.length).setAll(0, key); - ivMem.asTypedList(iv.length).setAll(0, iv); - dataMem.asTypedList(input.length).setAll(0, input); - final ctx = EVP_CIPHER_CTX_new(); - EVP_EncryptInit_ex(ctx, alg, nullptr, keyMem, ivMem); - EVP_EncryptUpdate(ctx, dataMem, lenMem, dataMem, input.length); - EVP_EncryptFinal_ex(ctx, dataMem.elementAt(lenMem.value), lenMem); - EVP_CIPHER_CTX_free(ctx); - return Uint8List.fromList(dataMem.asTypedList(input.length)); - } finally { - malloc.free(mem); - } - } -} - -final Cipher aesCtr = _AesCtr(); - -class _AesCtr extends Cipher { - _AesCtr() : super._(); - - @override - Pointer getAlg(int keysize) { - switch (keysize) { - case 128: - return EVP_aes_128_ctr(); - case 256: - return EVP_aes_256_ctr(); - default: - throw ArgumentError('invalid key size'); - } - } -} - -FutureOr pbkdf2( - Uint8List passphrase, - Uint8List salt, - Hash hash, - int iterations, - int bits, -) { - final outLen = bits ~/ 8; - final mem = malloc.call(passphrase.length + salt.length + outLen); - final saltMem = mem.elementAt(passphrase.length); - final outMem = saltMem.elementAt(salt.length); - try { - mem.asTypedList(passphrase.length).setAll(0, passphrase); - saltMem.asTypedList(salt.length).setAll(0, salt); - PKCS5_PBKDF2_HMAC( - mem, - passphrase.length, - saltMem, - salt.length, - iterations, - hash.ptr, - outLen, - outMem, - ); - return Uint8List.fromList(outMem.asTypedList(outLen)); - } finally { - malloc.free(mem); - } -} diff --git a/lib/src/utils/crypto/subtle.dart b/lib/src/utils/crypto/subtle.dart deleted file mode 100644 index d1cb7c13..00000000 --- a/lib/src/utils/crypto/subtle.dart +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) 2020 Famedly GmbH -// SPDX-License-Identifier: AGPL-3.0-or-later - -import 'dart:async'; -import 'dart:js_util'; -import 'dart:typed_data'; - -import 'package:js/js.dart'; - -@JS() -@anonymous -class Pbkdf2Params { - external factory Pbkdf2Params({ - String name, - String hash, - Uint8List salt, - int iterations, - }); - String? name; - String? hash; - Uint8List? salt; - int? iterations; -} - -@JS() -@anonymous -class AesCtrParams { - external factory AesCtrParams({ - String name, - Uint8List counter, - int length, - }); - String? name; - Uint8List? counter; - int? length; -} - -@JS('crypto.subtle.encrypt') -external dynamic _encrypt(dynamic algorithm, dynamic key, Uint8List data); - -Future encrypt(dynamic algorithm, dynamic key, Uint8List data) { - return promiseToFuture(_encrypt(algorithm, key, data)); -} - -@JS('crypto.subtle.decrypt') -external dynamic _decrypt(dynamic algorithm, dynamic key, Uint8List data); - -Future decrypt(dynamic algorithm, dynamic key, Uint8List data) { - return promiseToFuture(_decrypt(algorithm, key, data)); -} - -@JS('crypto.subtle.importKey') -external dynamic _importKey( - String format, - dynamic keyData, - dynamic algorithm, - bool extractable, - List keyUsages, -); - -Future importKey( - String format, - dynamic keyData, - dynamic algorithm, - bool extractable, - List keyUsages, -) { - return promiseToFuture( - _importKey(format, keyData, algorithm, extractable, keyUsages), - ); -} - -@JS('crypto.subtle.exportKey') -external dynamic _exportKey(String algorithm, dynamic key); - -Future exportKey(String algorithm, dynamic key) { - return promiseToFuture(_exportKey(algorithm, key)); -} - -@JS('crypto.subtle.deriveKey') -external dynamic _deriveKey( - dynamic algorithm, - dynamic baseKey, - dynamic derivedKeyAlgorithm, - bool extractable, - List keyUsages, -); - -Future deriveKey( - dynamic algorithm, - dynamic baseKey, - dynamic derivedKeyAlgorithm, - bool extractable, - List keyUsages, -) { - return promiseToFuture( - _deriveKey( - algorithm, - baseKey, - derivedKeyAlgorithm, - extractable, - keyUsages, - ), - ); -} - -@JS('crypto.subtle.deriveBits') -external dynamic _deriveBits(dynamic algorithm, dynamic baseKey, int length); - -Future deriveBits(dynamic algorithm, dynamic baseKey, int length) { - return promiseToFuture(_deriveBits(algorithm, baseKey, length)); -} - -@JS('crypto.subtle.digest') -external dynamic _digest(String algorithm, Uint8List data); - -Future digest(String algorithm, Uint8List data) { - return promiseToFuture(_digest(algorithm, data)); -} diff --git a/lib/src/utils/native_implementations.dart b/lib/src/utils/native_implementations.dart index 367909d3..ba0163e1 100644 --- a/lib/src/utils/native_implementations.dart +++ b/lib/src/utils/native_implementations.dart @@ -155,7 +155,10 @@ class NativeImplementationsIsolate extends NativeImplementations { bool retryInDummy = true, }) { return runInBackground( - NativeImplementations.dummy.decryptFile, + (EncryptedFile args) async { + await vodozemacInit?.call(); + return NativeImplementations.dummy.decryptFile(args); + }, file, ); } @@ -180,7 +183,10 @@ class NativeImplementationsIsolate extends NativeImplementations { bool retryInDummy = true, }) { return runInBackground( - NativeImplementations.dummy.keyFromPassphrase, + (KeyFromPassphraseArgs args) async { + await vodozemacInit?.call(); + return NativeImplementations.dummy.keyFromPassphrase(args); + }, args, ); } diff --git a/pubspec.yaml b/pubspec.yaml index af0b6f27..33377ab7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,14 +14,10 @@ dependencies: blurhash_dart: ^1.1.0 canonical_json: ^1.1.0 collection: ^1.15.0 - crypto: ^3.0.0 - enhanced_enum: ^0.2.4 - ffi: ^2.0.0 html: ^0.15.0 html_unescape: ^2.0.0 http: ">=0.13.0 <2.0.0" image: ^4.0.15 - js: ^0.6.3 js_interop: ^0.0.1 markdown: ^7.1.1 mime: ">=1.0.0 <3.0.0" @@ -32,7 +28,7 @@ dependencies: sqflite_common: ^2.4.5 sqlite3: ^2.1.0 typed_data: ^1.3.2 - vodozemac: ^0.2.0 + vodozemac: ^0.3.0 web: ^1.1.1 webrtc_interface: ^1.2.0 @@ -43,4 +39,4 @@ dev_dependencies: import_sorter: ^4.6.0 lints: ^5.0.0 sqflite_common_ffi: ^2.3.4+4 # sqflite_common_ffi aggressively requires newer dart versions - test: ^1.25.13 + test: ^1.25.13 \ No newline at end of file diff --git a/scripts/prepare_vodozemac.sh b/scripts/prepare_vodozemac.sh index 47f65044..00bc4df0 100755 --- a/scripts/prepare_vodozemac.sh +++ b/scripts/prepare_vodozemac.sh @@ -1,7 +1,9 @@ #!/usr/bin/env bash rm -rf rust -git clone https://github.com/famedly/dart-vodozemac.git +version=$(yq ".dependencies.vodozemac" < pubspec.yaml) +version=$(expr "$version" : '\^*\(.*\)') +git clone https://github.com/famedly/dart-vodozemac.git -b ${version} mv ./dart-vodozemac/rust ./ rm -rf dart-vodozemac cd ./rust diff --git a/test/encryption/ssss_test.dart b/test/encryption/ssss_test.dart index 96726d76..25a3d074 100644 --- a/test/encryption/ssss_test.dart +++ b/test/encryption/ssss_test.dart @@ -48,462 +48,476 @@ class MockSSSS extends SSSS { } void main() { - group('SSSS', tags: 'olm', () { - Logs().level = Level.error; + group( + 'SSSS', + tags: 'olm', + () { + Logs().level = Level.error; - late Client client; + late Client client; - setUpAll(() async { - await vod.init( - wasmPath: './pkg/', - libraryPath: './rust/target/debug/', - ); + setUpAll(() async { + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); - client = await getClient(); - }); - - test('basic things', () async { - expect( - client.encryption!.ssss.defaultKeyId, - '0FajDWYaM6wQ4O60OZnLvwZfsBNu4Bu3', - ); - }); - - test('encrypt / decrypt', () async { - final key = Uint8List.fromList(secureRandomBytes(32)); - - final enc = await SSSS.encryptAes('secret foxies', key, 'name'); - final dec = await SSSS.decryptAes(enc, key, 'name'); - expect(dec, 'secret foxies'); - }); - - test('store', () async { - final handle = client.encryption!.ssss.open(); - var failed = false; - try { - await handle.unlock(passphrase: 'invalid'); - } catch (_) { - failed = true; - } - expect(failed, true); - expect(handle.isUnlocked, false); - failed = false; - try { - await handle.unlock(recoveryKey: 'invalid'); - } catch (_) { - failed = true; - } - expect(failed, true); - expect(handle.isUnlocked, false); - await handle.unlock(passphrase: ssssPassphrase); - await handle.unlock(recoveryKey: ssssKey); - expect(handle.isUnlocked, true); - FakeMatrixApi.calledEndpoints.clear(); - - // OpenSSSS store waits for accountdata to be updated before returning - // but we can't update that before the below endpoint is not hit. - await handle.ssss - .store('best animal', 'foxies', handle.keyId, handle.privateKey!); - - final content = FakeMatrixApi - .calledEndpoints[ - '/client/v3/user/%40test%3AfakeServer.notExisting/account_data/best%20animal']! - .first; - client.accountData['best animal'] = BasicEvent.fromJson({ - 'type': 'best animal', - 'content': json.decode(content), + client = await getClient(); }); - expect(await handle.getStored('best animal'), 'foxies'); - }); - test('encode / decode recovery key', () async { - final key = Uint8List.fromList(secureRandomBytes(32)); - final encoded = SSSS.encodeRecoveryKey(key); - var decoded = SSSS.decodeRecoveryKey(encoded); - expect(key, decoded); + test('basic things', () async { + expect( + client.encryption!.ssss.defaultKeyId, + '0FajDWYaM6wQ4O60OZnLvwZfsBNu4Bu3', + ); + }); - decoded = SSSS.decodeRecoveryKey('$encoded \n\t'); - expect(key, decoded); + test('encrypt / decrypt', () async { + final key = Uint8List.fromList(secureRandomBytes(32)); - final handle = client.encryption!.ssss.open(); - await handle.unlock(recoveryKey: ssssKey); - expect(handle.recoveryKey, ssssKey); - }); + final enc = await SSSS.encryptAes('secret foxies', key, 'name'); + final dec = await SSSS.decryptAes(enc, key, 'name'); + expect(dec, 'secret foxies'); + }); - test('cache', () async { - await client.encryption!.ssss.clearCache(); - final handle = - client.encryption!.ssss.open(EventTypes.CrossSigningSelfSigning); - await handle.unlock(recoveryKey: ssssKey, postUnlock: false); - expect( - (await client.encryption!.ssss - .getCached(EventTypes.CrossSigningSelfSigning)) != - null, - false, - ); - expect( - (await client.encryption!.ssss - .getCached(EventTypes.CrossSigningUserSigning)) != - null, - false, - ); - await handle.getStored(EventTypes.CrossSigningSelfSigning); - expect( - (await client.encryption!.ssss - .getCached(EventTypes.CrossSigningSelfSigning)) != - null, - true, - ); - await handle.maybeCacheAll(); - expect( - (await client.encryption!.ssss - .getCached(EventTypes.CrossSigningUserSigning)) != - null, - true, - ); - expect( - (await client.encryption!.ssss.getCached(EventTypes.MegolmBackup)) != - null, - true, - ); - }); + test('store', () async { + final handle = client.encryption!.ssss.open(); + var failed = false; + try { + await handle.unlock(passphrase: 'invalid'); + } catch (_) { + failed = true; + } + expect(failed, true); + expect(handle.isUnlocked, false); + failed = false; + try { + await handle.unlock(recoveryKey: 'invalid'); + } catch (_) { + failed = true; + } + expect(failed, true); + expect(handle.isUnlocked, false); + await handle.unlock(passphrase: ssssPassphrase); + await handle.unlock(recoveryKey: ssssKey); + expect(handle.isUnlocked, true); + FakeMatrixApi.calledEndpoints.clear(); - test('postUnlock', () async { - await client.encryption!.ssss.clearCache(); - client.userDeviceKeys[client.userID!]!.masterKey! - .setDirectVerified(false); - final handle = - client.encryption!.ssss.open(EventTypes.CrossSigningSelfSigning); - await handle.unlock(recoveryKey: ssssKey); - expect( - (await client.encryption!.ssss - .getCached(EventTypes.CrossSigningSelfSigning)) != - null, - true, - ); - expect( - (await client.encryption!.ssss - .getCached(EventTypes.CrossSigningUserSigning)) != - null, - true, - ); - expect( - (await client.encryption!.ssss.getCached(EventTypes.MegolmBackup)) != - null, - true, - ); - expect( - client.userDeviceKeys[client.userID!]!.masterKey!.directVerified, - true, - ); - }); + // OpenSSSS store waits for accountdata to be updated before returning + // but we can't update that before the below endpoint is not hit. + await handle.ssss + .store('best animal', 'foxies', handle.keyId, handle.privateKey!); - test('make share requests', () async { - final key = - client.userDeviceKeys[client.userID!]!.deviceKeys['OTHERDEVICE']!; - key.setDirectVerified(true); - FakeMatrixApi.calledEndpoints.clear(); - await client.encryption!.ssss.request('some.type', [key]); - expect( - FakeMatrixApi.calledEndpoints.keys.any( - (k) => k.startsWith('/client/v3/sendToDevice/m.room.encrypted'), - ), - true, - ); - }); + final content = FakeMatrixApi + .calledEndpoints[ + '/client/v3/user/%40test%3AfakeServer.notExisting/account_data/best%20animal']! + .first; + client.accountData['best animal'] = BasicEvent.fromJson({ + 'type': 'best animal', + 'content': json.decode(content), + }); + expect(await handle.getStored('best animal'), 'foxies'); + }); - test('answer to share requests', () async { - var event = ToDeviceEvent( - sender: client.userID!, - type: 'm.secret.request', - content: { - 'action': 'request', - 'requesting_device_id': 'OTHERDEVICE', - 'name': EventTypes.CrossSigningSelfSigning, - 'request_id': '1', - }, - ); - FakeMatrixApi.calledEndpoints.clear(); - await client.encryption!.ssss.handleToDeviceEvent(event); - expect( - FakeMatrixApi.calledEndpoints.keys.any( - (k) => k.startsWith('/client/v3/sendToDevice/m.room.encrypted'), - ), - true, - ); + test('encode / decode recovery key', () async { + final key = Uint8List.fromList(secureRandomBytes(32)); + final encoded = SSSS.encodeRecoveryKey(key); + var decoded = SSSS.decodeRecoveryKey(encoded); + expect(key, decoded); - // now test some fail scenarios + decoded = SSSS.decodeRecoveryKey('$encoded \n\t'); + expect(key, decoded); - // not by us - event = ToDeviceEvent( - sender: '@someotheruser:example.org', - type: 'm.secret.request', - content: { - 'action': 'request', - 'requesting_device_id': 'OTHERDEVICE', - 'name': EventTypes.CrossSigningSelfSigning, - 'request_id': '1', - }, - ); - FakeMatrixApi.calledEndpoints.clear(); - await client.encryption!.ssss.handleToDeviceEvent(event); - expect( - FakeMatrixApi.calledEndpoints.keys.any( - (k) => k.startsWith('/client/v3/sendToDevice/m.room.encrypted'), - ), - false, - ); + final handle = client.encryption!.ssss.open(); + await handle.unlock(recoveryKey: ssssKey); + expect(handle.recoveryKey, ssssKey); + }); - // secret not cached - event = ToDeviceEvent( - sender: client.userID!, - type: 'm.secret.request', - content: { - 'action': 'request', - 'requesting_device_id': 'OTHERDEVICE', - 'name': 'm.unknown.secret', - 'request_id': '1', - }, - ); - FakeMatrixApi.calledEndpoints.clear(); - await client.encryption!.ssss.handleToDeviceEvent(event); - expect( - FakeMatrixApi.calledEndpoints.keys.any( - (k) => k.startsWith('/client/v3/sendToDevice/m.room.encrypted'), - ), - false, - ); + test('cache', () async { + await client.encryption!.ssss.clearCache(); + final handle = + client.encryption!.ssss.open(EventTypes.CrossSigningSelfSigning); + await handle.unlock(recoveryKey: ssssKey, postUnlock: false); + expect( + (await client.encryption!.ssss + .getCached(EventTypes.CrossSigningSelfSigning)) != + null, + false, + ); + expect( + (await client.encryption!.ssss + .getCached(EventTypes.CrossSigningUserSigning)) != + null, + false, + ); + await handle.getStored(EventTypes.CrossSigningSelfSigning); + expect( + (await client.encryption!.ssss + .getCached(EventTypes.CrossSigningSelfSigning)) != + null, + true, + ); + await handle.maybeCacheAll(); + expect( + (await client.encryption!.ssss + .getCached(EventTypes.CrossSigningUserSigning)) != + null, + true, + ); + expect( + (await client.encryption!.ssss.getCached(EventTypes.MegolmBackup)) != + null, + true, + ); + }); - // is a cancelation - event = ToDeviceEvent( - sender: client.userID!, - type: 'm.secret.request', - content: { - 'action': 'request_cancellation', - 'requesting_device_id': 'OTHERDEVICE', - 'name': EventTypes.CrossSigningSelfSigning, - 'request_id': '1', - }, - ); - FakeMatrixApi.calledEndpoints.clear(); - await client.encryption!.ssss.handleToDeviceEvent(event); - expect( - FakeMatrixApi.calledEndpoints.keys.any( - (k) => k.startsWith('/client/v3/sendToDevice/m.room.encrypted'), - ), - false, - ); + test('postUnlock', () async { + await client.encryption!.ssss.clearCache(); + client.userDeviceKeys[client.userID!]!.masterKey! + .setDirectVerified(false); + final handle = + client.encryption!.ssss.open(EventTypes.CrossSigningSelfSigning); + await handle.unlock(recoveryKey: ssssKey); + expect( + (await client.encryption!.ssss + .getCached(EventTypes.CrossSigningSelfSigning)) != + null, + true, + ); + expect( + (await client.encryption!.ssss + .getCached(EventTypes.CrossSigningUserSigning)) != + null, + true, + ); + expect( + (await client.encryption!.ssss.getCached(EventTypes.MegolmBackup)) != + null, + true, + ); + expect( + client.userDeviceKeys[client.userID!]!.masterKey!.directVerified, + true, + ); + }); - // device not verified - final key = - client.userDeviceKeys[client.userID!]!.deviceKeys['OTHERDEVICE']!; - key.setDirectVerified(false); - client.userDeviceKeys[client.userID!]!.masterKey! - .setDirectVerified(false); - event = ToDeviceEvent( - sender: client.userID!, - type: 'm.secret.request', - content: { - 'action': 'request', - 'requesting_device_id': 'OTHERDEVICE', - 'name': EventTypes.CrossSigningSelfSigning, - 'request_id': '1', - }, - ); - FakeMatrixApi.calledEndpoints.clear(); - await client.encryption!.ssss.handleToDeviceEvent(event); - expect( - FakeMatrixApi.calledEndpoints.keys.any( - (k) => k.startsWith('/client/v3/sendToDevice/m.room.encrypted'), - ), - false, - ); - key.setDirectVerified(true); - }); + test('make share requests', () async { + final key = + client.userDeviceKeys[client.userID!]!.deviceKeys['OTHERDEVICE']!; + key.setDirectVerified(true); + FakeMatrixApi.calledEndpoints.clear(); + await client.encryption!.ssss.request('some.type', [key]); + expect( + FakeMatrixApi.calledEndpoints.keys.any( + (k) => k.startsWith('/client/v3/sendToDevice/m.room.encrypted'), + ), + true, + ); + }); - test('receive share requests', () async { - final key = - client.userDeviceKeys[client.userID!]!.deviceKeys['OTHERDEVICE']!; - key.setDirectVerified(true); - final handle = - client.encryption!.ssss.open(EventTypes.CrossSigningSelfSigning); - await handle.unlock(recoveryKey: ssssKey); + test('answer to share requests', () async { + var event = ToDeviceEvent( + sender: client.userID!, + type: 'm.secret.request', + content: { + 'action': 'request', + 'requesting_device_id': 'OTHERDEVICE', + 'name': EventTypes.CrossSigningSelfSigning, + 'request_id': '1', + }, + ); + FakeMatrixApi.calledEndpoints.clear(); + await client.encryption!.ssss.handleToDeviceEvent(event); + expect( + FakeMatrixApi.calledEndpoints.keys.any( + (k) => k.startsWith('/client/v3/sendToDevice/m.room.encrypted'), + ), + true, + ); - await client.encryption!.ssss.clearCache(); - client.encryption!.ssss.pendingShareRequests.clear(); - await client.encryption!.ssss.request('best animal', [key]); - var event = ToDeviceEvent( - sender: client.userID!, - type: 'm.secret.send', - content: { - 'request_id': client.encryption!.ssss.pendingShareRequests.keys.first, - 'secret': 'foxies!', - }, - encryptedContent: { - 'sender_key': key.curve25519Key, - }, - ); - await client.encryption!.ssss.handleToDeviceEvent(event); - expect(await client.encryption!.ssss.getCached('best animal'), 'foxies!'); + // now test some fail scenarios + + // not by us + event = ToDeviceEvent( + sender: '@someotheruser:example.org', + type: 'm.secret.request', + content: { + 'action': 'request', + 'requesting_device_id': 'OTHERDEVICE', + 'name': EventTypes.CrossSigningSelfSigning, + 'request_id': '1', + }, + ); + FakeMatrixApi.calledEndpoints.clear(); + await client.encryption!.ssss.handleToDeviceEvent(event); + expect( + FakeMatrixApi.calledEndpoints.keys.any( + (k) => k.startsWith('/client/v3/sendToDevice/m.room.encrypted'), + ), + false, + ); + + // secret not cached + event = ToDeviceEvent( + sender: client.userID!, + type: 'm.secret.request', + content: { + 'action': 'request', + 'requesting_device_id': 'OTHERDEVICE', + 'name': 'm.unknown.secret', + 'request_id': '1', + }, + ); + FakeMatrixApi.calledEndpoints.clear(); + await client.encryption!.ssss.handleToDeviceEvent(event); + expect( + FakeMatrixApi.calledEndpoints.keys.any( + (k) => k.startsWith('/client/v3/sendToDevice/m.room.encrypted'), + ), + false, + ); + + // is a cancelation + event = ToDeviceEvent( + sender: client.userID!, + type: 'm.secret.request', + content: { + 'action': 'request_cancellation', + 'requesting_device_id': 'OTHERDEVICE', + 'name': EventTypes.CrossSigningSelfSigning, + 'request_id': '1', + }, + ); + FakeMatrixApi.calledEndpoints.clear(); + await client.encryption!.ssss.handleToDeviceEvent(event); + expect( + FakeMatrixApi.calledEndpoints.keys.any( + (k) => k.startsWith('/client/v3/sendToDevice/m.room.encrypted'), + ), + false, + ); + + // device not verified + final key = + client.userDeviceKeys[client.userID!]!.deviceKeys['OTHERDEVICE']!; + key.setDirectVerified(false); + client.userDeviceKeys[client.userID!]!.masterKey! + .setDirectVerified(false); + event = ToDeviceEvent( + sender: client.userID!, + type: 'm.secret.request', + content: { + 'action': 'request', + 'requesting_device_id': 'OTHERDEVICE', + 'name': EventTypes.CrossSigningSelfSigning, + 'request_id': '1', + }, + ); + FakeMatrixApi.calledEndpoints.clear(); + await client.encryption!.ssss.handleToDeviceEvent(event); + expect( + FakeMatrixApi.calledEndpoints.keys.any( + (k) => k.startsWith('/client/v3/sendToDevice/m.room.encrypted'), + ), + false, + ); + key.setDirectVerified(true); + }); + + test('receive share requests', () async { + final key = + client.userDeviceKeys[client.userID!]!.deviceKeys['OTHERDEVICE']!; + key.setDirectVerified(true); + final handle = + client.encryption!.ssss.open(EventTypes.CrossSigningSelfSigning); + await handle.unlock(recoveryKey: ssssKey); - // test the different validators - for (final type in [ - EventTypes.CrossSigningSelfSigning, - EventTypes.CrossSigningUserSigning, - EventTypes.MegolmBackup, - ]) { - final secret = await handle.getStored(type); await client.encryption!.ssss.clearCache(); client.encryption!.ssss.pendingShareRequests.clear(); - await client.encryption!.ssss.request(type, [key]); - event = ToDeviceEvent( + await client.encryption!.ssss.request('best animal', [key]); + var event = ToDeviceEvent( sender: client.userID!, type: 'm.secret.send', content: { 'request_id': client.encryption!.ssss.pendingShareRequests.keys.first, - 'secret': secret, + 'secret': 'foxies!', }, encryptedContent: { 'sender_key': key.curve25519Key, }, ); await client.encryption!.ssss.handleToDeviceEvent(event); - expect(await client.encryption!.ssss.getCached(type), secret); - } + expect( + await client.encryption!.ssss.getCached('best animal'), + 'foxies!', + ); - // test different fail scenarios + // test the different validators + for (final type in [ + EventTypes.CrossSigningSelfSigning, + EventTypes.CrossSigningUserSigning, + EventTypes.MegolmBackup, + ]) { + final secret = await handle.getStored(type); + await client.encryption!.ssss.clearCache(); + client.encryption!.ssss.pendingShareRequests.clear(); + await client.encryption!.ssss.request(type, [key]); + event = ToDeviceEvent( + sender: client.userID!, + type: 'm.secret.send', + content: { + 'request_id': + client.encryption!.ssss.pendingShareRequests.keys.first, + 'secret': secret, + }, + encryptedContent: { + 'sender_key': key.curve25519Key, + }, + ); + await client.encryption!.ssss.handleToDeviceEvent(event); + expect(await client.encryption!.ssss.getCached(type), secret); + } - // not encrypted - await client.encryption!.ssss.clearCache(); - client.encryption!.ssss.pendingShareRequests.clear(); - await client.encryption!.ssss.request('best animal', [key]); - event = ToDeviceEvent( - sender: client.userID!, - type: 'm.secret.send', - content: { - 'request_id': client.encryption!.ssss.pendingShareRequests.keys.first, - 'secret': 'foxies!', - }, - ); - await client.encryption!.ssss.handleToDeviceEvent(event); - expect(await client.encryption!.ssss.getCached('best animal'), null); + // test different fail scenarios - // unknown request id - await client.encryption!.ssss.clearCache(); - client.encryption!.ssss.pendingShareRequests.clear(); - await client.encryption!.ssss.request('best animal', [key]); - event = ToDeviceEvent( - sender: client.userID!, - type: 'm.secret.send', - content: { - 'request_id': 'invalid', - 'secret': 'foxies!', - }, - encryptedContent: { - 'sender_key': key.curve25519Key, - }, - ); - await client.encryption!.ssss.handleToDeviceEvent(event); - expect(await client.encryption!.ssss.getCached('best animal'), null); + // not encrypted + await client.encryption!.ssss.clearCache(); + client.encryption!.ssss.pendingShareRequests.clear(); + await client.encryption!.ssss.request('best animal', [key]); + event = ToDeviceEvent( + sender: client.userID!, + type: 'm.secret.send', + content: { + 'request_id': + client.encryption!.ssss.pendingShareRequests.keys.first, + 'secret': 'foxies!', + }, + ); + await client.encryption!.ssss.handleToDeviceEvent(event); + expect(await client.encryption!.ssss.getCached('best animal'), null); - // not from a device we sent the request to - await client.encryption!.ssss.clearCache(); - client.encryption!.ssss.pendingShareRequests.clear(); - await client.encryption!.ssss.request('best animal', [key]); - event = ToDeviceEvent( - sender: client.userID!, - type: 'm.secret.send', - content: { - 'request_id': client.encryption!.ssss.pendingShareRequests.keys.first, - 'secret': 'foxies!', - }, - encryptedContent: { - 'sender_key': 'invalid', - }, - ); - await client.encryption!.ssss.handleToDeviceEvent(event); - expect(await client.encryption!.ssss.getCached('best animal'), null); + // unknown request id + await client.encryption!.ssss.clearCache(); + client.encryption!.ssss.pendingShareRequests.clear(); + await client.encryption!.ssss.request('best animal', [key]); + event = ToDeviceEvent( + sender: client.userID!, + type: 'm.secret.send', + content: { + 'request_id': 'invalid', + 'secret': 'foxies!', + }, + encryptedContent: { + 'sender_key': key.curve25519Key, + }, + ); + await client.encryption!.ssss.handleToDeviceEvent(event); + expect(await client.encryption!.ssss.getCached('best animal'), null); - // secret not a string - await client.encryption!.ssss.clearCache(); - client.encryption!.ssss.pendingShareRequests.clear(); - await client.encryption!.ssss.request('best animal', [key]); - event = ToDeviceEvent( - sender: client.userID!, - type: 'm.secret.send', - content: { - 'request_id': client.encryption!.ssss.pendingShareRequests.keys.first, - 'secret': 42, - }, - encryptedContent: { - 'sender_key': key.curve25519Key, - }, - ); - await client.encryption!.ssss.handleToDeviceEvent(event); - expect(await client.encryption!.ssss.getCached('best animal'), null); + // not from a device we sent the request to + await client.encryption!.ssss.clearCache(); + client.encryption!.ssss.pendingShareRequests.clear(); + await client.encryption!.ssss.request('best animal', [key]); + event = ToDeviceEvent( + sender: client.userID!, + type: 'm.secret.send', + content: { + 'request_id': + client.encryption!.ssss.pendingShareRequests.keys.first, + 'secret': 'foxies!', + }, + encryptedContent: { + 'sender_key': 'invalid', + }, + ); + await client.encryption!.ssss.handleToDeviceEvent(event); + expect(await client.encryption!.ssss.getCached('best animal'), null); - // validator doesn't check out - await client.encryption!.ssss.clearCache(); - client.encryption!.ssss.pendingShareRequests.clear(); - await client.encryption!.ssss.request(EventTypes.MegolmBackup, [key]); - event = ToDeviceEvent( - sender: client.userID!, - type: 'm.secret.send', - content: { - 'request_id': client.encryption!.ssss.pendingShareRequests.keys.first, - 'secret': 'foxies!', - }, - encryptedContent: { - 'sender_key': key.curve25519Key, - }, - ); - await client.encryption!.ssss.handleToDeviceEvent(event); - expect( - await client.encryption!.ssss.getCached(EventTypes.MegolmBackup), - null, - ); - }); + // secret not a string + await client.encryption!.ssss.clearCache(); + client.encryption!.ssss.pendingShareRequests.clear(); + await client.encryption!.ssss.request('best animal', [key]); + event = ToDeviceEvent( + sender: client.userID!, + type: 'm.secret.send', + content: { + 'request_id': + client.encryption!.ssss.pendingShareRequests.keys.first, + 'secret': 42, + }, + encryptedContent: { + 'sender_key': key.curve25519Key, + }, + ); + await client.encryption!.ssss.handleToDeviceEvent(event); + expect(await client.encryption!.ssss.getCached('best animal'), null); - test('request all', () async { - final key = - client.userDeviceKeys[client.userID!]!.deviceKeys['OTHERDEVICE']!; - key.setDirectVerified(true); - await client.encryption!.ssss.clearCache(); - client.encryption!.ssss.pendingShareRequests.clear(); - await client.encryption!.ssss.maybeRequestAll([key]); - expect(client.encryption!.ssss.pendingShareRequests.length, 3); - }); + // validator doesn't check out + await client.encryption!.ssss.clearCache(); + client.encryption!.ssss.pendingShareRequests.clear(); + await client.encryption!.ssss.request(EventTypes.MegolmBackup, [key]); + event = ToDeviceEvent( + sender: client.userID!, + type: 'm.secret.send', + content: { + 'request_id': + client.encryption!.ssss.pendingShareRequests.keys.first, + 'secret': 'foxies!', + }, + encryptedContent: { + 'sender_key': key.curve25519Key, + }, + ); + await client.encryption!.ssss.handleToDeviceEvent(event); + expect( + await client.encryption!.ssss.getCached(EventTypes.MegolmBackup), + null, + ); + }); - test('periodicallyRequestMissingCache', () async { - client.userDeviceKeys[client.userID!]!.masterKey!.setDirectVerified(true); - client.encryption!.ssss = MockSSSS(client.encryption!); - (client.encryption!.ssss as MockSSSS).requestedSecrets = false; - await client.encryption!.ssss.periodicallyRequestMissingCache(); - expect((client.encryption!.ssss as MockSSSS).requestedSecrets, true); - // it should only retry once every 15 min - (client.encryption!.ssss as MockSSSS).requestedSecrets = false; - await client.encryption!.ssss.periodicallyRequestMissingCache(); - expect((client.encryption!.ssss as MockSSSS).requestedSecrets, false); - }); + test('request all', () async { + final key = + client.userDeviceKeys[client.userID!]!.deviceKeys['OTHERDEVICE']!; + key.setDirectVerified(true); + await client.encryption!.ssss.clearCache(); + client.encryption!.ssss.pendingShareRequests.clear(); + await client.encryption!.ssss.maybeRequestAll([key]); + expect(client.encryption!.ssss.pendingShareRequests.length, 3); + }); - test('createKey', () async { - // with passphrase - var newKey = await client.encryption!.ssss.createKey('test'); - expect(client.encryption!.ssss.isKeyValid(newKey.keyId), true); - var testKey = client.encryption!.ssss.open(newKey.keyId); - await testKey.unlock(passphrase: 'test'); - await testKey.setPrivateKey(newKey.privateKey!); + test('periodicallyRequestMissingCache', () async { + client.userDeviceKeys[client.userID!]!.masterKey! + .setDirectVerified(true); + client.encryption!.ssss = MockSSSS(client.encryption!); + (client.encryption!.ssss as MockSSSS).requestedSecrets = false; + await client.encryption!.ssss.periodicallyRequestMissingCache(); + expect((client.encryption!.ssss as MockSSSS).requestedSecrets, true); + // it should only retry once every 15 min + (client.encryption!.ssss as MockSSSS).requestedSecrets = false; + await client.encryption!.ssss.periodicallyRequestMissingCache(); + expect((client.encryption!.ssss as MockSSSS).requestedSecrets, false); + }); - // without passphrase - newKey = await client.encryption!.ssss.createKey(); - expect(client.encryption!.ssss.isKeyValid(newKey.keyId), true); - testKey = client.encryption!.ssss.open(newKey.keyId); - await testKey.setPrivateKey(newKey.privateKey!); - }); + test('createKey', () async { + // with passphrase + var newKey = await client.encryption!.ssss.createKey('test'); + expect(client.encryption!.ssss.isKeyValid(newKey.keyId), true); + var testKey = client.encryption!.ssss.open(newKey.keyId); + await testKey.unlock(passphrase: 'test'); + await testKey.setPrivateKey(newKey.privateKey!); - test('dispose client', () async { - await client.dispose(closeDatabase: true); - }); - }); + // without passphrase + newKey = await client.encryption!.ssss.createKey(); + expect(client.encryption!.ssss.isKeyValid(newKey.keyId), true); + testKey = client.encryption!.ssss.open(newKey.keyId); + await testKey.setPrivateKey(newKey.privateKey!); + }); + + test('dispose client', () async { + await client.dispose(closeDatabase: true); + }); + }, + timeout: Timeout(const Duration(minutes: 2)), + ); } diff --git a/test/matrix_file_test.dart b/test/matrix_file_test.dart index 4dc11668..3c19f175 100644 --- a/test/matrix_file_test.dart +++ b/test/matrix_file_test.dart @@ -22,10 +22,14 @@ import 'package:http/http.dart' as http; import 'package:test/test.dart'; import 'package:matrix/matrix.dart'; +import 'fake_client.dart'; void main() { /// All Tests related to device keys group('Matrix File', tags: 'olm', () { + setUpAll(() async { + await getClient(); // To trigger vodozemac init + }); Logs().level = Level.error; test('Decrypt', () async { final text = 'hello world';