From 7eee2d672f404609bff3bd4d7cea2409d241541a Mon Sep 17 00:00:00 2001 From: Herbert Poul Date: Tue, 10 Sep 2019 17:04:42 +0200 Subject: [PATCH] always use little endian, fixed packet index for payload during serialization. --- CHANGELOG.md | 6 + example/pubspec.lock | 197 +++++++++++++++++++++++++++++ example/pubspec.yaml | 14 ++ lib/src/internal/byte_utils.dart | 76 ++++++++--- lib/src/kdbx_format.dart | 39 +++--- lib/src/kdbx_header.dart | 67 +++++----- lib/src/kdbx_object.dart | 5 +- pubspec.yaml | 9 +- test/internal/byte_utils_test.dart | 8 +- test/pytest/Dockerfile | 7 + test/pytest/docker-compose.yml | 7 + test/{ => pytest}/kdbx3_decrypt.py | 6 +- 12 files changed, 363 insertions(+), 78 deletions(-) create mode 100644 example/pubspec.lock create mode 100644 example/pubspec.yaml create mode 100644 test/pytest/Dockerfile create mode 100644 test/pytest/docker-compose.yml rename test/{ => pytest}/kdbx3_decrypt.py (98%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fdbea4..1a8e0bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.2.0 + +- Fixed writing of packet index for payload. +- Fixed big endian vs. little endian encoding. +- Compatibility fixes with other kdbx apps. + ## 0.1.0 - Support for reading and writing kdbx 2.x files diff --git a/example/pubspec.lock b/example/pubspec.lock new file mode 100644 index 0000000..6e5c40c --- /dev/null +++ b/example/pubspec.lock @@ -0,0 +1,197 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.2" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.14.11" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + cookie_jar: + dependency: transitive + description: + name: cookie_jar + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + dio: + dependency: transitive + description: + name: dio + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.16" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.0" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.3" + kdbx: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.1.0" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.3+2" + logging_appenders: + dependency: transitive + description: + name: logging_appenders + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.2" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.7" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.4" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" + pointycastle: + dependency: transitive + description: + name: pointycastle + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + prompts: + dependency: transitive + description: + name: prompts + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + rxdart: + dependency: transitive + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.22.2" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.5" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.6" + uuid: + dependency: transitive + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "3.5.0" +sdks: + dart: ">=2.4.0 <3.0.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 0000000..88f9809 --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,14 @@ +name: kdbx_example +description: Demonstrates how to use the kdbx plugin. +publish_to: 'none' +version: 1.0.0+1 + +environment: + sdk: ">=2.2.2 <3.0.0" + +dependencies: + flutter: + sdk: flutter + + kdbx: + path: ../ diff --git a/lib/src/internal/byte_utils.dart b/lib/src/internal/byte_utils.dart index 307b1ff..dfa3aff 100644 --- a/lib/src/internal/byte_utils.dart +++ b/lib/src/internal/byte_utils.dart @@ -23,30 +23,65 @@ class ByteUtils { static String toHex(int val) => '0x${val.toRadixString(16)}'; static String toHexList(List list) => - list.map((val) => toHex(val)).join(' '); + list?.map((val) => toHex(val))?.join(' ') ?? '(null)'; } class ReaderHelper { - ReaderHelper(this.data); + ReaderHelper(this.byteData) : lengthInBytes = byteData.lengthInBytes; - final Uint8List data; + final Uint8List byteData; int pos = 0; + final int lengthInBytes; + +// ByteData _nextByteBuffer(int byteCount) { +// final ret = ByteData.view(data, pos, pos += byteCount); +// pos += byteCount; +// return ret; +// } + +// ByteData _nextByteBuffer(int byteCount) => +// ByteData.view(data, pos, (pos += byteCount) - pos); + +// ByteData _nextByteBuffer(int byteCount) { +// try { +// return ByteData.view(data, pos, byteCount); +// } finally { +// pos += byteCount; +// } +// } + + ByteData _nextByteBuffer(int byteCount) => _advanceByteCount( + byteCount, + () => ByteData.view( + byteData.buffer, pos + byteData.offsetInBytes, byteCount)); + + Uint8List _nextBytes(int byteCount) => _advanceByteCount( + byteCount, + () => Uint8List.view( + byteData.buffer, pos + byteData.offsetInBytes, byteCount)); + + T _advanceByteCount(int byteCount, T Function() func) { + try { + return func(); + } finally { + pos += byteCount; + } + } - ByteBuffer _nextByteBuffer(int byteCount) => - (data.sublist(pos, pos += byteCount) as Uint8List).buffer; - - int readUint32() => _nextByteBuffer(4).asUint32List().first; - - int readUint16() => _nextByteBuffer(2).asUint16List().first; + int readUint8() => _nextByteBuffer(1).getUint8(0); + int readUint16() => _nextByteBuffer(2).getUint16(0, Endian.little); + int readUint32() => _nextByteBuffer(4).getUint32(0, Endian.little); + int readUint64() => _nextByteBuffer(8).getUint64(0, Endian.little); - int readUint8() => data[pos++]; + Uint8List readBytes(int size) => _nextBytes(size); - ByteBuffer readBytes(int size) => _nextByteBuffer(size); + Uint8List readBytesUpTo(int maxSize) => + _nextBytes(min(maxSize, lengthInBytes - pos)); - ByteBuffer readBytesUpTo(int maxSize) => - _nextByteBuffer(min(maxSize, data.lengthInBytes - pos)); + Uint8List readRemaining() => _nextBytes(lengthInBytes - pos); - Uint8List readRemaining() => data.sublist(pos) as Uint8List; + static int singleUint32(Uint8List bytes) => ReaderHelper(bytes).readUint32(); + static int singleUint64(Uint8List bytes) => ReaderHelper(bytes).readUint64(); } class WriterHelper { @@ -54,25 +89,32 @@ class WriterHelper { final BytesBuilder output; + void _write(ByteData byteData) => output.add(byteData.buffer.asUint8List()); + void writeBytes(Uint8List bytes) { output.add(bytes); // output.asUint8List().addAll(bytes); } void writeUint32(int value) { - output.add(Uint32List.fromList([value]).buffer.asUint8List()); + _write(ByteData(4)..setUint32(0, value, Endian.little)); // output.asUint32List().add(value); } void writeUint64(int value) { - output.add(Uint64List.fromList([value]).buffer.asUint8List()); + _write(ByteData(8)..setUint64(0, value, Endian.little)); } void writeUint16(int value) { - output.add(Uint16List.fromList([value]).buffer.asUint8List()); + _write(ByteData(2)..setUint16(0, value, Endian.little)); } void writeUint8(int value) { output.addByte(value); } + + static Uint8List singleUint32Bytes(int val) => + (WriterHelper()..writeUint32(val)).output.toBytes(); + static Uint8List singleUint64Bytes(int val) => + (WriterHelper()..writeUint64(val)).output.toBytes(); } diff --git a/lib/src/kdbx_format.dart b/lib/src/kdbx_format.dart index 5603e7c..30c4c9e 100644 --- a/lib/src/kdbx_format.dart +++ b/lib/src/kdbx_format.dart @@ -89,8 +89,7 @@ class KdbxFile { header.generateSalts(); header.write(writer); - final streamKey = - header.fields[HeaderFields.ProtectedStreamKey].bytes.asUint8List(); + final streamKey = header.fields[HeaderFields.ProtectedStreamKey].bytes; final gen = ProtectedSaltGenerator(streamKey); body.meta.headerHash.set( @@ -165,16 +164,15 @@ class KdbxBody extends KdbxNode { Uint8List _encryptV3(KdbxFile kdbxFile, Uint8List compressedBytes) { final byteWriter = WriterHelper(); - byteWriter.writeBytes(kdbxFile - .header.fields[HeaderFields.StreamStartBytes].bytes - .asUint8List()); + byteWriter.writeBytes( + kdbxFile.header.fields[HeaderFields.StreamStartBytes].bytes); HashedBlockReader.writeBlocks(ReaderHelper(compressedBytes), byteWriter); final bytes = byteWriter.output.toBytes(); final masterKey = KdbxFormat._generateMasterKeyV3(kdbxFile.header, kdbxFile.credentials); final encrypted = KdbxFormat._encryptDataAes(masterKey, bytes, - kdbxFile.header.fields[HeaderFields.EncryptionIV].bytes.asUint8List()); + kdbxFile.header.fields[HeaderFields.EncryptionIV].bytes); return encrypted; } @@ -276,8 +274,7 @@ class KdbxFormat { throw KdbxUnsupportedException( 'Inner encryption: $protectedValueEncryption'); } - final streamKey = - header.fields[HeaderFields.ProtectedStreamKey].bytes.asUint8List(); + final streamKey = header.fields[HeaderFields.ProtectedStreamKey].bytes; final gen = ProtectedSaltGenerator(streamKey); final document = xml.parse(xmlString); @@ -301,26 +298,25 @@ class KdbxFormat { KdbxHeader header, Uint8List masterKey, Uint8List encryptedPayload) { final encryptionIv = header.fields[HeaderFields.EncryptionIV].bytes; final decryptCipher = CBCBlockCipher(AESFastEngine()); - decryptCipher.init(false, - ParametersWithIV(KeyParameter(masterKey), encryptionIv.asUint8List())); + decryptCipher.init( + false, ParametersWithIV(KeyParameter(masterKey), encryptionIv)); final paddedDecrypted = AesHelper.processBlocks(decryptCipher, encryptedPayload); final streamStart = header.fields[HeaderFields.StreamStartBytes].bytes; if (paddedDecrypted.lengthInBytes < streamStart.lengthInBytes) { - _logger.warning('decrypted content was shorter than expected stream start block.'); + _logger.warning( + 'decrypted content was shorter than expected stream start block.'); throw KdbxInvalidKeyException(); } - _logger.finest( - 'streamStart: ${ByteUtils.toHexList(streamStart.asUint8List())}'); + _logger.finest('streamStart: ${ByteUtils.toHexList(streamStart)}'); _logger.finest( 'actual : ${ByteUtils.toHexList(paddedDecrypted.sublist(0, streamStart.lengthInBytes))}'); - - if (!ByteUtils.eq(streamStart.asUint8List(), - paddedDecrypted.sublist(0, streamStart.lengthInBytes))) { + if (!ByteUtils.eq( + streamStart, paddedDecrypted.sublist(0, streamStart.lengthInBytes))) { throw KdbxInvalidKeyException(); } @@ -333,11 +329,12 @@ class KdbxFormat { static Uint8List _generateMasterKeyV3( KdbxHeader header, Credentials credentials) { - final rounds = - header.fields[HeaderFields.TransformRounds].bytes.asUint64List().first; - final seed = header.fields[HeaderFields.TransformSeed].bytes.asUint8List(); + final rounds = ReaderHelper.singleUint64( + header.fields[HeaderFields.TransformRounds].bytes); + final seed = header.fields[HeaderFields.TransformSeed].bytes; final masterSeed = header.fields[HeaderFields.MasterSeed].bytes; - _logger.finer('Rounds: $rounds'); + _logger.finer( + 'Rounds: $rounds (${ByteUtils.toHexList(header.fields[HeaderFields.TransformRounds].bytes)})'); final cipher = ECBBlockCipher(AESFastEngine()) ..init(true, KeyParameter(seed)); @@ -348,7 +345,7 @@ class KdbxFormat { } transformedKey = crypto.sha256.convert(transformedKey).bytes as Uint8List; final masterKey = crypto.sha256 - .convert(Uint8List.fromList(masterSeed.asUint8List() + transformedKey)) + .convert(Uint8List.fromList(masterSeed + transformedKey)) .bytes as Uint8List; return masterKey; } diff --git a/lib/src/kdbx_header.dart b/lib/src/kdbx_header.dart index d7e722c..c02ab1d 100644 --- a/lib/src/kdbx_header.dart +++ b/lib/src/kdbx_header.dart @@ -45,7 +45,7 @@ class HeaderField { HeaderField(this.field, this.bytes); final HeaderFields field; - final ByteBuffer bytes; + final Uint8List bytes; String get name => field.toString(); } @@ -68,11 +68,6 @@ class KdbxHeader { fields: _defaultFieldValues(), ); - static ByteBuffer _intAsUint32Bytes(int val) => - (WriterHelper()..writeUint32(val)).output.toBytes().buffer; - static ByteBuffer _intAsUint64Bytes(int val) => - (WriterHelper()..writeUint64(val)).output.toBytes().buffer; - static List _requiredFields(int majorVersion) { if (majorVersion < 3) { throw KdbxUnsupportedException('Unsupported version: $majorVersion'); @@ -107,21 +102,20 @@ class KdbxHeader { } } - void _setHeaderField(HeaderFields field, ByteBuffer bytes) { + void _setHeaderField(HeaderFields field, Uint8List bytes) { fields[field] = HeaderField(field, bytes); } void generateSalts() { // TODO make sure default algorithm is "secure" engouh. Or whether we should // use like [SecureRandom] from PointyCastle? - _setHeaderField(HeaderFields.MasterSeed, ByteUtils.randomBytes(32).buffer); + _setHeaderField(HeaderFields.MasterSeed, ByteUtils.randomBytes(32)); if (versionMajor < 4) { - _setHeaderField(HeaderFields.TransformSeed, ByteUtils.randomBytes(32).buffer); - _setHeaderField( - HeaderFields.StreamStartBytes, ByteUtils.randomBytes(32).buffer); + _setHeaderField(HeaderFields.TransformSeed, ByteUtils.randomBytes(32)); + _setHeaderField(HeaderFields.StreamStartBytes, ByteUtils.randomBytes(32)); _setHeaderField( - HeaderFields.ProtectedStreamKey, ByteUtils.randomBytes(32).buffer); - _setHeaderField(HeaderFields.EncryptionIV, ByteUtils.randomBytes(16).buffer); + HeaderFields.ProtectedStreamKey, ByteUtils.randomBytes(32)); + _setHeaderField(HeaderFields.EncryptionIV, ByteUtils.randomBytes(16)); } else { throw KdbxUnsupportedException( 'We do not support Kdbx 4.x right now. ($versionMajor.$versionMinor)'); @@ -140,7 +134,8 @@ class KdbxHeader { in HeaderFields.values.where((f) => f != HeaderFields.EndOfHeader)) { _writeField(writer, field); } - fields[HeaderFields.EndOfHeader] = HeaderField(HeaderFields.EndOfHeader, Uint8List(0).buffer); + fields[HeaderFields.EndOfHeader] = + HeaderField(HeaderFields.EndOfHeader, Uint8List(0)); _writeField(writer, HeaderFields.EndOfHeader); } @@ -152,7 +147,7 @@ class KdbxHeader { _logger.finer('Writing header $field (${value.bytes.lengthInBytes})'); writer.writeUint8(field.index); _writeFieldSize(writer, value.bytes.lengthInBytes); - writer.writeBytes(value.bytes.asUint8List()); + writer.writeBytes(value.bytes); } void _writeFieldSize(WriterHelper writer, int size) { @@ -167,11 +162,13 @@ class KdbxHeader { Map.fromEntries([ HeaderField(HeaderFields.CipherID, CryptoConsts.CIPHER_IDS[Cipher.aes].toBytes()), - HeaderField(HeaderFields.CompressionFlags, _intAsUint32Bytes(1)), - HeaderField(HeaderFields.TransformRounds, _intAsUint64Bytes(6000)), + HeaderField( + HeaderFields.CompressionFlags, WriterHelper.singleUint32Bytes(1)), + HeaderField( + HeaderFields.TransformRounds, WriterHelper.singleUint64Bytes(6000)), HeaderField( HeaderFields.InnerRandomStreamID, - _intAsUint32Bytes(ProtectedValueEncryption.values + WriterHelper.singleUint32Bytes(ProtectedValueEncryption.values .indexOf(ProtectedValueEncryption.salsa20))), ].map((f) => MapEntry(f.field, f))); @@ -207,8 +204,9 @@ class KdbxHeader { final headerId = reader.readUint8(); final int bodySize = versionMajor >= 4 ? reader.readUint32() : reader.readUint16(); - _logger.finer('Read header ${HeaderFields.values[headerId]}'); final bodyBytes = bodySize > 0 ? reader.readBytes(bodySize) : null; + _logger.finer( + 'Read header ${HeaderFields.values[headerId]}: ${ByteUtils.toHexList(bodyBytes)}'); if (headerId > 0) { yield HeaderField(HeaderFields.values[headerId], bodyBytes); } else { @@ -224,7 +222,8 @@ class KdbxHeader { final Map fields; Compression get compression { - switch (fields[HeaderFields.CompressionFlags].bytes.asUint32List().single) { + switch (ReaderHelper.singleUint32( + fields[HeaderFields.CompressionFlags].bytes)) { case 0: return Compression.none; case 1: @@ -235,8 +234,8 @@ class KdbxHeader { } ProtectedValueEncryption get innerRandomStreamEncryption => - ProtectedValueEncryption.values[ - fields[HeaderFields.InnerRandomStreamID].bytes.asUint32List().single]; + ProtectedValueEncryption.values[ReaderHelper.singleUint32( + fields[HeaderFields.InnerRandomStreamID].bytes)]; } class KdbxException implements Exception {} @@ -263,15 +262,17 @@ class HashedBlockReader { Uint8List.fromList(readNextBlock(reader).expand((x) => x).toList()); static Iterable readNextBlock(ReaderHelper reader) sync* { + int expectedBlockIndex = 0; while (true) { // ignore: unused_local_variable final blockIndex = reader.readUint32(); + assert(blockIndex == expectedBlockIndex++); final blockHash = reader.readBytes(HASH_SIZE); final blockSize = reader.readUint32(); if (blockSize > 0) { - final blockData = reader.readBytes(blockSize).asUint8List(); - if (!ByteUtils.eq(crypto.sha256.convert(blockData).bytes as Uint8List, - blockHash.asUint8List())) { + final blockData = reader.readBytes(blockSize); + if (!ByteUtils.eq( + crypto.sha256.convert(blockData).bytes as Uint8List, blockHash)) { throw KdbxCorruptedFileException(); } yield blockData; @@ -284,23 +285,23 @@ class HashedBlockReader { // static Uint8List writeBlocks(WriterHelper writer) => static void writeBlocks(ReaderHelper reader, WriterHelper writer) { - while (true) { - int blockIndex = 0; + for (int blockIndex = 0;; blockIndex++) { final block = reader.readBytesUpTo(BLOCK_SIZE); if (block.lengthInBytes == 0) { // written all data, write a last empty block. - writer.writeUint32(blockIndex++); - writer.writeBytes(Uint8List.fromList(List.generate(HASH_SIZE, (i) => 0))); - writer.writeUint32(0); + writer.writeUint32(blockIndex); + writer.writeBytes(Uint8List.fromList( + List.generate(HASH_SIZE, (i) => 0))); // hash 32 ** 0x0 + writer.writeUint32(0); // block size = 0 return; } final blockSize = block.lengthInBytes; - final blockHash = crypto.sha256.convert(block.asUint8List()); + final blockHash = crypto.sha256.convert(block); assert(blockHash.bytes.length == HASH_SIZE); - writer.writeUint32(blockIndex++); + writer.writeUint32(blockIndex); writer.writeBytes(blockHash.bytes as Uint8List); writer.writeUint32(blockSize); - writer.writeBytes(block.asUint8List()); + writer.writeBytes(block); } } } diff --git a/lib/src/kdbx_object.dart b/lib/src/kdbx_object.dart index ae07c14..dc32f65 100644 --- a/lib/src/kdbx_object.dart +++ b/lib/src/kdbx_object.dart @@ -13,7 +13,6 @@ import 'package:xml/xml.dart'; final _logger = Logger('kdbx.kdbx_object'); - class ChangeEvent { ChangeEvent({this.object, this.isDirty}); @@ -29,7 +28,7 @@ mixin Changeable { bool _isDirty = false; set isDirty(bool dirty) { - _logger.finest('changing dirty (old:$_isDirty) $dirty'); +// _logger.finest('changing dirty (old:$_isDirty) $dirty'); _isDirty = dirty; _controller.add(ChangeEvent(object: this as T, isDirty: dirty)); } @@ -113,7 +112,7 @@ class KdbxUuid { /// base64 representation of uuid. final String uuid; - ByteBuffer toBytes() => base64.decode(uuid).buffer; + Uint8List toBytes() => base64.decode(uuid); @override String toString() => uuid; diff --git a/pubspec.yaml b/pubspec.yaml index 207e864..40a6c6b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,6 +8,8 @@ environment: sdk: '>=2.4.0 <3.0.0' dependencies: + flutter: + sdk: flutter # path: ^1.6.0 logging: '>=0.11.3+2 <1.0.0' crypto: '>=2.0.0 <3.0.0' @@ -26,5 +28,8 @@ dependencies: logging_appenders: '>=0.1.0 <1.0.0' dev_dependencies: - pedantic: ^1.7.0 - test: ^1.6.0 + flutter_test: + sdk: flutter + pedantic: '>=1.7.0 <2.0.0' + test: '>=1.6.0 <2.0.0' + diff --git a/test/internal/byte_utils_test.dart b/test/internal/byte_utils_test.dart index 23a0677..340478e 100644 --- a/test/internal/byte_utils_test.dart +++ b/test/internal/byte_utils_test.dart @@ -1,4 +1,3 @@ - import 'dart:io'; import 'package:kdbx/src/internal/byte_utils.dart'; @@ -13,5 +12,12 @@ void main() { print('result: ' + ByteUtils.toHexList(writer.output.toBytes())); expect(writer.output.toBytes(), hasLength(4)); }); + test('uint64', () { + final bytes = WriterHelper.singleUint64Bytes(6000); + final read = ReaderHelper.singleUint64(bytes); + print('read: $read'); + expect(read, 6000); + print('bytes: ${ByteUtils.toHexList(bytes)}'); + }); }); } diff --git a/test/pytest/Dockerfile b/test/pytest/Dockerfile new file mode 100644 index 0000000..fb0382b --- /dev/null +++ b/test/pytest/Dockerfile @@ -0,0 +1,7 @@ +FROM python + +RUN apt-get update && apt-get install -y libgcrypt20-dev + +RUN pip install pygcrypt lxml + +WORKDIR /root diff --git a/test/pytest/docker-compose.yml b/test/pytest/docker-compose.yml new file mode 100644 index 0000000..51311cd --- /dev/null +++ b/test/pytest/docker-compose.yml @@ -0,0 +1,7 @@ +version: '3' +services: + py: + build: . + volumes: + - ./:/root + diff --git a/test/kdbx3_decrypt.py b/test/pytest/kdbx3_decrypt.py similarity index 98% rename from test/kdbx3_decrypt.py rename to test/pytest/kdbx3_decrypt.py index 67c4afe..54e80ac 100644 --- a/test/kdbx3_decrypt.py +++ b/test/pytest/kdbx3_decrypt.py @@ -9,7 +9,7 @@ import struct -database = 'FooBar.kdbx' +database = 'test.kdbx' password = b'FooBar' # password = None #keyfile = 'test3.key' @@ -147,6 +147,7 @@ payload_data = b'' while True: # read index of block (4 bytes) block_index = struct.unpack('