From a25d1932ee9d45967dff699f135e208f9a07a3fc Mon Sep 17 00:00:00 2001 From: Lukas Lihotzki Date: Tue, 23 Mar 2021 18:08:40 +0100 Subject: [PATCH] fix: fast pbkdf2 with OpenSSL --- .gitlab-ci.yml | 4 +++ README.md | 1 + lib/encryption/ssss.dart | 18 +++-------- lib/src/utils/crypto/crypto.dart | 1 + lib/src/utils/crypto/ffi.dart | 20 ++++++++++++ lib/src/utils/crypto/js.dart | 12 +++++++ lib/src/utils/crypto/native.dart | 20 ++++++++++++ lib/src/utils/crypto/subtle.dart | 54 ++++++++++++++++++++++++++++++++ 8 files changed, 117 insertions(+), 13 deletions(-) create mode 100644 lib/src/utils/crypto/crypto.dart create mode 100644 lib/src/utils/crypto/ffi.dart create mode 100644 lib/src/utils/crypto/js.dart create mode 100644 lib/src/utils/crypto/native.dart create mode 100644 lib/src/utils/crypto/subtle.dart diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9e5646f9..2d840d3e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -50,6 +50,10 @@ coverage_without_olm: - apt update - apt install -y dart - ln -s /usr/lib/dart/bin/pub /usr/bin/ + # Need to check if famedlysdk works without OpenSSL. + # Cannot uninstall OpenSSL, because it may be used internally. + # Break famedlysdk's FFI to OpenSSL instead: + - sed -i 's/libcrypto/libnocrypto/g' lib/src/utils/crypto/ffi.dart - pub get - pub run test diff --git a/README.md b/README.md index 0df77a48..1b8ae70e 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ Matrix SDK for the famedly talk app written in dart. ## Native libraries For E2EE, libolm must be provided (see https://pub.dev/packages/olm#using-dart-olm). +Additionally, OpenSSL (libcrypto) must be provided on native platforms for E2EE. ## API diff --git a/lib/encryption/ssss.dart b/lib/encryption/ssss.dart index 22247dac..2c2c0d95 100644 --- a/lib/encryption/ssss.dart +++ b/lib/encryption/ssss.dart @@ -24,13 +24,10 @@ import 'dart:async'; import 'package:base58check/base58.dart'; import 'package:crypto/crypto.dart'; import 'package:encrypt/encrypt.dart'; -import 'package:pointycastle/digests/sha512.dart'; -import 'package:pointycastle/key_derivators/api.dart'; -import 'package:pointycastle/key_derivators/pbkdf2.dart'; -import 'package:pointycastle/macs/hmac.dart'; import '../famedlysdk.dart'; import '../src/database/database.dart'; +import '../src/utils/crypto/crypto.dart'; import '../src/utils/run_in_background.dart'; import '../src/utils/run_in_root.dart'; import 'encryption.dart'; @@ -154,16 +151,11 @@ class SSSS { .trim(); } - static Uint8List keyFromPassphrase(String passphrase, PassphraseInfo info) { + static Future keyFromPassphrase(String passphrase, PassphraseInfo info) async { if (info.algorithm != AlgorithmTypes.pbkdf2) { throw Exception('Unknown algorithm'); } - final out = Uint8List(info.bits != null ? (info.bits / 8).ceil() : 32); - final generator = PBKDF2KeyDerivator(HMac(SHA512Digest(), 128)); - generator.init( - Pbkdf2Parameters(utf8.encode(info.salt), info.iterations, out.length)); - generator.deriveKey(utf8.encode(passphrase), 0, out, 0); - return out; + return await pbkdf2(utf8.encode(passphrase), utf8.encode(info.salt), info.iterations, info.bits ?? 256); } void setValidator(String type, FutureOr Function(String) validator) { @@ -717,6 +709,6 @@ class _KeyFromPassphraseArgs { _KeyFromPassphraseArgs({this.passphrase, this.info}); } -Uint8List _keyFromPassphrase(_KeyFromPassphraseArgs args) { - return SSSS.keyFromPassphrase(args.passphrase, args.info); +Future _keyFromPassphrase(_KeyFromPassphraseArgs args) async { + return await SSSS.keyFromPassphrase(args.passphrase, args.info); } diff --git a/lib/src/utils/crypto/crypto.dart b/lib/src/utils/crypto/crypto.dart new file mode 100644 index 00000000..5e16ecab --- /dev/null +++ b/lib/src/utils/crypto/crypto.dart @@ -0,0 +1 @@ +export 'native.dart' if (dart.library.js) 'js.dart'; diff --git a/lib/src/utils/crypto/ffi.dart b/lib/src/utils/crypto/ffi.dart new file mode 100644 index 00000000..49508df8 --- /dev/null +++ b/lib/src/utils/crypto/ffi.dart @@ -0,0 +1,20 @@ +import 'dart:ffi'; +import 'dart:io'; + +final libcrypto = Platform.isIOS + ? DynamicLibrary.process() + : DynamicLibrary.open(Platform.isAndroid + ? 'libcrypto.so' + : Platform.isWindows + ? 'libcrypto.dll' + : Platform.isMacOS ? 'libcrypto.1.1.dylib' : '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_sha512 = libcrypto.lookupFunction< + Pointer Function(), + Pointer Function() +>('EVP_sha512'); diff --git a/lib/src/utils/crypto/js.dart b/lib/src/utils/crypto/js.dart new file mode 100644 index 00000000..631ccc7e --- /dev/null +++ b/lib/src/utils/crypto/js.dart @@ -0,0 +1,12 @@ +// Copyright (c) 2020 Famedly GmbH +// SPDX-License-Identifier: AGPL-3.0-or-later + +import 'dart:typed_data'; + +import 'subtle.dart'; + +Future pbkdf2(Uint8List passphrase, Uint8List salt, int iterations, int bits) async { + final raw = await importKey('raw', passphrase, 'PBKDF2', false, ['deriveBits']); + final res = await deriveBits(Pbkdf2Params(name: 'PBKDF2', hash: 'SHA-512', 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 new file mode 100644 index 00000000..82940b24 --- /dev/null +++ b/lib/src/utils/crypto/native.dart @@ -0,0 +1,20 @@ +import 'dart:typed_data'; +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +import 'ffi.dart'; + +Uint8List pbkdf2(Uint8List passphrase, Uint8List salt, 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, EVP_sha512(), 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 new file mode 100644 index 00000000..814847e0 --- /dev/null +++ b/lib/src/utils/crypto/subtle.dart @@ -0,0 +1,54 @@ +// Copyright (c) 2020 Famedly GmbH +// SPDX-License-Identifier: AGPL-3.0-or-later + +@JS() +library subtle; + +import 'package:js/js.dart'; +import 'dart:async'; +import 'dart:js_util'; +import 'dart:typed_data'; + +@JS() +class CryptoKey {} + +@JS() +@anonymous +class Pbkdf2Params { + external factory Pbkdf2Params({String name, String hash, Uint8List salt, int iterations}); + String name; + String hash; + Uint8List salt; + int iterations; +} + +@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, CryptoKey key); + +Future exportKey(String algorithm, CryptoKey key) { + return promiseToFuture(_exportKey(algorithm, key)); +} + +@JS('crypto.subtle.deriveKey') +external dynamic _deriveKey(dynamic algorithm, CryptoKey baseKey, dynamic derivedKeyAlgorithm, bool extractable, List keyUsages); + +Future deriveKey(dynamic algorithm, CryptoKey baseKey, dynamic derivedKeyAlgorithm, bool extractable, List keyUsages) { + return promiseToFuture(_deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages)); +} + +@JS('crypto.subtle.deriveBits') +external dynamic _deriveBits(dynamic algorithm, CryptoKey baseKey, int length); + +Future deriveBits(dynamic algorithm, CryptoKey baseKey, int length) { + return promiseToFuture(_deriveBits(algorithm, baseKey, length)); +}