Browse Source

If argon2 ffi implementation is not available, fallback to pointycastle (dart-only) implementation.

pull/5/head
Herbert Poul 3 years ago
parent
commit
90bc4d3138
  1. 5
      CHANGELOG.md
  2. 38
      lib/src/internal/pointycastle_argon2.dart
  3. 13
      lib/src/kdbx_format.dart
  4. 4
      pubspec.yaml
  5. 3
      test/kdbx4_test.dart
  6. 59
      test/kdbx4_test_pointycastle.dart

5
CHANGELOG.md

@ -1,3 +1,8 @@
## 2.2.0
- If argon2 ffi implementation is not available, fallback to pointycastle (dart-only)
implementation.
## 2.1.1 ## 2.1.1
- Throw KdbxInvalidFileStructure for invalid files. - Throw KdbxInvalidFileStructure for invalid files.

38
lib/src/internal/pointycastle_argon2.dart

@ -0,0 +1,38 @@
import 'dart:typed_data';
import 'package:argon2_ffi_base/argon2_ffi_base.dart';
import 'package:pointycastle/export.dart' as pc;
import 'package:pointycastle/pointycastle.dart' as pc;
/// Dart-only implementation using pointycastle's Argon KDF.
class PointyCastleArgon2 extends Argon2 {
const PointyCastleArgon2();
@override
bool get isFfi => false;
@override
bool get isImplemented => true;
pc.KeyDerivator argon2Kdf() => pc.Argon2BytesGenerator();
@override
Uint8List argon2(Argon2Arguments args) {
final kdf = argon2Kdf();
kdf.init(pc.Argon2Parameters(
args.type,
args.salt,
desiredKeyLength: args.length,
iterations: args.iterations,
memory: args.memory,
lanes: args.parallelism,
version: args.version,
));
return kdf.process(args.key);
}
@override
Future<Uint8List> argon2Async(Argon2Arguments args) {
return Future.value(argon2(args));
}
}

13
lib/src/kdbx_format.dart

