diff --git a/lib/encryption/ssss.dart b/lib/encryption/ssss.dart index ec9897c6..8828b6b2 100644 --- a/lib/encryption/ssss.dart +++ b/lib/encryption/ssss.dart @@ -23,7 +23,6 @@ import 'dart:async'; import 'package:base58check/base58.dart'; import 'package:crypto/crypto.dart'; -import 'package:encrypt/encrypt.dart'; import '../famedlysdk.dart'; import '../src/database/database.dart'; @@ -78,13 +77,13 @@ class SSSS { return _DerivedKeys(aesKey: aesKey.bytes, hmacKey: hmacKey.bytes); } - static _Encrypted encryptAes(String data, Uint8List key, String name, - [String ivStr]) { + static Future<_Encrypted> encryptAes(String data, Uint8List key, String name, + [String ivStr]) async { Uint8List iv; if (ivStr != null) { iv = base64.decode(ivStr); } else { - iv = Uint8List.fromList(SecureRandom(16).bytes); + iv = Uint8List.fromList(uc.secureRandomBytes(16)); } // we need to clear bit 63 of the IV iv[8] &= 0x7f; @@ -92,9 +91,7 @@ class SSSS { final keys = deriveKeys(key, name); final plain = Uint8List.fromList(utf8.encode(data)); - final ciphertext = AES(Key(keys.aesKey), mode: AESMode.ctr, padding: null) - .encrypt(plain, iv: IV(iv)) - .bytes; + final ciphertext = await uc.aesCtr.encrypt(plain, keys.aesKey, iv); final hmac = Hmac(sha256, keys.hmacKey).convert(ciphertext); @@ -104,7 +101,7 @@ class SSSS { mac: base64.encode(hmac.bytes)); } - static String decryptAes(_Encrypted data, Uint8List key, String name) { + static Future decryptAes(_Encrypted data, Uint8List key, String name) async { final keys = deriveKeys(key, name); final cipher = base64.decode(data.ciphertext); final hmac = base64 @@ -113,8 +110,7 @@ class SSSS { if (hmac != data.mac.replaceAll(RegExp(r'=+$'), '')) { throw Exception('Bad MAC'); } - final decipher = AES(Key(keys.aesKey), mode: AESMode.ctr, padding: null) - .decrypt(Encrypted(cipher), iv: IV(base64.decode(data.iv))); + final decipher = await uc.aesCtr.encrypt(cipher, keys.aesKey, base64.decode(data.iv)); return String.fromCharCodes(decipher); } @@ -196,9 +192,9 @@ class SSSS { // we need to derive the key off of the passphrase content.passphrase = PassphraseInfo(); content.passphrase.algorithm = AlgorithmTypes.pbkdf2; - content.passphrase.salt = - base64.encode(SecureRandom(pbkdf2SaltLength).bytes); // generate salt - content.passphrase.iterations = pbkdf2DefaultIterations; + content.passphrase.salt = base64 + .encode(uc.secureRandomBytes(pbkdf2SaltLength)); // generate salt + content.passphrase.iterations = pbkdf2DefaultIterations;; content.passphrase.bits = ssssKeyLength * 8; privateKey = await runInBackground( _keyFromPassphrase, @@ -210,10 +206,10 @@ class SSSS { ); } else { // we need to just generate a new key from scratch - privateKey = Uint8List.fromList(SecureRandom(ssssKeyLength).bytes); + privateKey = Uint8List.fromList(uc.secureRandomBytes(ssssKeyLength)); } // now that we have the private key, let's create the iv and mac - final encrypted = encryptAes(zeroStr, privateKey, ''); + final encrypted = await encryptAes(zeroStr, privateKey, ''); content.iv = encrypted.iv; content.mac = encrypted.mac; content.algorithm = AlgorithmTypes.secretStorageV1AesHmcSha2; @@ -223,7 +219,7 @@ class SSSS { // make sure we generate a unique key id final keyId = () sync* { for (;;) { - yield base64.encode(SecureRandom(keyidByteLength).bytes); + yield base64.encode(uc.secureRandomBytes(keyidByteLength)); } }() .firstWhere((keyId) => getKey(keyId) == null); @@ -238,14 +234,14 @@ class SSSS { await waitForAccountData; final key = open(keyId); - key.setPrivateKey(privateKey); + await key.setPrivateKey(privateKey); return key; } - bool checkKey(Uint8List key, SecretStorageKeyContent info) { + Future checkKey(Uint8List key, SecretStorageKeyContent info) async { if (info.algorithm == AlgorithmTypes.secretStorageV1AesHmcSha2) { if ((info.mac is String) && (info.iv is String)) { - final encrypted = encryptAes(zeroStr, key, '', info.iv); + final encrypted = await encryptAes(zeroStr, key, '', info.iv); return info.mac.replaceAll(RegExp(r'=+$'), '') == encrypted.mac.replaceAll(RegExp(r'=+$'), ''); } else { @@ -303,7 +299,7 @@ class SSSS { final enc = secretInfo.content['encrypted'][keyId]; final encryptInfo = _Encrypted( iv: enc['iv'], ciphertext: enc['ciphertext'], mac: enc['mac']); - final decrypted = decryptAes(encryptInfo, key, type); + final decrypted = await decryptAes(encryptInfo, key, type); if (cacheTypes.contains(type) && client.database != null) { // cache the thing await client.database @@ -319,7 +315,7 @@ class SSSS { {bool add = false}) async { final triggerCacheCallback = _cacheCallbacks.containsKey(type) && await getCached(type) == null; - final encrypted = encryptAes(secret, key, type); + final encrypted = await encryptAes(secret, key, type); Map content; if (add && client.accountData[type] != null) { content = client.accountData[type].content.copy(); @@ -649,7 +645,7 @@ class OpenSSSS { throw Exception('Nothing specified'); } // verify the validity of the key - if (!ssss.checkKey(privateKey, keyData)) { + if (!await ssss.checkKey(privateKey, keyData)) { privateKey = null; throw Exception('Inalid key'); } @@ -658,8 +654,8 @@ class OpenSSSS { } } - void setPrivateKey(Uint8List key) { - if (!ssss.checkKey(key, keyData)) { + Future setPrivateKey(Uint8List key) async { + if (!await ssss.checkKey(key, keyData)) { throw Exception('Invalid key'); } privateKey = key; diff --git a/lib/src/utils/crypto/crypto.dart b/lib/src/utils/crypto/crypto.dart index 5e16ecab..fc4d1c97 100644 --- a/lib/src/utils/crypto/crypto.dart +++ b/lib/src/utils/crypto/crypto.dart @@ -1 +1,11 @@ export 'native.dart' if (dart.library.js) 'js.dart'; + +import 'dart:typed_data'; +import 'dart:math'; + +Uint8List secureRandomBytes(int len) { + final rng = Random.secure(); + final list = Uint8List(len); + list.setAll(0, Iterable.generate(list.length, (i) => rng.nextInt(256))); + return list; +} diff --git a/lib/src/utils/crypto/ffi.dart b/lib/src/utils/crypto/ffi.dart index 3e58f691..1a6aec8d 100644 --- a/lib/src/utils/crypto/ffi.dart +++ b/lib/src/utils/crypto/ffi.dart @@ -28,3 +28,38 @@ final EVP_sha512 = libcrypto.lookupFunction< Pointer Function(), Pointer Function() >('EVP_sha512'); + +final EVP_aes_128_ctr = libcrypto.lookupFunction< + Pointer Function(), + Pointer Function() +>('EVP_aes_128_ctr'); + +final EVP_aes_256_ctr = libcrypto.lookupFunction< + Pointer 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'); diff --git a/lib/src/utils/crypto/js.dart b/lib/src/utils/crypto/js.dart index 5db00f68..39606bc7 100644 --- a/lib/src/utils/crypto/js.dart +++ b/lib/src/utils/crypto/js.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; import 'subtle.dart'; +import 'subtle.dart' as subtle; abstract class Hash { Hash._(this.name); @@ -26,6 +27,26 @@ 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); diff --git a/lib/src/utils/crypto/native.dart b/lib/src/utils/crypto/native.dart index c63f8026..66c9af0f 100644 --- a/lib/src/utils/crypto/native.dart +++ b/lib/src/utils/crypto/native.dart @@ -25,6 +25,47 @@ class _Sha512 extends Hash { _Sha512() : super._(EVP_sha512()); } +abstract class Cipher { + Cipher._(); + Pointer getAlg(int keysize); + Uint8List 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'); + } + } +} + Uint8List pbkdf2(Uint8List passphrase, Uint8List salt, Hash hash, int iterations, int bits) { final outLen = bits ~/ 8; final mem = malloc.call(passphrase.length + salt.length + outLen); diff --git a/lib/src/utils/crypto/subtle.dart b/lib/src/utils/crypto/subtle.dart index 814847e0..d5b84b24 100644 --- a/lib/src/utils/crypto/subtle.dart +++ b/lib/src/utils/crypto/subtle.dart @@ -22,6 +22,29 @@ class Pbkdf2Params { 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, CryptoKey key, Uint8List data); + +Future encrypt(dynamic algorithm, CryptoKey key, Uint8List data) { + return promiseToFuture(_encrypt(algorithm, key, data)); +} + +@JS('crypto.subtle.decrypt') +external dynamic _decrypt(dynamic algorithm, CryptoKey key, Uint8List data); + +Future decrypt(dynamic algorithm, CryptoKey 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); diff --git a/pubspec.yaml b/pubspec.yaml index e22894c3..4d6d081a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,6 @@ dependencies: html_unescape: ^1.0.2 moor: ^4.0.0 random_string: ^2.1.0 - encrypt: ^5.0.0-beta.1 crypto: ^3.0.0 base58check: ^2.0.0 olm: ^2.0.0 @@ -26,7 +25,6 @@ dependencies: isolate: ^2.0.3 logger: ^1.0.0 matrix_api_lite: ^0.2.4 - pointycastle: ^3.0.0-nullsafety.2 dev_dependencies: test: ^1.15.7 diff --git a/test/encryption/ssss_test.dart b/test/encryption/ssss_test.dart index ee7ea000..83826236 100644 --- a/test/encryption/ssss_test.dart +++ b/test/encryption/ssss_test.dart @@ -18,17 +18,24 @@ import 'dart:typed_data'; import 'dart:convert'; +import 'dart:math'; import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/encryption.dart'; import 'package:logger/logger.dart'; import 'package:test/test.dart'; -import 'package:encrypt/encrypt.dart'; import 'package:olm/olm.dart' as olm; import '../fake_client.dart'; import '../fake_matrix_api.dart'; +Uint8List secureRandomBytes(int len) { + final rng = Random.secure(); + final list = Uint8List(len); + list.setAll(0, Iterable.generate(list.length, (i) => rng.nextInt(256))); + return list; +} + class MockSSSS extends SSSS { MockSSSS(Encryption encryption) : super(encryption); @@ -69,12 +76,12 @@ void main() { '0FajDWYaM6wQ4O60OZnLvwZfsBNu4Bu3'); }); - test('encrypt / decrypt', () { + test('encrypt / decrypt', () async { if (!olmEnabled) return; - final key = Uint8List.fromList(SecureRandom(32).bytes); + final key = Uint8List.fromList(secureRandomBytes(32)); - final enc = SSSS.encryptAes('secret foxies', key, 'name'); - final dec = SSSS.decryptAes(enc, key, 'name'); + final enc = await SSSS.encryptAes('secret foxies', key, 'name'); + final dec = await SSSS.decryptAes(enc, key, 'name'); expect(dec, 'secret foxies'); }); @@ -117,7 +124,7 @@ void main() { test('encode / decode recovery key', () async { if (!olmEnabled) return; - final key = Uint8List.fromList(SecureRandom(32).bytes); + final key = Uint8List.fromList(secureRandomBytes(32)); final encoded = SSSS.encodeRecoveryKey(key); final decoded = SSSS.decodeRecoveryKey(encoded); expect(key, decoded); @@ -472,13 +479,13 @@ void main() { expect(client.encryption.ssss.isKeyValid(newKey.keyId), true); var testKey = client.encryption.ssss.open(newKey.keyId); await testKey.unlock(passphrase: 'test'); - testKey.setPrivateKey(newKey.privateKey); + await testKey.setPrivateKey(newKey.privateKey); // without passphrase newKey = await client.encryption.ssss.createKey(); expect(client.encryption.ssss.isKeyValid(newKey.keyId), true); testKey = client.encryption.ssss.open(newKey.keyId); - testKey.setPrivateKey(newKey.privateKey); + await testKey.setPrivateKey(newKey.privateKey); }); test('dispose client', () async {