refactor: use OpenSSL for AES

This commit is contained in:
Lukas Lihotzki 2021-03-24 16:55:12 +01:00
parent 7faf05fe90
commit 761138a56d
8 changed files with 165 additions and 34 deletions

View File

@ -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<String> 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<bool> 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<String, dynamic> 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<void> setPrivateKey(Uint8List key) async {
if (!await ssss.checkKey(key, keyData)) {
throw Exception('Invalid key');
}
privateKey = key;

View File

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

View File

@ -28,3 +28,38 @@ 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');

View File

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

View File

@ -25,6 +25,47 @@ class _Sha512 extends Hash {
_Sha512() : super._(EVP_sha512());
}
abstract class Cipher {
Cipher._();
Pointer<NativeType> getAlg(int keysize);
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');
}
}
}
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);

View File

@ -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<ByteBuffer> 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<ByteBuffer> 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<String> keyUsages);

View File

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

View File

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