@ -14,6 +14,7 @@ import 'package:kdbx/src/crypto/protected_salt_generator.dart';
import 'package:kdbx/src/internal/consts.dart'; import 'package:kdbx/src/internal/consts.dart';
import 'package:kdbx/src/internal/crypto_utils.dart'; import 'package:kdbx/src/internal/crypto_utils.dart';
import 'package:kdbx/src/internal/extension_utils.dart'; import 'package:kdbx/src/internal/extension_utils.dart';
import 'package:kdbx/src/internal/pointycastle_argon2.dart';
import 'package:kdbx/src/kdbx_deleted_object.dart'; import 'package:kdbx/src/kdbx_deleted_object.dart';
import 'package:kdbx/src/kdbx_entry.dart'; import 'package:kdbx/src/kdbx_entry.dart';
import 'package:kdbx/src/kdbx_group.dart'; import 'package:kdbx/src/kdbx_group.dart';
@ -524,9 +525,13 @@ class _KeysV4 {
} }
class KdbxFormat { class KdbxFormat {
KdbxFormat([this.argon2]) : assert(kdbxKeyCommonAssertConsistency()); KdbxFormat([Argon2? argon2])
: assert(kdbxKeyCommonAssertConsistency()),
argon2 = argon2 == null || !argon2.isImplemented
? const PointyCastleArgon2()
: argon2;
final Argon2? argon2; final Argon2 argon2;
static bool dartWebWorkaround = false; static bool dartWebWorkaround = false;
/// Creates a new, empty [KdbxFile] with default settings. /// Creates a new, empty [KdbxFile] with default settings.
@ -537,7 +542,7 @@ class KdbxFormat {
String? generator, String? generator,
KdbxHeader? header, KdbxHeader? header,
}) { }) {
header ??= argon2 == null ? KdbxHeader.createV3() : KdbxHeader.createV4(); header ??= KdbxHeader.createV4();
final ctx = KdbxReadWriteContext(binaries: [], header: header); final ctx = KdbxReadWriteContext(binaries: [], header: header);
final meta = KdbxMeta.create( final meta = KdbxMeta.create(
databaseName: name, databaseName: name,
@ -789,7 +794,7 @@ class KdbxFormat {
final credentialHash = credentials.getHash(); final credentialHash = credentials.getHash();
final key = final key =
await KeyEncrypterKdf(argon2!).encrypt(credentialHash, kdfParameters); await KeyEncrypterKdf(argon2).encrypt(credentialHash, kdfParameters);
// final keyWithSeed = Uint8List(65); // final keyWithSeed = Uint8List(65);
// keyWithSeed.replaceRange(0, masterSeed.length, masterSeed); // keyWithSeed.replaceRange(0, masterSeed.length, masterSeed);

4
pubspec.yaml

@ -1,6 +1,6 @@
name: kdbx name: kdbx
description: KeepassX format implementation in pure dart. (kdbx 3.x and 4.x support). description: KeepassX format implementation in pure dart. (kdbx 3.x and 4.x support).
version: 2.1.0 version: 2.2.0
homepage: https://github.com/authpass/kdbx.dart homepage: https://github.com/authpass/kdbx.dart
environment: environment:
@ -27,7 +27,7 @@ dependencies:
# required for bin/ # required for bin/
args: '>1.5.0 <3.0.0' args: '>1.5.0 <3.0.0'
logging_appenders: '>=0.1.0 <2.0.0' logging_appenders: '>=0.1.0 <2.0.0'
argon2_ffi_base: ^1.0.0+2 argon2_ffi_base: ^1.1.0+1
dev_dependencies: dev_dependencies:
pedantic: '>=1.7.0 <2.0.0' pedantic: '>=1.7.0 <2.0.0'

3
test/kdbx4_test.dart

@ -15,6 +15,9 @@ final _logger = Logger('kdbx4_test');
void main() { void main() {
final testUtil = TestUtil(); final testUtil = TestUtil();
final kdbxFormat = testUtil.kdbxFormat; final kdbxFormat = testUtil.kdbxFormat;
if (!kdbxFormat.argon2.isFfi) {
throw StateError('Expected ffi!');
}
group('Reading', () { group('Reading', () {
test('bubb', () async { test('bubb', () async {
final data = await File('test/keepassxcpasswords.kdbx').readAsBytes(); final data = await File('test/keepassxcpasswords.kdbx').readAsBytes();

59
test/kdbx4_test_pointycastle.dart

@ -0,0 +1,59 @@
import 'dart:io';
import 'package:kdbx/kdbx.dart';
import 'package:kdbx/src/kdbx_header.dart';
import 'package:logging/logging.dart';
import 'package:test/test.dart';
import 'internal/test_utils.dart';
final _logger = Logger('kdbx4_test_pointycastle');
void main() {
// ignore: unused_local_variable
final testUtil = TestUtil();
final kdbxFormat = KdbxFormat();
if (kdbxFormat.argon2.isFfi) {
throw StateError('Expected non-ffi implementation.');
}
_logger.fine('argon2 implementation: ${kdbxFormat.argon2}');
group('Reading pointycastle argon2', () {
test('pc: Reading kdbx4_keeweb', () async {
final data = await File('test/kdbx4_keeweb.kdbx').readAsBytes();
final file = await kdbxFormat.read(
data, Credentials(ProtectedValue.fromString('asdf')));
final firstEntry = file.body.rootGroup.entries.first;
final pwd = firstEntry.getString(KdbxKeyCommon.PASSWORD)!.getText();
expect(pwd, 'def');
});
});
group('Writing pointycastle argon2', () {
test('Create and save', () async {
final credentials = Credentials(ProtectedValue.fromString('asdf'));
final kdbx = kdbxFormat.create(
credentials,
'Test Keystore',
header: KdbxHeader.createV4(),
);
final rootGroup = kdbx.body.rootGroup;
_createEntry(kdbx, rootGroup, 'user1', 'LoremIpsum');
_createEntry(kdbx, rootGroup, 'user2', 'Second Password');
final saved = await kdbx.save();
final loadedKdbx = await kdbxFormat.read(
saved, Credentials(ProtectedValue.fromString('asdf')));
_logger.fine('Successfully loaded kdbx $loadedKdbx');
File('test_v4x.kdbx').writeAsBytesSync(saved);
});
});
}
KdbxEntry _createEntry(
KdbxFile file, KdbxGroup group, String username, String password) {
final entry = KdbxEntry.create(file, group);
group.addEntry(entry);
entry.setString(KdbxKeyCommon.USER_NAME, PlainValue(username));
entry.setString(KdbxKeyCommon.PASSWORD, ProtectedValue.fromString(password));
return entry;
}
Loading…
Cancel
Save