diff --git a/lib/kdbx.dart b/lib/kdbx.dart index 32a1167..b0c7fa5 100644 --- a/lib/kdbx.dart +++ b/lib/kdbx.dart @@ -1,6 +1,9 @@ /// dart library for reading keepass file format (kdbx). library kdbx; +export 'src/credentials/credentials.dart' + show Credentials, CredentialsPart, HashCredentials, PasswordCredentials; +export 'src/credentials/keyfile.dart' show KeyFileComposite, KeyFileCredentials; export 'src/crypto/key_encrypter_kdf.dart' show KeyEncrypterKdf, KdfType, KdfField; export 'src/crypto/protected_value.dart' @@ -12,17 +15,7 @@ export 'src/kdbx_dao.dart' show KdbxDao; export 'src/kdbx_entry.dart' show KdbxEntry, KdbxKey, KdbxKeyCommon; export 'src/kdbx_exceptions.dart'; export 'src/kdbx_file.dart'; -export 'src/kdbx_format.dart' - show - KdbxBody, - Credentials, - CredentialsPart, - HashCredentials, - MergeContext, - KdbxFormat, - KeyFileComposite, - KeyFileCredentials, - PasswordCredentials; +export 'src/kdbx_format.dart' show KdbxBody, MergeContext, KdbxFormat; export 'src/kdbx_group.dart' show KdbxGroup; export 'src/kdbx_header.dart' show KdbxVersion; export 'src/kdbx_meta.dart'; diff --git a/lib/src/credentials/credentials.dart b/lib/src/credentials/credentials.dart new file mode 100644 index 0000000..7929b27 --- /dev/null +++ b/lib/src/credentials/credentials.dart @@ -0,0 +1,43 @@ +import 'dart:typed_data'; + +import 'package:kdbx/src/credentials/keyfile.dart'; +import 'package:kdbx/src/crypto/protected_value.dart'; +import 'package:kdbx/src/internal/extension_utils.dart'; + +abstract class CredentialsPart { + Uint8List getBinary(); +} + +abstract class Credentials { + factory Credentials(ProtectedValue password) => + Credentials.composite(password, null); //PasswordCredentials(password); + factory Credentials.composite(ProtectedValue? password, Uint8List? keyFile) => + KeyFileComposite( + password: password?.let((that) => PasswordCredentials(that)), + keyFile: keyFile == null ? null : KeyFileCredentials(keyFile), + ); + + factory Credentials.fromHash(Uint8List hash) => HashCredentials(hash); + + Uint8List getHash(); +} + +class PasswordCredentials implements CredentialsPart { + PasswordCredentials(this._password); + + final ProtectedValue _password; + + @override + Uint8List getBinary() { + return _password.hash; + } +} + +class HashCredentials implements Credentials { + HashCredentials(this.hash); + + final Uint8List hash; + + @override + Uint8List getHash() => hash; +} diff --git a/lib/src/credentials/keyfile.dart b/lib/src/credentials/keyfile.dart new file mode 100644 index 0000000..b0e3f05 --- /dev/null +++ b/lib/src/credentials/keyfile.dart @@ -0,0 +1,77 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:collection/collection.dart' show IterableExtension; +import 'package:convert/convert.dart' as convert; +import 'package:kdbx/src/credentials/credentials.dart'; +import 'package:kdbx/src/crypto/protected_value.dart'; +import 'package:xml/xml.dart' as xml; +import 'package:crypto/crypto.dart' as crypto; + +import 'package:logging/logging.dart'; + +final _logger = Logger('keyfile'); + +class KeyFileCredentials implements CredentialsPart { + factory KeyFileCredentials(Uint8List keyFileContents) { + try { + final keyFileAsString = utf8.decode(keyFileContents); + if (_hexValuePattern.hasMatch(keyFileAsString)) { + return KeyFileCredentials._(ProtectedValue.fromBinary( + convert.hex.decode(keyFileAsString) as Uint8List)); + } + final xmlContent = xml.XmlDocument.parse(keyFileAsString); + final metaVersion = + xmlContent.findAllElements('Version').singleOrNull?.text; + final key = xmlContent.findAllElements('Key').single; + final dataString = key.findElements('Data').single; + final encoded = dataString.text.replaceAll(RegExp(r'\s'), ''); + Uint8List dataBytes; + if (metaVersion != null && metaVersion.startsWith('2.')) { + dataBytes = convert.hex.decode(encoded) as Uint8List; + } else { + dataBytes = base64.decode(encoded); + } + _logger.finer('Decoded base64 of keyfile.'); + return KeyFileCredentials._(ProtectedValue.fromBinary(dataBytes)); + } catch (e, stackTrace) { + _logger.warning( + 'Unable to parse key file as hex or XML, use as is.', e, stackTrace); + final bytes = crypto.sha256.convert(keyFileContents).bytes as Uint8List; + return KeyFileCredentials._(ProtectedValue.fromBinary(bytes)); + } + } + + KeyFileCredentials._(this._keyFileValue); + + static final RegExp _hexValuePattern = + RegExp(r'^[a-f\d]{64}', caseSensitive: false); + + final ProtectedValue _keyFileValue; + + @override + Uint8List getBinary() { + return _keyFileValue.binaryValue; +// return crypto.sha256.convert(_keyFileValue.binaryValue).bytes as Uint8List; + } +} + +class KeyFileComposite implements Credentials { + KeyFileComposite({required this.password, required this.keyFile}); + + PasswordCredentials? password; + KeyFileCredentials? keyFile; + + @override + Uint8List getHash() { + final buffer = [...?password?.getBinary(), ...?keyFile?.getBinary()]; + return crypto.sha256.convert(buffer).bytes as Uint8List; + +// final output = convert.AccumulatorSink(); +// final input = crypto.sha256.startChunkedConversion(output); +//// input.add(password.getHash()); +// input.add(buffer); +// input.close(); +// return output.events.single.bytes as Uint8List; + } +} diff --git a/lib/src/kdbx_file.dart b/lib/src/kdbx_file.dart index d413834..02022b1 100644 --- a/lib/src/kdbx_file.dart +++ b/lib/src/kdbx_file.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:collection/collection.dart'; +import 'package:kdbx/src/credentials/credentials.dart'; import 'package:kdbx/src/crypto/protected_value.dart'; import 'package:kdbx/src/kdbx_consts.dart'; import 'package:kdbx/src/kdbx_dao.dart'; diff --git a/lib/src/kdbx_format.dart b/lib/src/kdbx_format.dart index 6af8c75..7c017ff 100644 --- a/lib/src/kdbx_format.dart +++ b/lib/src/kdbx_format.dart @@ -6,9 +6,9 @@ import 'dart:typed_data'; import 'package:archive/archive.dart'; import 'package:argon2_ffi_base/argon2_ffi_base.dart'; import 'package:collection/collection.dart' show IterableExtension; -import 'package:convert/convert.dart' as convert; import 'package:crypto/crypto.dart' as crypto; import 'package:kdbx/kdbx.dart'; +import 'package:kdbx/src/credentials/credentials.dart'; import 'package:kdbx/src/crypto/key_encrypter_kdf.dart'; import 'package:kdbx/src/crypto/protected_salt_generator.dart'; import 'package:kdbx/src/internal/consts.dart'; @@ -31,40 +31,6 @@ import 'package:xml/xml.dart' as xml; final _logger = Logger('kdbx.format'); -abstract class Credentials { - factory Credentials(ProtectedValue password) => - Credentials.composite(password, null); //PasswordCredentials(password); - factory Credentials.composite(ProtectedValue? password, Uint8List? keyFile) => - KeyFileComposite( - password: password?.let((that) => PasswordCredentials(that)), - keyFile: keyFile == null ? null : KeyFileCredentials(keyFile), - ); - - factory Credentials.fromHash(Uint8List hash) => HashCredentials(hash); - - Uint8List getHash(); -} - -class KeyFileComposite implements Credentials { - KeyFileComposite({required this.password, required this.keyFile}); - - PasswordCredentials? password; - KeyFileCredentials? keyFile; - - @override - Uint8List getHash() { - final buffer = [...?password?.getBinary(), ...?keyFile?.getBinary()]; - return crypto.sha256.convert(buffer).bytes as Uint8List; - -// final output = convert.AccumulatorSink(); -// final input = crypto.sha256.startChunkedConversion(output); -//// input.add(password.getHash()); -// input.add(buffer); -// input.close(); -// return output.events.single.bytes as Uint8List; - } -} - /// Context used during reading and writing. class KdbxReadWriteContext { KdbxReadWriteContext({ @@ -147,74 +113,6 @@ class KdbxReadWriteContext { } } -abstract class CredentialsPart { - Uint8List getBinary(); -} - -class KeyFileCredentials implements CredentialsPart { - factory KeyFileCredentials(Uint8List keyFileContents) { - try { - final keyFileAsString = utf8.decode(keyFileContents); - if (_hexValuePattern.hasMatch(keyFileAsString)) { - return KeyFileCredentials._(ProtectedValue.fromBinary( - convert.hex.decode(keyFileAsString) as Uint8List)); - } - final xmlContent = xml.XmlDocument.parse(keyFileAsString); - final metaVersion = - xmlContent.findAllElements('Version').singleOrNull?.text; - final key = xmlContent.findAllElements('Key').single; - final dataString = key.findElements('Data').single; - final encoded = dataString.text.replaceAll(RegExp(r'\s'), ''); - Uint8List dataBytes; - if (metaVersion != null && metaVersion.startsWith('2.')) { - dataBytes = convert.hex.decode(encoded) as Uint8List; - } else { - dataBytes = base64.decode(encoded); - } - _logger.finer('Decoded base64 of keyfile.'); - return KeyFileCredentials._(ProtectedValue.fromBinary(dataBytes)); - } catch (e, stackTrace) { - _logger.warning( - 'Unable to parse key file as hex or XML, use as is.', e, stackTrace); - final bytes = crypto.sha256.convert(keyFileContents).bytes as Uint8List; - return KeyFileCredentials._(ProtectedValue.fromBinary(bytes)); - } - } - - KeyFileCredentials._(this._keyFileValue); - - static final RegExp _hexValuePattern = - RegExp(r'^[a-f\d]{64}', caseSensitive: false); - - final ProtectedValue _keyFileValue; - - @override - Uint8List getBinary() { - return _keyFileValue.binaryValue; -// return crypto.sha256.convert(_keyFileValue.binaryValue).bytes as Uint8List; - } -} - -class PasswordCredentials implements CredentialsPart { - PasswordCredentials(this._password); - - final ProtectedValue _password; - - @override - Uint8List getBinary() { - return _password.hash; - } -} - -class HashCredentials implements Credentials { - HashCredentials(this.hash); - - final Uint8List hash; - - @override - Uint8List getHash() => hash; -} - class KdbxBody extends KdbxNode { KdbxBody.create(this.meta, this.rootGroup) : super.create('KeePassFile') { node.children.add(meta.node);