From 8fa79475298e9171bbd92740ef3159c383df2848 Mon Sep 17 00:00:00 2001 From: Herbert Poul Date: Wed, 19 Feb 2020 12:48:50 +0100 Subject: [PATCH] first version implementing kdbx4 support. --- .idea/dictionaries/herbert.xml | 1 + bin/kdbx.dart | 4 +- example/kdbx_example.dart | 2 +- lib/src/crypto/key_encrypter_kdf.dart | 113 +++++++++++++ lib/src/crypto/protected_salt_generator.dart | 37 ++++ lib/src/internal/byte_utils.dart | 42 ++++- lib/src/kdbx_format.dart | 169 +++++++++++++++++-- lib/src/kdbx_header.dart | 42 ++++- lib/src/kdbx_var_dictionary.dart | 127 ++++++++++++++ libargon2_ffi.dylib | Bin 0 -> 72052 bytes pubspec.yaml | 4 + test/kdbx4_test.dart | 117 +++++++++++++ test/kdbx_test.dart | 20 +-- test/keepassxcpasswords.kdbx | Bin 1493 -> 1461 bytes 14 files changed, 641 insertions(+), 37 deletions(-) create mode 100644 lib/src/crypto/key_encrypter_kdf.dart create mode 100644 lib/src/kdbx_var_dictionary.dart create mode 100755 libargon2_ffi.dylib create mode 100644 test/kdbx4_test.dart diff --git a/.idea/dictionaries/herbert.xml b/.idea/dictionaries/herbert.xml index 93abe3f..7e2bf04 100644 --- a/.idea/dictionaries/herbert.xml +++ b/.idea/dictionaries/herbert.xml @@ -3,6 +3,7 @@ consts derivator + encrypter kdbx diff --git a/bin/kdbx.dart b/bin/kdbx.dart index 971ce70..cbab6c7 100644 --- a/bin/kdbx.dart +++ b/bin/kdbx.dart @@ -74,8 +74,8 @@ abstract class KdbxFileCommand extends Command { final bytes = await File(inputFile).readAsBytes(); final password = prompts.get('Password for $inputFile', conceal: true, validate: (str) => str.isNotEmpty); - final file = KdbxFormat.read( - bytes, Credentials(ProtectedValue.fromString(password))); + final file = KdbxFormat(null) + .read(bytes, Credentials(ProtectedValue.fromString(password))); return runWithFile(file); } diff --git a/example/kdbx_example.dart b/example/kdbx_example.dart index 0453429..d4902e5 100644 --- a/example/kdbx_example.dart +++ b/example/kdbx_example.dart @@ -1,5 +1,5 @@ import 'package:kdbx/kdbx.dart'; void main() { - KdbxFormat.read(null, null); + KdbxFormat().read(null, null); } diff --git a/lib/src/crypto/key_encrypter_kdf.dart b/lib/src/crypto/key_encrypter_kdf.dart new file mode 100644 index 0000000..dc902f0 --- /dev/null +++ b/lib/src/crypto/key_encrypter_kdf.dart @@ -0,0 +1,113 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:kdbx/kdbx.dart'; +import 'package:kdbx/src/internal/byte_utils.dart'; +import 'package:kdbx/src/kdbx_var_dictionary.dart'; +import 'package:logging/logging.dart'; + +final _logger = Logger('key_encrypter_kdf'); + +enum KdfType { + Argon2, + Aes, +} + +class KdfField { + KdfField(this.field, this.type); + + final String field; + final ValueType type; + + static final salt = KdfField('S', ValueType.typeBytes); + static final parallelism = KdfField('P', ValueType.typeUInt32); + static final memory = KdfField('M', ValueType.typeUInt64); + static final iterations = KdfField('I', ValueType.typeUInt64); + static final version = KdfField('V', ValueType.typeUInt32); + static final secretKey = KdfField('K', ValueType.typeBytes); + static final assocData = KdfField('A', ValueType.typeBytes); + static final rounds = KdfField('R', ValueType.typeInt64); + + static final fields = [ + salt, + parallelism, + memory, + iterations, + version, + secretKey, + assocData, + rounds + ]; + + static void debugAll(VarDictionary dict) { + _logger + .fine('VarDictionary{\n${fields.map((f) => f.debug(dict)).join('\n')}'); + } + + T read(VarDictionary dict) => dict.get(type, field); + String debug(VarDictionary dict) { + final value = dict.get(type, field); + final strValue = type == ValueType.typeBytes + ? ByteUtils.toHexList(value as Uint8List) + : value; + return '$field=$strValue'; + } +} + +class KeyEncrypterKdf { + KeyEncrypterKdf(this.argon2); + + static const kdfUuids = { + '72Nt34wpREuR96mkA+MKDA==': KdfType.Argon2, + 'ydnzmmKKRGC/dA0IwYpP6g==': KdfType.Aes, + }; + + final Argon2 argon2; + + Uint8List encrypt(Uint8List key, VarDictionary kdfParameters) { + final uuid = kdfParameters.get(ValueType.typeBytes, '\$UUID'); + if (uuid == null) { + throw KdbxCorruptedFileException('No Kdf UUID'); + } + final kdfUuid = base64.encode(uuid); + switch (kdfUuids[kdfUuid]) { + case KdfType.Argon2: + _logger.fine('Must be using argon2'); + return encryptArgon2(key, kdfParameters); + break; + case KdfType.Aes: + _logger.fine('Must be using aes'); + break; + } + throw UnsupportedError('unsupported encrypt stuff.'); + } + + Uint8List encryptArgon2(Uint8List key, VarDictionary kdfParameters) { + _logger.fine('argon2():'); + _logger.fine('key: ${ByteUtils.toHexList(key)}'); + KdfField.debugAll(kdfParameters); + return argon2.argon2( + key, + KdfField.salt.read(kdfParameters), + 65536, //KdfField.memory.read(kdfParameters), + KdfField.iterations.read(kdfParameters), + 32, + KdfField.parallelism.read(kdfParameters), + 0, + KdfField.version.read(kdfParameters), + ); + } +} + +abstract class Argon2 { + Uint8List argon2( + Uint8List key, + Uint8List salt, + int memory, + int iterations, + int length, + int parallelism, + int type, + int version, + ); +} diff --git a/lib/src/crypto/protected_salt_generator.dart b/lib/src/crypto/protected_salt_generator.dart index 891da36..b63f5bb 100644 --- a/lib/src/crypto/protected_salt_generator.dart +++ b/lib/src/crypto/protected_salt_generator.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:crypto/crypto.dart'; +import 'package:cryptography/cryptography.dart' as cryptography; import 'package:pointycastle/export.dart'; class ProtectedSaltGenerator { @@ -11,6 +12,9 @@ class ProtectedSaltGenerator { ..init(false, ParametersWithIV(KeyParameter(hash), salsaNonce)); return ProtectedSaltGenerator._(cipher); } + factory ProtectedSaltGenerator.chacha20(Uint8List key) { + return ChachaProtectedSaltGenerator.create(key); // Chacha20(); + } ProtectedSaltGenerator._(this._cipher); @@ -30,3 +34,36 @@ class ProtectedSaltGenerator { return base64.encode(encrypted); } } + +class ChachaProtectedSaltGenerator implements ProtectedSaltGenerator { + ChachaProtectedSaltGenerator._(this._secretKey, this._nonce); + + factory ChachaProtectedSaltGenerator.create(Uint8List key) { + final hash = sha512.convert(key); + final secretKey = hash.bytes.sublist(0, 32); + final nonce = hash.bytes.sublist(32, 32 + 12); + return ChachaProtectedSaltGenerator._( + cryptography.SecretKey(secretKey), cryptography.SecretKey(nonce)); + } + + final cryptography.SecretKey _secretKey; + final cryptography.SecretKey _nonce; + + @override + StreamCipher get _cipher => throw UnimplementedError(); + + @override + String decryptBase64(String protectedValue) { + final result = cryptography.chacha20 + .decrypt(base64.decode(protectedValue), _secretKey, nonce: _nonce); + return utf8.decode(result); + } + + @override + String encryptToBase64(String plainValue) { + final input = utf8.encode(plainValue) as Uint8List; + final encrypted = + cryptography.chacha20.encrypt(input, _secretKey, nonce: _nonce); + return base64.encode(encrypted); + } +} diff --git a/lib/src/internal/byte_utils.dart b/lib/src/internal/byte_utils.dart index dfa3aff..560cf50 100644 --- a/lib/src/internal/byte_utils.dart +++ b/lib/src/internal/byte_utils.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; @@ -20,7 +21,7 @@ class ByteUtils { return true; } - static String toHex(int val) => '0x${val.toRadixString(16)}'; + static String toHex(int val) => '0x${val.toRadixString(16).padLeft(2, '0')}'; static String toHexList(List list) => list?.map((val) => toHex(val))?.join(' ') ?? '(null)'; @@ -73,8 +74,13 @@ class ReaderHelper { int readUint32() => _nextByteBuffer(4).getUint32(0, Endian.little); int readUint64() => _nextByteBuffer(8).getUint64(0, Endian.little); + int readInt32() => _nextByteBuffer(4).getInt32(0, Endian.little); + int readInt64() => _nextByteBuffer(8).getInt64(0, Endian.little); + Uint8List readBytes(int size) => _nextBytes(size); + String readString(int size) => const Utf8Decoder().convert(readBytes(size)); + Uint8List readBytesUpTo(int maxSize) => _nextBytes(min(maxSize, lengthInBytes - pos)); @@ -84,6 +90,8 @@ class ReaderHelper { static int singleUint64(Uint8List bytes) => ReaderHelper(bytes).readUint64(); } +typedef LengthWriter = void Function(int length); + class WriterHelper { WriterHelper([BytesBuilder output]) : output = output ?? BytesBuilder(); @@ -91,25 +99,40 @@ class WriterHelper { void _write(ByteData byteData) => output.add(byteData.buffer.asUint8List()); - void writeBytes(Uint8List bytes) { + void writeBytes(Uint8List bytes, [LengthWriter lengthWriter]) { + lengthWriter?.call(4); output.add(bytes); // output.asUint8List().addAll(bytes); } - void writeUint32(int value) { + void writeUint32(int value, [LengthWriter lengthWriter]) { + lengthWriter?.call(4); _write(ByteData(4)..setUint32(0, value, Endian.little)); // output.asUint32List().add(value); } - void writeUint64(int value) { + void writeUint64(int value, [LengthWriter lengthWriter]) { + lengthWriter?.call(8); _write(ByteData(8)..setUint64(0, value, Endian.little)); } - void writeUint16(int value) { + void writeUint16(int value, [LengthWriter lengthWriter]) { + lengthWriter?.call(2); _write(ByteData(2)..setUint16(0, value, Endian.little)); } - void writeUint8(int value) { + void writeInt32(int value, [LengthWriter lengthWriter]) { + lengthWriter?.call(4); + _write(ByteData(4)..setInt32(0, value, Endian.little)); + } + + void writeInt64(int value, [LengthWriter lengthWriter]) { + lengthWriter?.call(8); + _write(ByteData(8)..setInt64(0, value, Endian.little)); + } + + void writeUint8(int value, [LengthWriter lengthWriter]) { + lengthWriter?.call(1); output.addByte(value); } @@ -117,4 +140,11 @@ class WriterHelper { (WriterHelper()..writeUint32(val)).output.toBytes(); static Uint8List singleUint64Bytes(int val) => (WriterHelper()..writeUint64(val)).output.toBytes(); + + int writeString(String value, [LengthWriter lengthWriter]) { + final bytes = const Utf8Encoder().convert(value); + lengthWriter?.call(bytes.length); + writeBytes(bytes); + return bytes.length; + } } diff --git a/lib/src/kdbx_format.dart b/lib/src/kdbx_format.dart index ccc9a20..90f8497 100644 --- a/lib/src/kdbx_format.dart +++ b/lib/src/kdbx_format.dart @@ -1,14 +1,17 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:ffi'; import 'dart:io'; import 'dart:typed_data'; import 'package:convert/convert.dart' as convert; import 'package:crypto/crypto.dart' as crypto; import 'package:kdbx/kdbx.dart'; +import 'package:kdbx/src/crypto/key_encrypter_kdf.dart'; import 'package:kdbx/src/crypto/protected_salt_generator.dart'; import 'package:kdbx/src/crypto/protected_value.dart'; import 'package:kdbx/src/internal/byte_utils.dart'; +import 'package:kdbx/src/internal/consts.dart'; import 'package:kdbx/src/internal/crypto_utils.dart'; import 'package:kdbx/src/kdbx_group.dart'; import 'package:kdbx/src/kdbx_header.dart'; @@ -278,7 +281,11 @@ class KdbxBody extends KdbxNode { } class KdbxFormat { - static KdbxFile create( + KdbxFormat([this.argon2]); + + final Argon2 argon2; + + KdbxFile create( Credentials credentials, String name, { String generator, @@ -293,19 +300,22 @@ class KdbxFormat { return KdbxFile(credentials, header, body); } - static KdbxFile read(Uint8List input, Credentials credentials) { + KdbxFile read(Uint8List input, Credentials credentials) { final reader = ReaderHelper(input); final header = KdbxHeader.read(reader); - if (header.versionMajor != 3) { + if (header.versionMajor == 3) { + return _loadV3(header, reader, credentials); + } else if (header.versionMajor == 4) { + return _loadV4(header, reader, credentials); + } else { _logger.finer('Unsupported version for $header'); throw KdbxUnsupportedException('Unsupported kdbx version ' '${header.versionMajor}.${header.versionMinor}.' - ' Only 3.x is supported.'); + ' Only 3.x and 4.x is supported.'); } - return _loadV3(header, reader, credentials); } - static KdbxFile _loadV3( + KdbxFile _loadV3( KdbxHeader header, ReaderHelper reader, Credentials credentials) { // _getMasterKeyV3(header, credentials); final masterKey = _generateMasterKeyV3(header, credentials); @@ -324,14 +334,138 @@ class KdbxFormat { } } - static KdbxBody _loadXml(KdbxHeader header, String xmlString) { + KdbxFile _loadV4( + KdbxHeader header, ReaderHelper reader, Credentials credentials) { + 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)}'); + throw KdbxCorruptedFileException('Header hash does not match.'); + } + _logger + .finest('KdfParameters: ${header.readKdfParameters.toDebugString()}'); + _logger.finest('Header hash matches.'); + final key = _computeKeysV4(header, credentials); + final masterSeed = header.fields[HeaderFields.MasterSeed].bytes; + if (masterSeed.length != 32) { + throw const FormatException('Master seed must be 32 bytes.'); + } +// final keyWithSeed = Uint8List(65); +// keyWithSeed.replaceRange(0, masterSeed.length, masterSeed); +// keyWithSeed.replaceRange( +// masterSeed.length, masterSeed.length + key.length, key); +// keyWithSeed[64] = 1; + _logger.fine('masterSeed: ${ByteUtils.toHexList(masterSeed)}'); + final keyWithSeed = masterSeed + key + Uint8List.fromList([1]); + assert(keyWithSeed.length == 65); + final cipher = crypto.sha256.convert(keyWithSeed.sublist(0, 64)); + final hmacKey = crypto.sha512.convert(keyWithSeed); + _logger.fine('hmacKey: ${ByteUtils.toHexList(hmacKey.bytes)}'); + final headerHmac = + _getHeaderHmac(header, reader, hmacKey.bytes as Uint8List); + final expectedHmac = reader.readBytes(headerHmac.bytes.length); + _logger.fine('Expected: ${ByteUtils.toHexList(expectedHmac)}'); + _logger.fine('Actual : ${ByteUtils.toHexList(headerHmac.bytes)}'); + if (!ByteUtils.eq(hash, actualHash)) { + throw KdbxInvalidKeyException(); + } +// final hmacTransformer = crypto.Hmac(crypto.sha256, hmacKey.bytes); +// final blockreader.readBytes(32); + final bodyStuff = hmacBlockTransformer(reader); + _logger.fine('body decrypt: ${ByteUtils.toHexList(bodyStuff)}'); + final decrypted = decrypt(header, bodyStuff, cipher.bytes as Uint8List); + _logger.finer('compression: ${header.compression}'); + if (header.compression == Compression.gzip) { + final content = GZipCodec().decode(decrypted) as Uint8List; + final contentReader = ReaderHelper(content); + final fieldIterable = + KdbxHeader.readField(contentReader, 4, InnerHeaderFields.values); + final headerFields = Map.fromEntries( + fieldIterable.map((field) => MapEntry(field.field, field))); + _logger.fine('inner header fields: $headerFields'); + header.fields.addAll(headerFields); + final xml = utf8.decode(contentReader.readRemaining()); + _logger.fine('content: $xml'); + return KdbxFile(credentials, header, _loadXml(header, xml)); + } + return null; + } + + Uint8List hmacBlockTransformer(ReaderHelper reader) { + Uint8List blockHash; + int blockLength; + List ret = []; + while (true) { + blockHash = reader.readBytes(32); + blockLength = reader.readUint32(); + if (blockLength < 1) { + return Uint8List.fromList(ret); + } + ret.addAll(reader.readBytes(blockLength)); + } + } + + Uint8List decrypt( + KdbxHeader header, Uint8List encrypted, Uint8List cipherKey) { + final cipherId = base64.encode(header.fields[HeaderFields.CipherID].bytes); + if (cipherId == CryptoConsts.CIPHER_IDS[Cipher.aes].uuid) { + _logger.fine('We need AES'); + final result = _decryptContentV4(header, cipherKey, encrypted); + _logger.fine('Result: ${ByteUtils.toHexList(result)}'); + return result; + } else if (cipherId == CryptoConsts.CIPHER_IDS[Cipher.chaCha20].uuid) { + _logger.fine('We need chacha20'); + } else { + throw UnsupportedError('Unsupported cipherId $cipherId'); + } + } + +// Uint8List _transformDataV4Aes() { +// } + + crypto.Digest _getHeaderHmac( + KdbxHeader header, ReaderHelper reader, Uint8List key) { + final writer = WriterHelper() + ..writeUint32(0xffffffff) + ..writeUint32(0xffffffff) + ..writeBytes(key); + final hmacKey = crypto.sha512.convert(writer.output.toBytes()).bytes; + final src = reader.byteData.sublist(0, header.endPos); + final hmacKeyStuff = crypto.Hmac(crypto.sha256, hmacKey); + _logger.fine('keySha: ${ByteUtils.toHexList(hmacKey)}'); + _logger.fine('src: ${ByteUtils.toHexList(src)}'); + return hmacKeyStuff.convert(src); + } + + Uint8List _computeKeysV4(KdbxHeader header, Credentials credentials) { + final masterSeed = header.fields[HeaderFields.MasterSeed].bytes; + final kdfParameters = header.readKdfParameters; + assert(masterSeed.length == 32); + final credentialHash = credentials.getHash(); + _logger.fine('MasterSeed: ${ByteUtils.toHexList(masterSeed)}'); + _logger.fine('credentialHash: ${ByteUtils.toHexList(credentialHash)}'); + final ret = KeyEncrypterKdf(argon2).encrypt(credentialHash, kdfParameters); + _logger.fine('keyv4: ${ByteUtils.toHexList(ret)}'); + return ret; + } + + ProtectedSaltGenerator _createProtectedSaltGenerator(KdbxHeader header) { final protectedValueEncryption = header.innerRandomStreamEncryption; - if (protectedValueEncryption != ProtectedValueEncryption.salsa20) { + final streamKey = header.fields[HeaderFields.ProtectedStreamKey].bytes; + if (protectedValueEncryption == ProtectedValueEncryption.salsa20) { + return ProtectedSaltGenerator(streamKey); + } else if (protectedValueEncryption == ProtectedValueEncryption.chaCha20) { + return ProtectedSaltGenerator.chacha20(streamKey); + } else { throw KdbxUnsupportedException( 'Inner encryption: $protectedValueEncryption'); } - final streamKey = header.fields[HeaderFields.ProtectedStreamKey].bytes; - final gen = ProtectedSaltGenerator(streamKey); + } + + KdbxBody _loadXml(KdbxHeader header, String xmlString) { + final gen = _createProtectedSaltGenerator(header); final document = xml.parse(xmlString); @@ -350,7 +484,7 @@ class KdbxFormat { return KdbxBody.read(keePassFile, KdbxMeta.read(meta), rootGroup); } - static Uint8List _decryptContent( + Uint8List _decryptContent( KdbxHeader header, Uint8List masterKey, Uint8List encryptedPayload) { final encryptionIv = header.fields[HeaderFields.EncryptionIV].bytes; final decryptCipher = CBCBlockCipher(AESFastEngine()); @@ -383,6 +517,19 @@ class KdbxFormat { return content; } + Uint8List _decryptContentV4( + KdbxHeader header, Uint8List masterKey, Uint8List encryptedPayload) { + final encryptionIv = header.fields[HeaderFields.EncryptionIV].bytes; + final decryptCipher = CBCBlockCipher(AESFastEngine()); + decryptCipher.init( + false, ParametersWithIV(KeyParameter(masterKey), encryptionIv)); + final paddedDecrypted = + AesHelper.processBlocks(decryptCipher, encryptedPayload); + + final decrypted = AesHelper.unpad(paddedDecrypted); + return decrypted; + } + static Uint8List _generateMasterKeyV3( KdbxHeader header, Credentials credentials) { final rounds = ReaderHelper.singleUint64( diff --git a/lib/src/kdbx_header.dart b/lib/src/kdbx_header.dart index e3f68cc..6730eca 100644 --- a/lib/src/kdbx_header.dart +++ b/lib/src/kdbx_header.dart @@ -3,6 +3,7 @@ import 'dart:typed_data'; import 'package:crypto/crypto.dart' as crypto; import 'package:kdbx/src/internal/byte_utils.dart'; import 'package:kdbx/src/internal/consts.dart'; +import 'package:kdbx/src/kdbx_var_dictionary.dart'; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; @@ -23,7 +24,7 @@ enum Compression { } /// how protected values are encrypted in the xml. -enum ProtectedValueEncryption { plainText, arc4variant, salsa20 } +enum ProtectedValueEncryption { plainText, arc4variant, salsa20, chaCha20 } enum HeaderFields { EndOfHeader, @@ -41,6 +42,13 @@ enum HeaderFields { PublicCustomData, } +enum InnerHeaderFields { + EndOfHeader, + InnerRandomStreamID, + InnerRandomStreamKey, + Binary, +} + class HeaderField { HeaderField(this.field, this.bytes); @@ -57,6 +65,7 @@ class KdbxHeader { @required this.versionMinor, @required this.versionMajor, @required this.fields, + @required this.endPos, }); KdbxHeader.create() @@ -66,6 +75,7 @@ class KdbxHeader { versionMinor: 1, versionMajor: 3, fields: _defaultFieldValues(), + endPos: null, ); static List _requiredFields(int majorVersion) { @@ -76,7 +86,8 @@ class KdbxHeader { HeaderFields.CipherID, HeaderFields.CompressionFlags, HeaderFields.MasterSeed, - HeaderFields.EncryptionIV + HeaderFields.EncryptionIV, + HeaderFields.InnerRandomStreamID, ]; if (majorVersion < 4) { return baseHeaders + @@ -85,7 +96,7 @@ class KdbxHeader { HeaderFields.TransformRounds, HeaderFields.ProtectedStreamKey, HeaderFields.StreamStartBytes, - HeaderFields.InnerRandomStreamID +// HeaderFields.InnerRandomStreamID ]; } else { // TODO kdbx 4 support @@ -189,26 +200,37 @@ class KdbxHeader { _logger.finer('Reading version: $versionMajor.$versionMinor'); final headerFields = Map.fromEntries(readField(reader, versionMajor) .map((field) => MapEntry(field.field, field))); + return KdbxHeader( sig1: sig1, sig2: sig2, versionMinor: versionMinor, versionMajor: versionMajor, fields: headerFields, + endPos: reader.pos, ); } - static Iterable readField( - ReaderHelper reader, int versionMajor) sync* { + static Iterable readField(ReaderHelper reader, int versionMajor, + [List fields = HeaderFields.values]) sync* { while (true) { final headerId = reader.readUint8(); final int bodySize = versionMajor >= 4 ? reader.readUint32() : reader.readUint16(); final bodyBytes = bodySize > 0 ? reader.readBytes(bodySize) : null; _logger.finer( - 'Read header ${HeaderFields.values[headerId]}: ${ByteUtils.toHexList(bodyBytes)}'); + 'Read header ${fields[headerId]}: ${ByteUtils.toHexList(bodyBytes)}'); if (headerId > 0) { - yield HeaderField(HeaderFields.values[headerId], bodyBytes); + final dynamic field = fields[headerId]; + if (field is HeaderFields) { + yield HeaderField(field, bodyBytes); + } else { + if (field == InnerHeaderFields.InnerRandomStreamID) { + yield HeaderField(HeaderFields.InnerRandomStreamID, bodyBytes); + } else if (field == InnerHeaderFields.InnerRandomStreamKey) { + yield HeaderField(HeaderFields.ProtectedStreamKey, bodyBytes); + } + } } else { break; } @@ -221,6 +243,9 @@ class KdbxHeader { final int versionMajor; final Map fields; + /// end position of the header, if we have been reading from a stream. + final int endPos; + Compression get compression { switch (ReaderHelper.singleUint32( fields[HeaderFields.CompressionFlags].bytes)) { @@ -237,6 +262,9 @@ class KdbxHeader { ProtectedValueEncryption.values[ReaderHelper.singleUint32( fields[HeaderFields.InnerRandomStreamID].bytes)]; + VarDictionary get readKdfParameters => VarDictionary.read( + ReaderHelper(fields[HeaderFields.KdfParameters].bytes)); + @override String toString() { return 'KdbxHeader{sig1: $sig1, sig2: $sig2, versionMajor: $versionMajor, versionMinor: $versionMinor}'; diff --git a/lib/src/kdbx_var_dictionary.dart b/lib/src/kdbx_var_dictionary.dart new file mode 100644 index 0000000..b1ff948 --- /dev/null +++ b/lib/src/kdbx_var_dictionary.dart @@ -0,0 +1,127 @@ +import 'package:kdbx/src/internal/byte_utils.dart'; +import 'package:logging/logging.dart'; +import 'package:meta/meta.dart'; + +final _logger = Logger('kdbx_var_dictionary'); + +typedef Decoder = T Function(ReaderHelper reader, int length); +typedef Encoder = void Function(WriterHelper writer, T value); + +extension on WriterHelper { + LengthWriter _lengthWriter() => (int length) => writeInt32(length); +} + +@immutable +class ValueType { + const ValueType(this.code, this.decoder, [this.encoder]); + final int code; + final Decoder decoder; + final Encoder encoder; + + static final typeUInt32 = ValueType( + 0x04, + (reader, _) => reader.readUint32(), + (writer, value) => writer.writeUint32(value, writer._lengthWriter()), + ); + static final typeUInt64 = ValueType( + 0x05, + (reader, _) => reader.readUint64(), + (writer, value) => writer.writeUint64(value, writer._lengthWriter()), + ); + static final typeBool = ValueType( + 0x08, + (reader, _) => reader.readUint8() != 0, + (writer, value) => writer.writeUint8(value ? 1 : 0, writer._lengthWriter()), + ); + static final typeInt32 = ValueType( + 0x0C, + (reader, _) => reader.readInt32(), + (writer, value) => writer.writeInt32(value, writer._lengthWriter()), + ); + static final typeInt64 = ValueType( + 0x0D, + (reader, _) => reader.readInt64(), + (writer, value) => writer.writeInt64(value, writer._lengthWriter()), + ); + static final typeString = ValueType( + 0x18, + (reader, length) => reader.readString(length), + (writer, value) => writer.writeString(value, writer._lengthWriter()), + ); + static final typeBytes = ValueType( + 0x42, + (reader, length) => reader.readBytes(length), + (writer, value) => writer.writeBytes(value, writer._lengthWriter()), + ); + + static ValueType typeByCode(int code) => + values.firstWhere((t) => t.code == code); + + static final values = [ + typeUInt32, + typeUInt64, + typeBool, + typeInt32, + typeInt64, + typeString, + typeBytes, + ]; +} + +class VarDictionaryItem { + VarDictionaryItem(this._key, this._valueType, this._value); + + final String _key; + final ValueType _valueType; + final T _value; + + String toDebugString() { + return 'VarDictionaryItem{key=$_key, valueType=$_valueType, value=${_value.runtimeType}}'; + } +} + +class VarDictionary { + VarDictionary(List> items) + : assert(items != null), + _items = items, + _dict = Map.fromEntries(items.map((item) => MapEntry(item._key, item))); + + factory VarDictionary.read(ReaderHelper reader) { + final items = []; + final versionMinor = reader.readUint8(); + final versionMajor = reader.readUint8(); + _logger.finest('Reading VarDictionary $versionMajor.$versionMinor'); + assert(versionMajor == 1); + + while (true) { + final item = _readItem(reader); + if (item == null) { + break; + } + items.add(item); + } + return VarDictionary(items); + } + + final List> _items; + final Map> _dict; + + T get(ValueType type, String key) => _dict[key]?._value as T; + + static VarDictionaryItem _readItem(ReaderHelper reader) { + final type = reader.readUint8(); + if (type == 0) { + return null; + } + final keyLength = reader.readUint32(); + final key = reader.readString(keyLength); + final valueLength = reader.readInt32(); + final valueType = ValueType.typeByCode(type); + return VarDictionaryItem( + key, valueType, valueType.decoder(reader, valueLength)); + } + + String toDebugString() { + return 'VarDictionary{${_items.map((item) => item.toDebugString())}'; + } +} diff --git a/libargon2_ffi.dylib b/libargon2_ffi.dylib new file mode 100755 index 0000000000000000000000000000000000000000..bf06d71a898dcc9fd8fda96dbe348c532adfbf98 GIT binary patch literal 72052 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 literal 0 HcmV?d00001 diff --git a/pubspec.yaml b/pubspec.yaml index 646d3c0..830b3db 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: logging: '>=0.11.3+2 <1.0.0' crypto: '>=2.0.0 <3.0.0' pointycastle: '>=1.0.1 <2.0.0' + cryptography: ^0.1.2 xml: '>=3.7.0 <4.0.0' uuid: '>=2.0.0 <3.0.0' meta: '>=1.0.0 <2.0.0' @@ -25,8 +26,11 @@ dependencies: args: '>1.5.0 <2.0.0' prompts: '>=1.3.0 <2.0.0' logging_appenders: '>=0.1.0 <1.0.0' + ffi_helper: ^1.4.0 dev_dependencies: pedantic: '>=1.7.0 <2.0.0' test: '>=1.6.0 <2.0.0' + ffi: ^0.1.3 + diff --git a/test/kdbx4_test.dart b/test/kdbx4_test.dart new file mode 100644 index 0000000..d7c1af8 --- /dev/null +++ b/test/kdbx4_test.dart @@ -0,0 +1,117 @@ +import 'dart:convert'; +import 'dart:ffi'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:ffi/ffi.dart'; +import 'package:ffi_helper/ffi_helper.dart'; +import 'package:kdbx/kdbx.dart'; +import 'package:kdbx/src/crypto/key_encrypter_kdf.dart'; +import 'package:logging/logging.dart'; +import 'package:logging_appenders/logging_appenders.dart'; +import 'package:test/test.dart'; + +final _logger = Logger('kdbx4_test'); + +//typedef HashStuff = Pointer Function(Pointer str); +typedef Argon2HashNative = Pointer Function( + Pointer key, + IntPtr keyLen, + Pointer salt, + Uint64 saltlen, + Uint32 m_cost, // memory cost + Uint32 t_cost, // time cost (number iterations) + Uint32 parallelism, + IntPtr hashlen, + Uint8 type, + Uint32 version, +); +typedef Argon2Hash = Pointer Function( + Pointer key, + int keyLen, + Pointer salt, + int saltlen, + int m_cost, // memory cost + int t_cost, // time cost (number iterations) + int parallelism, + int hashlen, + int type, + int version, +); + +class Argon2Test implements Argon2 { + Argon2Test() { +// final argon2lib = DynamicLibrary.open('libargon2.1.dylib'); + final argon2lib = DynamicLibrary.open('libargon2_ffi.dylib'); + _argon2hash = argon2lib + .lookup>('hp_argon2_hash') + .asFunction(); + } + Argon2Hash _argon2hash; + + @override + Uint8List argon2( + Uint8List key, + Uint8List salt, + int memory, + int iterations, + int length, + int parallelism, + int type, + int version, + ) { +// print('hash: ${hashStuff('abc')}'); + final keyArray = Uint8Array.fromTypedList(key); +// final saltArray = Uint8Array.fromTypedList(salt); + final saltArray = allocate(count: salt.length); + final saltList = saltArray.asTypedList(length); + saltList.setAll(0, salt); + const int memoryCost = 1 << 16; + +// _logger.fine('saltArray: ${ByteUtils.toHexList(saltArray.view)}'); + + final result = _argon2hash( + keyArray.rawPtr, + keyArray.length, + saltArray, + salt.length, + memoryCost, + iterations, + parallelism, + length, + type, + version, + ); + + keyArray.free(); +// saltArray.free(); + free(saltArray); + final resultString = Utf8.fromUtf8(result); + return base64.decode(resultString); + } + +// String hashStuff(String password) => +// Utf8.fromUtf8(_hashStuff(Utf8.toUtf8(password))); +} + +void main() { + Logger.root.level = Level.ALL; + PrintAppender().attachToLogger(Logger.root); + final kdbxFormat = KdbxFormat(Argon2Test()); + group('Reading', () { + final argon2 = Argon2Test(); + 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 firstEntry = file.body.rootGroup.entries.first; + final pwd = firstEntry.getString(KdbxKey('Password')).getText(); + _logger.info('password: $pwd'); + expect(pwd, 'MyPassword'); + }); + }); +} diff --git a/test/kdbx_test.dart b/test/kdbx_test.dart index d34a145..ce08173 100644 --- a/test/kdbx_test.dart +++ b/test/kdbx_test.dart @@ -1,3 +1,4 @@ +import 'dart:ffi'; import 'dart:io'; import 'dart:typed_data'; @@ -20,12 +21,13 @@ class FakeProtectedSaltGenerator implements ProtectedSaltGenerator { void main() { Logger.root.level = Level.ALL; PrintAppender().attachToLogger(Logger.root); + final kdbxForamt = KdbxFormat(); group('Reading', () { setUp(() {}); test('First Test', () async { final data = await File('test/FooBar.kdbx').readAsBytes(); - KdbxFormat.read(data, Credentials(ProtectedValue.fromString('FooBar'))); + kdbxForamt.read(data, Credentials(ProtectedValue.fromString('FooBar'))); }); }); @@ -36,14 +38,14 @@ void main() { final cred = Credentials.composite( ProtectedValue.fromString('asdf'), keyFileBytes); final data = await File('test/password-and-keyfile.kdbx').readAsBytes(); - final file = KdbxFormat.read(data, cred); + final file = kdbxForamt.read(data, cred); expect(file.body.rootGroup.entries, hasLength(2)); }); }); group('Creating', () { test('Simple create', () { - final kdbx = KdbxFormat.create( + final kdbx = kdbxForamt.create( Credentials(ProtectedValue.fromString('FooBar')), 'CreateTest'); expect(kdbx, isNotNull); expect(kdbx.body.rootGroup, isNotNull); @@ -54,7 +56,7 @@ void main() { .toXmlString(pretty: true)); }); test('Create Entry', () { - final kdbx = KdbxFormat.create( + final kdbx = kdbxForamt.create( Credentials(ProtectedValue.fromString('FooBar')), 'CreateTest'); final rootGroup = kdbx.body.rootGroup; final entry = KdbxEntry.create(kdbx, rootGroup); @@ -71,7 +73,7 @@ void main() { test('Simple save and load', () { final credentials = Credentials(ProtectedValue.fromString('FooBar')); final Uint8List saved = (() { - final kdbx = KdbxFormat.create(credentials, 'CreateTest'); + final kdbx = kdbxForamt.create(credentials, 'CreateTest'); final rootGroup = kdbx.body.rootGroup; final entry = KdbxEntry.create(kdbx, rootGroup); rootGroup.addEntry(entry); @@ -82,7 +84,7 @@ void main() { // print(ByteUtils.toHexList(saved)); - final kdbx = KdbxFormat.read(saved, credentials); + final kdbx = kdbxForamt.read(saved, credentials); expect( kdbx.body.rootGroup.entries.first .getString(KdbxKey('Password')) @@ -92,12 +94,10 @@ void main() { }); }); - group('Unsupported version', () { + group('kdbx 4.x', () { test('Fails with exception', () async { final data = await File('test/keepassxcpasswords.kdbx').readAsBytes(); - expect(() { - KdbxFormat.read(data, Credentials(ProtectedValue.fromString('asdf'))); - }, throwsA(const TypeMatcher())); + kdbxForamt.read(data, Credentials(ProtectedValue.fromString('asdf'))); }); }); } diff --git a/test/keepassxcpasswords.kdbx b/test/keepassxcpasswords.kdbx index 69ad677291d8ca0008d10e770f34c8ec169868d3..2a50297c6dfe6db4a2d3a739990e5549d721458e 100644 GIT binary patch delta 1337 zcmV-91;+Z-3$+W7FExyI@$rqG`*JOqi7KOKrkP)7+rGPds$&QKpvlpVD!vC00000D zA+eK}tkX;S`>-rBt|=^$WMO~mQkqReuv82y&Tw{BdK%jnn@^T-2f2UF3|{9B8-ED| z0RR91Rs;Y5022TJ0000400000(5(va0Zb7uV8$j}VjSt-nj-h?Oqk2V1c;S3%}SoL z*{X~RfVQK+K8zM~HUcbrh9UTYxII>M1Kzm=FU*wSciiVB%6*)cS@VBkkUhhjos(H5 z({Ud3H5`oA&RTlo;ZwN^Fa!Vqrl2I8P8jds!!Ov7Y_4|;S2ES-hixJwmxq#%m_4)Y zcSao~Zg zcO8?8uU5iF;~;c9h_yx^%kWjo7DmZe^?7B>0a%Nhp>YJUI<8%m>gk@f2EkT8@$aYM z27s-kUaRxkkK`Fj*}iZao)As{Q-&yneu8BiP;Ak*1SK0qKu~`u?RtLo8UU&!n0pUr zGD?+jKmU_6nV&fBYMEiUX=rb;V^jM49+cI8gwzX>ic?EQ_UL^G;*v}rL{{vX)hU^( z3N3o=;a{Ok3jmzH(NK^hs}3;8I{Y>GJkXNA?w-j2S$u=XO;)CfCdX&wwE+fHLgJP{ zzRQqnq;S@vjN5;n)rbm$syYuaG`SQ8te*~2^lB#f zyzK(e$EocAm00jp9PL)-5;Ng#yyJrVR2FR>s%y6?u$)7V0WeX;?hCAZX#|4`fA%o$ zl)ZY748WN0&%q}-Zvx!%>A)>Q?$20H+;K~5f4(VW`J#VToK)Wioy|9)%|(LtQ=Mg+ zAT(-h%^iHEB9}?mlI9v!r6CD_Nto#|Eb5?yO|2~f$w$J;Hm~YsQBnmOPQ?32#gZ{s zQJXEYFOHFj4GsBhVST2A&n3F%9;a5WaZsuBWELZ_Qr1UVFdPELelIkHxf_!;u!CU7 z6@yeY=e>Vd`k5$&#(S{=gkMyv{63$D=`W17^=PJW+ezkEH0nE=dC81g1e0GnXx{@_ z)e&qILZuoO@EjVq`Ys=@>s*kP^_(9Gy@b)tHBn|kwEQr9mHdlaVawYe1zBD< za=an=Q;kK4aul!Yd;xX?tuy0vsp~8t|yS*yjt1kMJLA( zPA;AG9x1sO8$m|GUd1wT%qhO4MIm$Q;FGe9C za8B=sJGp_|+n6+i8D*ABKY`LGOGx^WF^eD$Gv#OGkiPdWV8}a$b6=VCsTuz+sJU-v zs|bH2LE|We6oE28efB?P3Qb~~Xa1r_rW=4I`NwxFRJs5tB*iu4mt6g zLLxVOker`RRyNi&+R2aXL}5Rsf;yribRwVyEF;sw~>uM;u)%398;X3SM+@5h)h$+A80 vDXmC7Hk6zgwH|nQN%#3MMN1Q8rT)tzPH@CKpKlCE-vD*Kp3(~u00000d$@Y> delta 1370 zcmV-g1*Q763)KsdFEyzC;6F`5$>_k|B=b-yNU*GXl=^iP)4}6~4M)P!=Q)iNBv<(UiInYSmN^N;($fiWi zV9cdN1U$_9UYc18x`eEx;1k)jvL;hFsrkOLRDJup&h{X05u@8T4XU?Xv-j~4N|bY zapE3%xjKp(Qmg&WZDg z2t3-km>w91gI~S@ZT!m2wquz68;5;>UUQ!y>iqnHcM@1_R#v#YY)IP43@9s8p|zFm z(#fYE$)TD&&XBp;nu)F)uLn_nBOejO+6anQQ+f7+K8_$tLCYwlj}kVJV-F~)_Jo#; zBeH8Khwa47JcY&F{XJlgw~UkU7F9-~qQ*cze@K*>)t z)T+~0M5u^xK42T?=LT8OLn+5?SV6t?md)c9pWqqWy!NlrA;EpJ3++g8`NAuD#fo?n zreJhq{ONzcbh)Er%G-#QD5xfU&=02yL@inGr>Ft*6mi!Dxq;u@9z8 z=#R=eq3xW##{(W4?^jifYmBI0vo)jP znX`|p`z;*StQLG@>CJy zSe9!7Z-x!)wT9>DB($v{VcChrK9iQ=G~gNUbIuVf)~xz;F{X3tGbrOHjY0r<-OTNo z{TfTcsfC$lVlbk44aL{!9zL_8jv7(aX&>n8dJ<{V)Y8cPY|WnuaEZcbE}Od4^>Z3W cY!Nr)ID6~q?ep#8@A^;ubBe1C9{>OV00eK6fB*mh