refactor: Upgrade to vodozemac cryptoutils

This commit is contained in:
Christian Kußowski 2025-09-30 10:07:19 +02:00
parent 03c5e354d8
commit fe94df97db
No known key found for this signature in database
GPG Key ID: E067ECD60F1A0652
17 changed files with 481 additions and 946 deletions

View File

@ -1,2 +1,2 @@
flutter_version=3.27.4
dart_version=3.6.2
flutter_version=3.35.4
dart_version=3.9.2

View File

@ -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

View File

@ -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

View File

@ -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: <latest-version>
flutter_openssl_crypto: <latest-version>
```
## Step 2: Create the client

View File

@ -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 {

View File

@ -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<String> _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');
}

View File

@ -16,8 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export 'native.dart' if (dart.library.js_interop) 'js.dart';
import 'dart:math';
import 'dart:typed_data';

View File

@ -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<EncryptedFile> 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<EncryptedFile> encryptFile(Uint8List input) async {
/// you would likely want to use [NativeImplementations] and
/// [Client.nativeImplementations] instead
Future<Uint8List?> 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);
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Uint8> pass,
IntPtr passlen,
Pointer<Uint8> salt,
IntPtr saltlen,
IntPtr iter,
Pointer<NativeType> digest,
IntPtr keylen,
Pointer<Uint8> out,
),
int Function(
Pointer<Uint8> pass,
int passlen,
Pointer<Uint8> salt,
int saltlen,
int iter,
Pointer<NativeType> digest,
int keylen,
Pointer<Uint8> out,
)>('PKCS5_PBKDF2_HMAC');
final EVP_sha1 = libcrypto.lookupFunction<Pointer<NativeType> Function(),
Pointer<NativeType> Function()>('EVP_sha1');
final EVP_sha256 = libcrypto.lookupFunction<Pointer<NativeType> Function(),
Pointer<NativeType> Function()>('EVP_sha256');
final EVP_sha512 = libcrypto.lookupFunction<Pointer<NativeType> Function(),
Pointer<NativeType> Function()>('EVP_sha512');
final EVP_aes_128_ctr = libcrypto.lookupFunction<Pointer<NativeType> Function(),
Pointer<NativeType> Function()>('EVP_aes_128_ctr');
final EVP_aes_256_ctr = libcrypto.lookupFunction<Pointer<NativeType> Function(),
Pointer<NativeType> Function()>('EVP_aes_256_ctr');
final EVP_CIPHER_CTX_new = libcrypto.lookupFunction<
Pointer<NativeType> Function(),
Pointer<NativeType> Function()>('EVP_CIPHER_CTX_new');
final EVP_EncryptInit_ex = libcrypto.lookupFunction<
Pointer<NativeType> Function(
Pointer<NativeType> ctx,
Pointer<NativeType> alg,
Pointer<NativeType> some,
Pointer<Uint8> key,
Pointer<Uint8> iv,
),
Pointer<NativeType> Function(
Pointer<NativeType> ctx,
Pointer<NativeType> alg,
Pointer<NativeType> some,
Pointer<Uint8> key,
Pointer<Uint8> iv,
)>('EVP_EncryptInit_ex');
final EVP_EncryptUpdate = libcrypto.lookupFunction<
Pointer<NativeType> Function(
Pointer<NativeType> ctx,
Pointer<Uint8> output,
Pointer<IntPtr> outputLen,
Pointer<Uint8> input,
IntPtr inputLen,
),
Pointer<NativeType> Function(
Pointer<NativeType> ctx,
Pointer<Uint8> output,
Pointer<IntPtr> outputLen,
Pointer<Uint8> input,
int inputLen,
)>('EVP_EncryptUpdate');
final EVP_EncryptFinal_ex = libcrypto.lookupFunction<
Pointer<NativeType> Function(
Pointer<NativeType> ctx,
Pointer<Uint8> data,
Pointer<IntPtr> len,
),
Pointer<NativeType> Function(
Pointer<NativeType> ctx,
Pointer<Uint8> data,
Pointer<IntPtr> len,
)>('EVP_EncryptFinal_ex');
final EVP_CIPHER_CTX_free = libcrypto.lookupFunction<
Pointer<NativeType> Function(Pointer<NativeType> ctx),
Pointer<NativeType> Function(
Pointer<NativeType> ctx,
)>('EVP_CIPHER_CTX_free');
final EVP_Digest = libcrypto.lookupFunction<
IntPtr Function(
Pointer<Uint8> data,
IntPtr len,
Pointer<Uint8> hash,
Pointer<IntPtr> hsize,
Pointer<NativeType> alg,
Pointer<NativeType> engine,
),
int Function(
Pointer<Uint8> data,
int len,
Pointer<Uint8> hash,
Pointer<IntPtr> hsize,
Pointer<NativeType> alg,
Pointer<NativeType> 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<IntPtr Function(Pointer<NativeType> ctx),
int Function(Pointer<NativeType> ctx)>('EVP_MD_get_size');
} catch (e) {
return libcrypto.lookupFunction<IntPtr Function(Pointer<NativeType> ctx),
int Function(Pointer<NativeType> ctx)>('EVP_MD_size');
}
}();

View File

@ -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<Uint8List> 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<Uint8List> 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<Uint8List> 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);
}

View File

@ -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<NativeType> ptr;
FutureOr<Uint8List> call(Uint8List data) {
final outSize = EVP_MD_size(ptr);
final mem = malloc.call<Uint8>(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<NativeType> getAlg(int keysize);
FutureOr<Uint8List> encrypt(Uint8List input, Uint8List key, Uint8List iv) {
final alg = getAlg(key.length * 8);
final mem = malloc
.call<Uint8>(sizeOf<IntPtr>() + key.length + iv.length + input.length);
final lenMem = mem.cast<IntPtr>();
final keyMem = mem.elementAt(sizeOf<IntPtr>());
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<NativeType> 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<Uint8List> pbkdf2(
Uint8List passphrase,
Uint8List salt,
Hash hash,
int iterations,
int bits,
) {
final outLen = bits ~/ 8;
final mem = malloc.call<Uint8>(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);
}
}

View File

@ -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<ByteBuffer> 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<ByteBuffer> 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<String> keyUsages,
);
Future<dynamic> importKey(
String format,
dynamic keyData,
dynamic algorithm,
bool extractable,
List<String> keyUsages,
) {
return promiseToFuture(
_importKey(format, keyData, algorithm, extractable, keyUsages),
);
}
@JS('crypto.subtle.exportKey')
external dynamic _exportKey(String algorithm, dynamic key);
Future<dynamic> 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<String> keyUsages,
);
Future<ByteBuffer> deriveKey(
dynamic algorithm,
dynamic baseKey,
dynamic derivedKeyAlgorithm,
bool extractable,
List<String> keyUsages,
) {
return promiseToFuture(
_deriveKey(
algorithm,
baseKey,
derivedKeyAlgorithm,
extractable,
keyUsages,
),
);
}
@JS('crypto.subtle.deriveBits')
external dynamic _deriveBits(dynamic algorithm, dynamic baseKey, int length);
Future<ByteBuffer> 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<ByteBuffer> digest(String algorithm, Uint8List data) {
return promiseToFuture(_digest(algorithm, data));
}

View File

@ -155,7 +155,10 @@ class NativeImplementationsIsolate extends NativeImplementations {
bool retryInDummy = true,
}) {
return runInBackground<Uint8List?, EncryptedFile>(
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<Uint8List, KeyFromPassphraseArgs>(
NativeImplementations.dummy.keyFromPassphrase,
(KeyFromPassphraseArgs args) async {
await vodozemacInit?.call();
return NativeImplementations.dummy.keyFromPassphrase(args);
},
args,
);
}

View File

@ -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

View File

@ -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

View File

@ -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)),
);
}

View File

@ -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';