Browse Source

implement kdbx3 to 4 upgrade, use kdbx4 by default

pull/3/head
Herbert Poul 4 years ago
parent
commit
b44dd7a56a
  1. 3
      CHANGELOG.md
  2. 2
      lib/src/kdbx_format.dart
  3. 34
      lib/src/kdbx_header.dart
  4. 11
      lib/src/kdbx_xml.dart
  5. 10
      test/internal/test_utils.dart
  6. 2
      test/kdbx_history_test.dart
  7. 21
      test/kdbx_upgrade_test.dart

3
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. - Implemented support for custom icons.
## 0.4.1 ## 0.4.1

2
lib/src/kdbx_format.dart

@ -335,7 +335,7 @@ class KdbxFormat {
String generator, String generator,
KdbxHeader header, KdbxHeader header,
}) { }) {
header ??= KdbxHeader.createV3(); header ??= argon2 == null ? KdbxHeader.createV3() : 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,

34
lib/src/kdbx_header.dart

@ -97,8 +97,23 @@ const _headerFieldsByVersion = {
HeaderFields.ProtectedStreamKey: [KdbxVersion.V3], HeaderFields.ProtectedStreamKey: [KdbxVersion.V3],
HeaderFields.StreamStartBytes: [KdbxVersion.V3], HeaderFields.StreamStartBytes: [KdbxVersion.V3],
HeaderFields.InnerRandomStreamID: [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 { enum InnerHeaderFields {
EndOfHeader, EndOfHeader,
InnerRandomStreamID, InnerRandomStreamID,
@ -229,6 +244,10 @@ class KdbxHeader {
// TODO make sure default algorithm is "secure" engouh. Or whether we should // TODO make sure default algorithm is "secure" engouh. Or whether we should
// use like [SecureRandom] from PointyCastle? // use like [SecureRandom] from PointyCastle?
_setHeaderField(HeaderFields.MasterSeed, ByteUtils.randomBytes(32)); _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) { if (version.major == KdbxVersion.V3.major) {
_setHeaderField(HeaderFields.TransformSeed, ByteUtils.randomBytes(32)); _setHeaderField(HeaderFields.TransformSeed, ByteUtils.randomBytes(32));
_setHeaderField(HeaderFields.StreamStartBytes, ByteUtils.randomBytes(32)); _setHeaderField(HeaderFields.StreamStartBytes, ByteUtils.randomBytes(32));
@ -264,6 +283,9 @@ class KdbxHeader {
writer.writeUint16(version.major); writer.writeUint16(version.major);
for (final field for (final field
in HeaderFields.values.where((f) => f != HeaderFields.EndOfHeader)) { 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); _writeField(writer, field);
} }
fields[HeaderFields.EndOfHeader] = fields[HeaderFields.EndOfHeader] =
@ -338,9 +360,9 @@ class KdbxHeader {
HeaderFields.CompressionFlags: HeaderFields.CompressionFlags:
WriterHelper.singleUint32Bytes(Compression.gzip.id), WriterHelper.singleUint32Bytes(Compression.gzip.id),
HeaderFields.KdfParameters: _createKdfDefaultParameters().write(), HeaderFields.KdfParameters: _createKdfDefaultParameters().write(),
HeaderFields.InnerRandomStreamID: WriterHelper.singleUint32Bytes( // HeaderFields.InnerRandomStreamID: WriterHelper.singleUint32Bytes(
ProtectedValueEncryption.values // ProtectedValueEncryption.values
.indexOf(ProtectedValueEncryption.chaCha20)), // .indexOf(ProtectedValueEncryption.chaCha20)),
}); });
static Map<HeaderFields, HeaderField> _headerFields( static Map<HeaderFields, HeaderField> _headerFields(
@ -483,8 +505,10 @@ class KdbxHeader {
_logger.fine('Creating kdf parameters.'); _logger.fine('Creating kdf parameters.');
writeKdfParameters(_createKdfDefaultParameters()); writeKdfParameters(_createKdfDefaultParameters());
} }
_setHeaderField( fields.remove(HeaderFields.TransformRounds);
HeaderFields.InnerRandomStreamID, fields.remove(HeaderFields.InnerRandomStreamID);
_setInnerHeaderField(
InnerHeaderFields.InnerRandomStreamID,
WriterHelper.singleUint32Bytes(ProtectedValueEncryption.values WriterHelper.singleUint32Bytes(ProtectedValueEncryption.values
.indexOf(ProtectedValueEncryption.chaCha20))); .indexOf(ProtectedValueEncryption.chaCha20)));
} }

11
lib/src/kdbx_xml.dart

@ -58,9 +58,16 @@ abstract class KdbxSubNode<T> {
void set(T value); void set(T value);
void remove() { void remove() {
for (final el in node.node.findElements(name)) { node.modify(() {
el.parentElement.children.remove(el); node.node.children.removeElementsByName(name);
});
} }
}
extension on List<XmlNode> {
void removeElementsByName(String name) {
removeWhere(
(element) => element is XmlElement && element.name.local == name);
} }
} }

10
test/internal/test_utils.dart

@ -4,9 +4,6 @@ import 'dart:typed_data';
import 'package:argon2_ffi_base/argon2_ffi_base.dart'; import 'package:argon2_ffi_base/argon2_ffi_base.dart';
import 'package:kdbx/kdbx.dart'; import 'package:kdbx/kdbx.dart';
// ignore_for_file: non_constant_identifier_names
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
final _logger = Logger('test_utils'); final _logger = Logger('test_utils');
@ -48,4 +45,11 @@ class TestUtil {
return await readKdbxFileBytes(await file.save(), return await readKdbxFileBytes(await file.save(),
credentials: file.credentials); credentials: file.credentials);
} }
static Future<void> 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');
}
} }

2
test/kdbx_history_test.dart

@ -57,7 +57,7 @@ void main() {
final dirtyExpect = StreamExpect(file.dirtyObjectsChanged); final dirtyExpect = StreamExpect(file.dirtyObjectsChanged);
{ {
final first = file.body.rootGroup.entries.first; 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); expect(first.getString(TestUtil.keyTitle).getText(), valueOrig);
await dirtyExpect.expectNext({first}, () { await dirtyExpect.expectNext({first}, () {
first.setString(TestUtil.keyTitle, PlainValue(value1)); first.setString(TestUtil.keyTitle, PlainValue(value1));

21
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);
});
});
}
Loading…
Cancel
Save