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.
## 0.4.1

2
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,

34
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, HeaderField> _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)));
}

11
lib/src/kdbx_xml.dart

@ -58,10 +58,17 @@ abstract class KdbxSubNode<T> {
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<XmlNode> {
void removeElementsByName(String name) {
removeWhere(
(element) => element is XmlElement && element.name.local == name);
}
}
abstract class KdbxSubTextNode<T> extends KdbxSubNode<T> {

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: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<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 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));

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