refactor: Upgrade to vodozemac cryptoutils
This commit is contained in:
parent
03c5e354d8
commit
fe94df97db
|
|
@ -1,2 +1,2 @@
|
||||||
flutter_version=3.27.4
|
flutter_version=3.35.4
|
||||||
dart_version=3.6.2
|
dart_version=3.9.2
|
||||||
|
|
@ -6,16 +6,13 @@ Matrix (matrix.org) SDK written in dart.
|
||||||
|
|
||||||
For E2EE, vodozemac must be provided.
|
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) package.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
flutter pub add matrix
|
flutter pub add matrix
|
||||||
|
|
||||||
# Optional: For end to end encryption:
|
# Optional: For end to end encryption:
|
||||||
flutter pub add flutter_vodozemac
|
flutter pub add flutter_vodozemac
|
||||||
flutter pub add flutter_openssl_crypto
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Get started
|
## Get started
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,6 @@ For Flutter you can use [flutter_vodozemac](https://pub.dev/packages/flutter_vod
|
||||||
flutter pub add flutter_vodozemac
|
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:
|
Now before you create your `Client`, init vodozemac:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ In your `pubspec.yaml` file add the following dependencies:
|
||||||
# (Optional) For end to end encryption, please head on the
|
# (Optional) For end to end encryption, please head on the
|
||||||
# encryption guide and add these dependencies:
|
# encryption guide and add these dependencies:
|
||||||
flutter_vodozemac: <latest-version>
|
flutter_vodozemac: <latest-version>
|
||||||
flutter_openssl_crypto: <latest-version>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Step 2: Create the client
|
## Step 2: Create the client
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:base58check/base58.dart';
|
import 'package:base58check/base58.dart';
|
||||||
import 'package:collection/collection.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/encryption.dart';
|
||||||
import 'package:matrix/encryption/utils/base64_unpadded.dart';
|
import 'package:matrix/encryption/utils/base64_unpadded.dart';
|
||||||
|
|
@ -74,16 +74,18 @@ class SSSS {
|
||||||
|
|
||||||
static DerivedKeys deriveKeys(Uint8List key, String name) {
|
static DerivedKeys deriveKeys(Uint8List key, String name) {
|
||||||
final zerosalt = Uint8List(8);
|
final zerosalt = Uint8List(8);
|
||||||
final prk = Hmac(sha256, zerosalt).convert(key);
|
final prk = CryptoUtils.hmac(key: zerosalt, input: key);
|
||||||
final b = Uint8List(1);
|
final b = Uint8List(1);
|
||||||
b[0] = 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;
|
b[0] = 2;
|
||||||
final hmacKey =
|
final hmacKey = CryptoUtils.hmac(
|
||||||
Hmac(sha256, prk.bytes).convert(aesKey.bytes + utf8.encode(name) + b);
|
key: prk,
|
||||||
|
input: aesKey + utf8.encode(name) + b,
|
||||||
|
);
|
||||||
return DerivedKeys(
|
return DerivedKeys(
|
||||||
aesKey: Uint8List.fromList(aesKey.bytes),
|
aesKey: Uint8List.fromList(aesKey),
|
||||||
hmacKey: Uint8List.fromList(hmacKey.bytes),
|
hmacKey: Uint8List.fromList(hmacKey),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,14 +107,15 @@ class SSSS {
|
||||||
final keys = deriveKeys(key, name);
|
final keys = deriveKeys(key, name);
|
||||||
|
|
||||||
final plain = Uint8List.fromList(utf8.encode(data));
|
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(
|
return EncryptedContent(
|
||||||
iv: base64.encode(iv),
|
iv: base64.encode(iv),
|
||||||
ciphertext: base64.encode(ciphertext),
|
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 keys = deriveKeys(key, name);
|
||||||
final cipher = base64decodeUnpadded(data.ciphertext);
|
final cipher = base64decodeUnpadded(data.ciphertext);
|
||||||
final hmac = base64
|
final hmac = base64
|
||||||
.encode(Hmac(sha256, keys.hmacKey).convert(cipher).bytes)
|
.encode(CryptoUtils.hmac(key: keys.hmacKey, input: cipher))
|
||||||
.replaceAll(RegExp(r'=+$'), '');
|
.replaceAll(RegExp(r'=+$'), '');
|
||||||
if (hmac != data.mac.replaceAll(RegExp(r'=+$'), '')) {
|
if (hmac != data.mac.replaceAll(RegExp(r'=+$'), '')) {
|
||||||
throw Exception('Bad MAC');
|
throw Exception('Bad MAC');
|
||||||
}
|
}
|
||||||
final decipher = await uc.aesCtr
|
final decipher = CryptoUtils.aesCtr(
|
||||||
.encrypt(cipher, keys.aesKey, base64decodeUnpadded(data.iv));
|
input: cipher,
|
||||||
|
key: keys.aesKey,
|
||||||
|
iv: base64decodeUnpadded(data.iv),
|
||||||
|
);
|
||||||
return String.fromCharCodes(decipher);
|
return String.fromCharCodes(decipher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,12 +190,10 @@ class SSSS {
|
||||||
if (info.salt == null) {
|
if (info.salt == null) {
|
||||||
throw InvalidPassphraseException('Passphrase info without salt');
|
throw InvalidPassphraseException('Passphrase info without salt');
|
||||||
}
|
}
|
||||||
return await uc.pbkdf2(
|
return CryptoUtils.pbkdf2(
|
||||||
Uint8List.fromList(utf8.encode(passphrase)),
|
passphrase: Uint8List.fromList(utf8.encode(passphrase)),
|
||||||
Uint8List.fromList(utf8.encode(info.salt!)),
|
salt: Uint8List.fromList(utf8.encode(info.salt!)),
|
||||||
uc.sha512,
|
iterations: info.iterations!,
|
||||||
info.iterations!,
|
|
||||||
info.bits ?? 256,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -742,7 +746,7 @@ class OpenSSSS {
|
||||||
info: keyData.passphrase!,
|
info: keyData.passphrase!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).timeout(Duration(seconds: 10));
|
).timeout(Duration(minutes: 2));
|
||||||
} else if (recoveryKey != null) {
|
} else if (recoveryKey != null) {
|
||||||
privateKey = SSSS.decodeRecoveryKey(recoveryKey);
|
privateKey = SSSS.decodeRecoveryKey(recoveryKey);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:canonical_json/canonical_json.dart';
|
import 'package:canonical_json/canonical_json.dart';
|
||||||
import 'package:crypto/crypto.dart' as crypto;
|
|
||||||
import 'package:typed_data/typed_data.dart';
|
import 'package:typed_data/typed_data.dart';
|
||||||
import 'package:vodozemac/vodozemac.dart' as vod;
|
import 'package:vodozemac/vodozemac.dart' as vod;
|
||||||
|
|
||||||
|
|
@ -1559,8 +1558,8 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
|
||||||
Future<String> _makeCommitment(String pubKey, String canonicalJson) async {
|
Future<String> _makeCommitment(String pubKey, String canonicalJson) async {
|
||||||
if (hash == 'sha256') {
|
if (hash == 'sha256') {
|
||||||
final bytes = utf8.encoder.convert(pubKey + canonicalJson);
|
final bytes = utf8.encoder.convert(pubKey + canonicalJson);
|
||||||
final digest = crypto.sha256.convert(bytes);
|
final digest = vod.CryptoUtils.sha256(input: bytes);
|
||||||
return encodeBase64Unpadded(digest.bytes);
|
return encodeBase64Unpadded(digest);
|
||||||
}
|
}
|
||||||
throw Exception('Unknown hash method');
|
throw Exception('Unknown hash method');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,6 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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:math';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:vodozemac/vodozemac.dart';
|
||||||
|
|
||||||
import 'package:matrix/encryption/utils/base64_unpadded.dart';
|
import 'package:matrix/encryption/utils/base64_unpadded.dart';
|
||||||
import 'package:matrix/src/utils/crypto/crypto.dart';
|
import 'package:matrix/src/utils/crypto/crypto.dart';
|
||||||
|
|
||||||
|
|
@ -38,8 +40,8 @@ class EncryptedFile {
|
||||||
Future<EncryptedFile> encryptFile(Uint8List input) async {
|
Future<EncryptedFile> encryptFile(Uint8List input) async {
|
||||||
final key = secureRandomBytes(32);
|
final key = secureRandomBytes(32);
|
||||||
final iv = secureRandomBytes(16);
|
final iv = secureRandomBytes(16);
|
||||||
final data = await aesCtr.encrypt(input, key, iv);
|
final data = CryptoUtils.aesCtr(input: input, key: key, iv: iv);
|
||||||
final hash = await sha256(data);
|
final hash = CryptoUtils.sha256(input: data);
|
||||||
return EncryptedFile(
|
return EncryptedFile(
|
||||||
data: data,
|
data: data,
|
||||||
k: base64Url.encode(key).replaceAll('=', ''),
|
k: base64Url.encode(key).replaceAll('=', ''),
|
||||||
|
|
@ -51,12 +53,12 @@ Future<EncryptedFile> encryptFile(Uint8List input) async {
|
||||||
/// you would likely want to use [NativeImplementations] and
|
/// you would likely want to use [NativeImplementations] and
|
||||||
/// [Client.nativeImplementations] instead
|
/// [Client.nativeImplementations] instead
|
||||||
Future<Uint8List?> decryptFileImplementation(EncryptedFile input) async {
|
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)) {
|
base64.normalize(input.sha256)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final key = base64decodeUnpadded(base64.normalize(input.k));
|
final key = base64decodeUnpadded(base64.normalize(input.k));
|
||||||
final iv = base64decodeUnpadded(base64.normalize(input.iv));
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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');
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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));
|
|
||||||
}
|
|
||||||
|
|
@ -155,7 +155,10 @@ class NativeImplementationsIsolate extends NativeImplementations {
|
||||||
bool retryInDummy = true,
|
bool retryInDummy = true,
|
||||||
}) {
|
}) {
|
||||||
return runInBackground<Uint8List?, EncryptedFile>(
|
return runInBackground<Uint8List?, EncryptedFile>(
|
||||||
NativeImplementations.dummy.decryptFile,
|
(EncryptedFile args) async {
|
||||||
|
await vodozemacInit?.call();
|
||||||
|
return NativeImplementations.dummy.decryptFile(args);
|
||||||
|
},
|
||||||
file,
|
file,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -180,7 +183,10 @@ class NativeImplementationsIsolate extends NativeImplementations {
|
||||||
bool retryInDummy = true,
|
bool retryInDummy = true,
|
||||||
}) {
|
}) {
|
||||||
return runInBackground<Uint8List, KeyFromPassphraseArgs>(
|
return runInBackground<Uint8List, KeyFromPassphraseArgs>(
|
||||||
NativeImplementations.dummy.keyFromPassphrase,
|
(KeyFromPassphraseArgs args) async {
|
||||||
|
await vodozemacInit?.call();
|
||||||
|
return NativeImplementations.dummy.keyFromPassphrase(args);
|
||||||
|
},
|
||||||
args,
|
args,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,10 @@ dependencies:
|
||||||
blurhash_dart: ^1.1.0
|
blurhash_dart: ^1.1.0
|
||||||
canonical_json: ^1.1.0
|
canonical_json: ^1.1.0
|
||||||
collection: ^1.15.0
|
collection: ^1.15.0
|
||||||
crypto: ^3.0.0
|
|
||||||
enhanced_enum: ^0.2.4
|
|
||||||
ffi: ^2.0.0
|
|
||||||
html: ^0.15.0
|
html: ^0.15.0
|
||||||
html_unescape: ^2.0.0
|
html_unescape: ^2.0.0
|
||||||
http: ">=0.13.0 <2.0.0"
|
http: ">=0.13.0 <2.0.0"
|
||||||
image: ^4.0.15
|
image: ^4.0.15
|
||||||
js: ^0.6.3
|
|
||||||
js_interop: ^0.0.1
|
js_interop: ^0.0.1
|
||||||
markdown: ^7.1.1
|
markdown: ^7.1.1
|
||||||
mime: ">=1.0.0 <3.0.0"
|
mime: ">=1.0.0 <3.0.0"
|
||||||
|
|
@ -32,7 +28,7 @@ dependencies:
|
||||||
sqflite_common: ^2.4.5
|
sqflite_common: ^2.4.5
|
||||||
sqlite3: ^2.1.0
|
sqlite3: ^2.1.0
|
||||||
typed_data: ^1.3.2
|
typed_data: ^1.3.2
|
||||||
vodozemac: ^0.2.0
|
vodozemac: ^0.3.0
|
||||||
web: ^1.1.1
|
web: ^1.1.1
|
||||||
webrtc_interface: ^1.2.0
|
webrtc_interface: ^1.2.0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
rm -rf rust
|
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 ./
|
mv ./dart-vodozemac/rust ./
|
||||||
rm -rf dart-vodozemac
|
rm -rf dart-vodozemac
|
||||||
cd ./rust
|
cd ./rust
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,10 @@ class MockSSSS extends SSSS {
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('SSSS', tags: 'olm', () {
|
group(
|
||||||
|
'SSSS',
|
||||||
|
tags: 'olm',
|
||||||
|
() {
|
||||||
Logs().level = Level.error;
|
Logs().level = Level.error;
|
||||||
|
|
||||||
late Client client;
|
late Client client;
|
||||||
|
|
@ -336,7 +339,8 @@ void main() {
|
||||||
sender: client.userID!,
|
sender: client.userID!,
|
||||||
type: 'm.secret.send',
|
type: 'm.secret.send',
|
||||||
content: {
|
content: {
|
||||||
'request_id': client.encryption!.ssss.pendingShareRequests.keys.first,
|
'request_id':
|
||||||
|
client.encryption!.ssss.pendingShareRequests.keys.first,
|
||||||
'secret': 'foxies!',
|
'secret': 'foxies!',
|
||||||
},
|
},
|
||||||
encryptedContent: {
|
encryptedContent: {
|
||||||
|
|
@ -344,7 +348,10 @@ void main() {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await client.encryption!.ssss.handleToDeviceEvent(event);
|
await client.encryption!.ssss.handleToDeviceEvent(event);
|
||||||
expect(await client.encryption!.ssss.getCached('best animal'), 'foxies!');
|
expect(
|
||||||
|
await client.encryption!.ssss.getCached('best animal'),
|
||||||
|
'foxies!',
|
||||||
|
);
|
||||||
|
|
||||||
// test the different validators
|
// test the different validators
|
||||||
for (final type in [
|
for (final type in [
|
||||||
|
|
@ -382,7 +389,8 @@ void main() {
|
||||||
sender: client.userID!,
|
sender: client.userID!,
|
||||||
type: 'm.secret.send',
|
type: 'm.secret.send',
|
||||||
content: {
|
content: {
|
||||||
'request_id': client.encryption!.ssss.pendingShareRequests.keys.first,
|
'request_id':
|
||||||
|
client.encryption!.ssss.pendingShareRequests.keys.first,
|
||||||
'secret': 'foxies!',
|
'secret': 'foxies!',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -415,7 +423,8 @@ void main() {
|
||||||
sender: client.userID!,
|
sender: client.userID!,
|
||||||
type: 'm.secret.send',
|
type: 'm.secret.send',
|
||||||
content: {
|
content: {
|
||||||
'request_id': client.encryption!.ssss.pendingShareRequests.keys.first,
|
'request_id':
|
||||||
|
client.encryption!.ssss.pendingShareRequests.keys.first,
|
||||||
'secret': 'foxies!',
|
'secret': 'foxies!',
|
||||||
},
|
},
|
||||||
encryptedContent: {
|
encryptedContent: {
|
||||||
|
|
@ -433,7 +442,8 @@ void main() {
|
||||||
sender: client.userID!,
|
sender: client.userID!,
|
||||||
type: 'm.secret.send',
|
type: 'm.secret.send',
|
||||||
content: {
|
content: {
|
||||||
'request_id': client.encryption!.ssss.pendingShareRequests.keys.first,
|
'request_id':
|
||||||
|
client.encryption!.ssss.pendingShareRequests.keys.first,
|
||||||
'secret': 42,
|
'secret': 42,
|
||||||
},
|
},
|
||||||
encryptedContent: {
|
encryptedContent: {
|
||||||
|
|
@ -451,7 +461,8 @@ void main() {
|
||||||
sender: client.userID!,
|
sender: client.userID!,
|
||||||
type: 'm.secret.send',
|
type: 'm.secret.send',
|
||||||
content: {
|
content: {
|
||||||
'request_id': client.encryption!.ssss.pendingShareRequests.keys.first,
|
'request_id':
|
||||||
|
client.encryption!.ssss.pendingShareRequests.keys.first,
|
||||||
'secret': 'foxies!',
|
'secret': 'foxies!',
|
||||||
},
|
},
|
||||||
encryptedContent: {
|
encryptedContent: {
|
||||||
|
|
@ -476,7 +487,8 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('periodicallyRequestMissingCache', () async {
|
test('periodicallyRequestMissingCache', () async {
|
||||||
client.userDeviceKeys[client.userID!]!.masterKey!.setDirectVerified(true);
|
client.userDeviceKeys[client.userID!]!.masterKey!
|
||||||
|
.setDirectVerified(true);
|
||||||
client.encryption!.ssss = MockSSSS(client.encryption!);
|
client.encryption!.ssss = MockSSSS(client.encryption!);
|
||||||
(client.encryption!.ssss as MockSSSS).requestedSecrets = false;
|
(client.encryption!.ssss as MockSSSS).requestedSecrets = false;
|
||||||
await client.encryption!.ssss.periodicallyRequestMissingCache();
|
await client.encryption!.ssss.periodicallyRequestMissingCache();
|
||||||
|
|
@ -505,5 +517,7 @@ void main() {
|
||||||
test('dispose client', () async {
|
test('dispose client', () async {
|
||||||
await client.dispose(closeDatabase: true);
|
await client.dispose(closeDatabase: true);
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
timeout: Timeout(const Duration(minutes: 2)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,14 @@ import 'package:http/http.dart' as http;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
import 'fake_client.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
/// All Tests related to device keys
|
/// All Tests related to device keys
|
||||||
group('Matrix File', tags: 'olm', () {
|
group('Matrix File', tags: 'olm', () {
|
||||||
|
setUpAll(() async {
|
||||||
|
await getClient(); // To trigger vodozemac init
|
||||||
|
});
|
||||||
Logs().level = Level.error;
|
Logs().level = Level.error;
|
||||||
test('Decrypt', () async {
|
test('Decrypt', () async {
|
||||||
final text = 'hello world';
|
final text = 'hello world';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue