Browse Source

make a few methods async to move argon2 into separate isolate.

remove-cryptography-dependency
Herbert Poul 5 years ago
parent
commit
8041b1e83d
  1. 2
      bin/kdbx.dart
  2. 1
      lib/kdbx.dart
  3. 67
      lib/src/crypto/argon2.dart
  4. 11
      lib/src/crypto/key_encrypter_kdf.dart
  5. 26
      lib/src/kdbx_format.dart
  6. 41
      test/kdbx4_test.dart
  7. 14
      test/kdbx_test.dart

2
bin/kdbx.dart

@ -74,7 +74,7 @@ abstract class KdbxFileCommand extends Command<void> {
final bytes = await File(inputFile).readAsBytes();
final password = prompts.get('Password for $inputFile',
conceal: true, validate: (str) => str.isNotEmpty);
final file = KdbxFormat(null)
final file = await KdbxFormat(null)
.read(bytes, Credentials(ProtectedValue.fromString(password)));
return runWithFile(file);
}

1
lib/kdbx.dart

@ -2,7 +2,6 @@
library kdbx;
export 'src/crypto/argon2.dart';
export 'src/crypto/key_encrypter_kdf.dart' show Argon2;
export 'src/crypto/protected_value.dart'
show ProtectedValue, StringValue, PlainValue;
export 'src/kdbx_consts.dart';

67
lib/src/crypto/argon2.dart

@ -6,6 +6,8 @@ import 'package:ffi/ffi.dart';
import 'package:ffi_helper/ffi_helper.dart';
import 'package:meta/meta.dart';
// ignore_for_file: non_constant_identifier_names
typedef Argon2HashNative = Pointer<Utf8> Function(
Pointer<Uint8> key,
IntPtr keyLen,
@ -33,16 +35,23 @@ typedef Argon2Hash = Pointer<Utf8> Function(
);
abstract class Argon2 {
Uint8List argon2(
Uint8List key,
Uint8List salt,
int memory,
int iterations,
int length,
int parallelism,
int type,
int version,
);
Uint8List argon2(Argon2Arguments args);
Future<Uint8List> argon2Async(Argon2Arguments args);
}
class Argon2Arguments {
Argon2Arguments(this.key, this.salt, this.memory, this.iterations,
this.length, this.parallelism, this.type, this.version);
final Uint8List key;
final Uint8List salt;
final int memory;
final int iterations;
final int length;
final int parallelism;
final int type;
final int version;
}
abstract class Argon2Base extends Argon2 {
@ -50,21 +59,12 @@ abstract class Argon2Base extends Argon2 {
Argon2Hash get argon2hash;
@override
Uint8List argon2(
Uint8List key,
Uint8List salt,
int memory,
int iterations,
int length,
int parallelism,
int type,
int version,
) {
final keyArray = Uint8Array.fromTypedList(key);
Uint8List argon2(Argon2Arguments args) {
final keyArray = Uint8Array.fromTypedList(args.key);
// final saltArray = Uint8Array.fromTypedList(salt);
final saltArray = allocate<Uint8>(count: salt.length);
final saltList = saltArray.asTypedList(length);
saltList.setAll(0, salt);
final saltArray = allocate<Uint8>(count: args.salt.length);
final saltList = saltArray.asTypedList(args.length);
saltList.setAll(0, args.salt);
// const memoryCost = 1 << 16;
// _logger.fine('saltArray: ${ByteUtils.toHexList(saltArray.view)}');
@ -73,13 +73,13 @@ abstract class Argon2Base extends Argon2 {
keyArray.rawPtr,
keyArray.length,
saltArray,
salt.length,
memory,
iterations,
parallelism,
length,
type,
version,
args.salt.length,
args.memory,
args.iterations,
args.parallelism,
args.length,
args.type,
args.version,
);
keyArray.free();
@ -88,4 +88,9 @@ abstract class Argon2Base extends Argon2 {
final resultString = Utf8.fromUtf8(result);
return base64.decode(resultString);
}
@override
Future<Uint8List> argon2Async(Argon2Arguments args) async {
return argon2(args);
}
}

11
lib/src/crypto/key_encrypter_kdf.dart

@ -78,7 +78,7 @@ class KeyEncrypterKdf {
final Argon2 argon2;
Uint8List encrypt(Uint8List key, VarDictionary kdfParameters) {
Future<Uint8List> encrypt(Uint8List key, VarDictionary kdfParameters) async {
final uuid = kdfParameters.get(ValueType.typeBytes, '\$UUID');
if (uuid == null) {
throw KdbxCorruptedFileException('No Kdf UUID');
@ -87,7 +87,7 @@ class KeyEncrypterKdf {
switch (kdfUuids[kdfUuid]) {
case KdfType.Argon2:
_logger.fine('Must be using argon2');
return encryptArgon2(key, kdfParameters);
return await encryptArgon2(key, kdfParameters);
break;
case KdfType.Aes:
_logger.fine('Must be using aes');
@ -97,8 +97,9 @@ class KeyEncrypterKdf {
'unsupported KDF Type UUID ${ByteUtils.toHexList(uuid)}.');
}
Uint8List encryptArgon2(Uint8List key, VarDictionary kdfParameters) {
return argon2.argon2(
Future<Uint8List> encryptArgon2(
Uint8List key, VarDictionary kdfParameters) async {
return await argon2.argon2Async(Argon2Arguments(
key,
KdfField.salt.read(kdfParameters),
// 65536, //KdfField.memory.read(kdfParameters),
@ -108,7 +109,7 @@ class KeyEncrypterKdf {
KdfField.parallelism.read(kdfParameters),
0,
KdfField.version.read(kdfParameters),
);
));
}
Uint8List encryptAes(Uint8List key, VarDictionary kdfParameters) {

26
lib/src/kdbx_format.dart

@ -149,7 +149,7 @@ class KdbxFile {
Stream<Set<KdbxObject>> get dirtyObjectsChanged =>
_dirtyObjectsChanged.stream;
Uint8List save() {
Future<Uint8List> save() async {
final output = BytesBuilder();
final writer = WriterHelper(output);
header.generateSalts();
@ -167,7 +167,7 @@ class KdbxFile {
final headerBytes = writer.output.toBytes();
writer.writeBytes(headerHash);
final gen = kdbxFormat._createProtectedSaltGenerator(header);
final keys = kdbxFormat._computeKeysV4(header, credentials);
final keys = await kdbxFormat._computeKeysV4(header, credentials);
final headerHmac = kdbxFormat._getHeaderHmac(headerBytes, keys.hmacKey);
writer.writeBytes(headerHmac.bytes as Uint8List);
body.writeV4(writer, this, gen, keys);
@ -365,13 +365,13 @@ class KdbxFormat {
);
}
KdbxFile read(Uint8List input, Credentials credentials) {
Future<KdbxFile> read(Uint8List input, Credentials credentials) async {
final reader = ReaderHelper(input);
final header = KdbxHeader.read(reader);
if (header.versionMajor == 3) {
return _loadV3(header, reader, credentials);
} else if (header.versionMajor == 4) {
return _loadV4(header, reader, credentials);
return await _loadV4(header, reader, credentials);
} else {
_logger.finer('Unsupported version for $header');
throw KdbxUnsupportedException('Unsupported kdbx version '
@ -399,20 +399,20 @@ class KdbxFormat {
}
}
KdbxFile _loadV4(
KdbxHeader header, ReaderHelper reader, Credentials credentials) {
Future<KdbxFile> _loadV4(
KdbxHeader header, ReaderHelper reader, Credentials credentials) async {
final headerBytes = reader.byteData.sublist(0, header.endPos);
final hash = crypto.sha256.convert(headerBytes).bytes;
final actualHash = reader.readBytes(hash.length);
if (!ByteUtils.eq(hash, actualHash)) {
_logger.fine(
'Does not match ${ByteUtils.toHexList(hash)} vs ${ByteUtils.toHexList(actualHash)}');
_logger.fine('Does not match ${ByteUtils.toHexList(hash)} '
'vs ${ByteUtils.toHexList(actualHash)}');
throw KdbxCorruptedFileException('Header hash does not match.');
}
// _logger
// .finest('KdfParameters: ${header.readKdfParameters.toDebugString()}');
_logger.finest('Header hash matches.');
final keys = _computeKeysV4(header, credentials);
final keys = await _computeKeysV4(header, credentials);
final headerHmac =
_getHeaderHmac(reader.byteData.sublist(0, header.endPos), keys.hmacKey);
final expectedHmac = reader.readBytes(headerHmac.bytes.length);
@ -435,7 +435,7 @@ class KdbxFormat {
final xml = utf8.decode(contentReader.readRemaining());
return KdbxFile(this, credentials, header, _loadXml(header, xml));
}
return null;
throw StateError('Kdbx4 without compression is not yet supported.');
}
Uint8List hmacBlockTransformerEncrypt(Uint8List hmacKey, Uint8List data) {
@ -539,7 +539,8 @@ class KdbxFormat {
return hmacKeyStuff.convert(src);
}
_KeysV4 _computeKeysV4(KdbxHeader header, Credentials credentials) {
Future<_KeysV4> _computeKeysV4(
KdbxHeader header, Credentials credentials) async {
final masterSeed = header.fields[HeaderFields.MasterSeed].bytes;
final kdfParameters = header.readKdfParameters;
if (masterSeed.length != 32) {
@ -547,7 +548,8 @@ class KdbxFormat {
}
final credentialHash = credentials.getHash();
final key = KeyEncrypterKdf(argon2).encrypt(credentialHash, kdfParameters);
final key =
await KeyEncrypterKdf(argon2).encrypt(credentialHash, kdfParameters);
// final keyWithSeed = Uint8List(65);
// keyWithSeed.replaceRange(0, masterSeed.length, masterSeed);

41
test/kdbx4_test.dart

@ -1,7 +1,5 @@
import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
import 'package:kdbx/kdbx.dart';
@ -60,21 +58,17 @@ void main() {
final kdbxFormat = KdbxFormat(Argon2Test());
group('Reading', () {
test('bubb', () async {
final key = utf8.encode('asdf') as Uint8List;
final salt = Uint8List(8);
// final result = Argon2Test().argon2(key, salt, 1 << 16, 5, 16, 1, 0x13, 1);
// _logger.fine('hashing: $result');
final data = await File('test/keepassxcpasswords.kdbx').readAsBytes();
final file =
kdbxFormat.read(data, Credentials(ProtectedValue.fromString('asdf')));
final file = await kdbxFormat.read(
data, Credentials(ProtectedValue.fromString('asdf')));
final firstEntry = file.body.rootGroup.entries.first;
final pwd = firstEntry.getString(KdbxKey('Password')).getText();
expect(pwd, 'MyPassword');
});
test('Reading kdbx4_keeweb', () async {
final data = await File('test/kdbx4_keeweb.kdbx').readAsBytes();
final file =
kdbxFormat.read(data, Credentials(ProtectedValue.fromString('asdf')));
final file = await kdbxFormat.read(
data, Credentials(ProtectedValue.fromString('asdf')));
final firstEntry = file.body.rootGroup.entries.first;
final pwd = firstEntry.getString(KdbxKey('Password')).getText();
expect(pwd, 'def');
@ -84,25 +78,25 @@ void main() {
await File('test/keyfile/BinaryKeyFilePasswords.kdbx').readAsBytes();
final keyFile =
await File('test/keyfile/binarykeyfile.key').readAsBytes();
final file = kdbxFormat.read(data,
final file = await kdbxFormat.read(data,
Credentials.composite(ProtectedValue.fromString('asdf'), keyFile));
expect(file.body.rootGroup.entries, hasLength(1));
});
test('Reading chacha20', () async {
final data = await File('test/chacha20.kdbx').readAsBytes();
final file =
kdbxFormat.read(data, Credentials(ProtectedValue.fromString('asdf')));
final file = await kdbxFormat.read(
data, Credentials(ProtectedValue.fromString('asdf')));
expect(file.body.rootGroup.entries, hasLength(1));
});
test('Reading aes-kdf', () async {
final data = await File('test/aeskdf.kdbx').readAsBytes();
final file =
kdbxFormat.read(data, Credentials(ProtectedValue.fromString('asdf')));
final file = await kdbxFormat.read(
data, Credentials(ProtectedValue.fromString('asdf')));
expect(file.body.rootGroup.entries, hasLength(1));
});
});
group('Writing', () {
test('Create and save', () {
test('Create and save', () async {
final credentials = Credentials(ProtectedValue.fromString('asdf'));
final kdbx = kdbxFormat.create(
credentials,
@ -112,27 +106,28 @@ void main() {
final rootGroup = kdbx.body.rootGroup;
_createEntry(kdbx, rootGroup, 'user1', 'LoremIpsum');
_createEntry(kdbx, rootGroup, 'user2', 'Second Password');
final saved = kdbx.save();
final saved = await kdbx.save();
final loadedKdbx = kdbxFormat.read(
final loadedKdbx = await kdbxFormat.read(
saved, Credentials(ProtectedValue.fromString('asdf')));
_logger.fine('Successfully loaded kdbx $loadedKdbx');
File('test_v4x.kdbx').writeAsBytesSync(saved);
});
test('Reading it', () async {
final data = await File('test/test_v4x.kdbx').readAsBytes();
final file =
kdbxFormat.read(data, Credentials(ProtectedValue.fromString('asdf')));
final file = await kdbxFormat.read(
data, Credentials(ProtectedValue.fromString('asdf')));
_logger.fine('successfully read ${file.body.rootGroup.name}');
});
test('write chacha20', () async {
final data = await File('test/chacha20.kdbx').readAsBytes();
final file =
kdbxFormat.read(data, Credentials(ProtectedValue.fromString('asdf')));
final file = await kdbxFormat.read(
data, Credentials(ProtectedValue.fromString('asdf')));
expect(file.body.rootGroup.entries, hasLength(1));
_createEntry(file, file.body.rootGroup, 'user1', 'LoremIpsum');
// and try to write it.
final output = file.save();
final output = await file.save();
expect(output, isNotNull);
File('test_output_chacha20.kdbx').writeAsBytesSync(output);
});

14
test/kdbx_test.dart

@ -1,4 +1,3 @@
import 'dart:ffi';
import 'dart:io';
import 'dart:typed_data';
@ -27,7 +26,8 @@ void main() {
test('First Test', () async {
final data = await File('test/FooBar.kdbx').readAsBytes();
kdbxForamt.read(data, Credentials(ProtectedValue.fromString('FooBar')));
await kdbxForamt.read(
data, Credentials(ProtectedValue.fromString('FooBar')));
});
});
@ -38,7 +38,7 @@ void main() {
final cred = Credentials.composite(
ProtectedValue.fromString('asdf'), keyFileBytes);
final data = await File('test/password-and-keyfile.kdbx').readAsBytes();
final file = kdbxForamt.read(data, cred);
final file = await kdbxForamt.read(data, cred);
expect(file.body.rootGroup.entries, hasLength(2));
});
test('Read with PW and hex keyfile', () async {
@ -47,7 +47,7 @@ void main() {
final cred = Credentials.composite(
ProtectedValue.fromString('testing99'), keyFileBytes);
final data = await File('test/keyfile/newdatabase2.kdbx').readAsBytes();
final file = kdbxForamt.read(data, cred);
final file = await kdbxForamt.read(data, cred);
expect(file.body.rootGroup.entries, hasLength(3));
});
});
@ -79,9 +79,9 @@ void main() {
});
group('Integration', () {
test('Simple save and load', () {
test('Simple save and load', () async {
final credentials = Credentials(ProtectedValue.fromString('FooBar'));
final Uint8List saved = (() {
final Uint8List saved = await (() async {
final kdbx = kdbxForamt.create(credentials, 'CreateTest');
final rootGroup = kdbx.body.rootGroup;
final entry = KdbxEntry.create(kdbx, rootGroup);
@ -93,7 +93,7 @@ void main() {
// print(ByteUtils.toHexList(saved));
final kdbx = kdbxForamt.read(saved, credentials);
final kdbx = await kdbxForamt.read(saved, credentials);
expect(
kdbx.body.rootGroup.entries.first
.getString(KdbxKey('Password'))

Loading…
Cancel
Save