From b44dd7a56af9ed70a353c1f23b8becb662acfa74 Mon Sep 17 00:00:00 2001 From: Herbert Poul Date: Tue, 18 Aug 2020 13:47:54 +0200 Subject: [PATCH] implement kdbx3 to 4 upgrade, use kdbx4 by default --- CHANGELOG.md | 3 ++- lib/src/kdbx_format.dart | 2 +- lib/src/kdbx_header.dart | 34 +++++++++++++++++++++++++++++----- lib/src/kdbx_xml.dart | 13 ++++++++++--- test/internal/test_utils.dart | 10 +++++++--- test/kdbx_history_test.dart | 2 +- test/kdbx_upgrade_test.dart | 21 +++++++++++++++++++++ 7 files changed, 71 insertions(+), 14 deletions(-) create mode 100644 test/kdbx_upgrade_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 74c0b48..b192127 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ -## Unreleased +## preparing 1.0.0 +- Use kdbx 4.x by default when creating new files. - Implemented support for custom icons. ## 0.4.1 diff --git a/lib/src/kdbx_format.dart b/lib/src/kdbx_format.dart index be8f4b9..9c2fbda 100644 --- a/lib/src/kdbx_format.dart +++ b/lib/src/kdbx_format.dart @@ -335,7 +335,7 @@ class KdbxFormat { String generator, KdbxHeader header, }) { - header ??= KdbxHeader.createV3(); + header ??= argon2 == null ? KdbxHeader.createV3() : KdbxHeader.createV4(); final ctx = KdbxReadWriteContext(binaries: [], header: header); final meta = KdbxMeta.create( databaseName: name, diff --git a/lib/src/kdbx_header.dart b/lib/src/kdbx_header.dart index ea6f446..848447c 100644 --- a/lib/src/kdbx_header.dart +++ b/lib/src/kdbx_header.dart @@ -97,8 +97,23 @@ const _headerFieldsByVersion = { HeaderFields.ProtectedStreamKey: [KdbxVersion.V3], HeaderFields.StreamStartBytes: [KdbxVersion.V3], HeaderFields.InnerRandomStreamID: [KdbxVersion.V3], + HeaderFields.KdfParameters: [KdbxVersion.V4], + HeaderFields.PublicCustomData: [KdbxVersion.V4], }; +bool _isHeaderFieldInVersion(HeaderFields field, KdbxVersion version) { + final f = _headerFieldsByVersion[field]; + if (f == null || f.isEmpty) { + return true; + } + for (final v in f) { + if (v.major == version.major) { + return true; + } + } + return false; +} + enum InnerHeaderFields { EndOfHeader, InnerRandomStreamID, @@ -229,6 +244,10 @@ class KdbxHeader { // TODO make sure default algorithm is "secure" engouh. Or whether we should // use like [SecureRandom] from PointyCastle? _setHeaderField(HeaderFields.MasterSeed, ByteUtils.randomBytes(32)); + fields.remove(HeaderFields.TransformSeed); + fields.remove(HeaderFields.StreamStartBytes); + fields.remove(HeaderFields.ProtectedStreamKey); + fields.remove(HeaderFields.EncryptionIV); if (version.major == KdbxVersion.V3.major) { _setHeaderField(HeaderFields.TransformSeed, ByteUtils.randomBytes(32)); _setHeaderField(HeaderFields.StreamStartBytes, ByteUtils.randomBytes(32)); @@ -264,6 +283,9 @@ class KdbxHeader { writer.writeUint16(version.major); for (final field in HeaderFields.values.where((f) => f != HeaderFields.EndOfHeader)) { + if (!_isHeaderFieldInVersion(field, version) && fields[field] != null) { + _logger.warning('Did not expect header field $field in $version'); + } _writeField(writer, field); } fields[HeaderFields.EndOfHeader] = @@ -338,9 +360,9 @@ class KdbxHeader { HeaderFields.CompressionFlags: WriterHelper.singleUint32Bytes(Compression.gzip.id), HeaderFields.KdfParameters: _createKdfDefaultParameters().write(), - HeaderFields.InnerRandomStreamID: WriterHelper.singleUint32Bytes( - ProtectedValueEncryption.values - .indexOf(ProtectedValueEncryption.chaCha20)), +// HeaderFields.InnerRandomStreamID: WriterHelper.singleUint32Bytes( +// ProtectedValueEncryption.values +// .indexOf(ProtectedValueEncryption.chaCha20)), }); static Map _headerFields( @@ -483,8 +505,10 @@ class KdbxHeader { _logger.fine('Creating kdf parameters.'); writeKdfParameters(_createKdfDefaultParameters()); } - _setHeaderField( - HeaderFields.InnerRandomStreamID, + fields.remove(HeaderFields.TransformRounds); + fields.remove(HeaderFields.InnerRandomStreamID); + _setInnerHeaderField( + InnerHeaderFields.InnerRandomStreamID, WriterHelper.singleUint32Bytes(ProtectedValueEncryption.values .indexOf(ProtectedValueEncryption.chaCha20))); } diff --git a/lib/src/kdbx_xml.dart b/lib/src/kdbx_xml.dart index 16bad8b..4503a85 100644 --- a/lib/src/kdbx_xml.dart +++ b/lib/src/kdbx_xml.dart @@ -58,9 +58,16 @@ abstract class KdbxSubNode { void set(T value); void remove() { - for (final el in node.node.findElements(name)) { - el.parentElement.children.remove(el); - } + node.modify(() { + node.node.children.removeElementsByName(name); + }); + } +} + +extension on List { + void removeElementsByName(String name) { + removeWhere( + (element) => element is XmlElement && element.name.local == name); } } diff --git a/test/internal/test_utils.dart b/test/internal/test_utils.dart index ee1d0f5..12735af 100644 --- a/test/internal/test_utils.dart +++ b/test/internal/test_utils.dart @@ -4,9 +4,6 @@ import 'dart:typed_data'; import 'package:argon2_ffi_base/argon2_ffi_base.dart'; import 'package:kdbx/kdbx.dart'; - -// ignore_for_file: non_constant_identifier_names - import 'package:logging/logging.dart'; final _logger = Logger('test_utils'); @@ -48,4 +45,11 @@ class TestUtil { return await readKdbxFileBytes(await file.save(), credentials: file.credentials); } + + static Future saveTestOutput(String name, KdbxFile file) async { + final bytes = await file.save(); + final outFile = File('test_output_$name.kdbx'); + await outFile.writeAsBytes(bytes); + _logger.info('Written to $outFile'); + } } diff --git a/test/kdbx_history_test.dart b/test/kdbx_history_test.dart index f9171bc..5df2dac 100644 --- a/test/kdbx_history_test.dart +++ b/test/kdbx_history_test.dart @@ -57,7 +57,7 @@ void main() { final dirtyExpect = StreamExpect(file.dirtyObjectsChanged); { final first = file.body.rootGroup.entries.first; - expect(file.header.versionMajor, 3); + expect(file.header.version.major, 3); expect(first.getString(TestUtil.keyTitle).getText(), valueOrig); await dirtyExpect.expectNext({first}, () { first.setString(TestUtil.keyTitle, PlainValue(value1)); diff --git a/test/kdbx_upgrade_test.dart b/test/kdbx_upgrade_test.dart new file mode 100644 index 0000000..4f6c4ee --- /dev/null +++ b/test/kdbx_upgrade_test.dart @@ -0,0 +1,21 @@ +import 'package:kdbx/src/kdbx_header.dart'; +import 'package:logging_appenders/logging_appenders.dart'; +import 'package:test/test.dart'; + +import 'internal/test_utils.dart'; + +void main() { + PrintAppender.setupLogging(); + group('Test upgrade from v3 to v4', () { + final format = TestUtil.kdbxFormat(); + test('Read v3, write v4', () async { + final file = + await TestUtil.readKdbxFile('test/FooBar.kdbx', password: 'FooBar'); + expect(file.header.version, KdbxVersion.V3_1); + file.upgrade(KdbxVersion.V4.major); + final v4 = await TestUtil.saveAndRead(file); + expect(v4.header.version, KdbxVersion.V4); + await TestUtil.saveTestOutput('kdbx4upgrade', v4); + }); + }); +}