Browse Source

support modifying of entries, storing entry history, ..

remove-cryptography-dependency
Herbert Poul 5 years ago
parent
commit
8643a7b30b
  1. 2
      lib/kdbx.dart
  2. 56
      lib/src/kdbx_entry.dart
  3. 13
      lib/src/kdbx_format.dart
  4. 3
      lib/src/kdbx_group.dart
  5. 13
      lib/src/kdbx_object.dart
  6. 8
      lib/src/kdbx_xml.dart
  7. 4
      test/kdbx_test.dart

2
lib/kdbx.dart

@ -1,7 +1,7 @@
/// dart library for reading keepass file format (kdbx).
library kdbx;
export 'src/crypto/protected_value.dart' show ProtectedValue, StringValue;
export 'src/crypto/protected_value.dart' show ProtectedValue, StringValue, PlainValue;
export 'src/kdbx_entry.dart';
export 'src/kdbx_format.dart';
export 'src/kdbx_header.dart'

56
lib/src/kdbx_entry.dart

@ -3,6 +3,7 @@ import 'package:kdbx/src/kdbx_consts.dart';
import 'package:kdbx/src/kdbx_format.dart';
import 'package:kdbx/src/kdbx_group.dart';
import 'package:kdbx/src/kdbx_object.dart';
import 'package:kdbx/src/kdbx_xml.dart';
import 'package:xml/xml.dart';
/// Represents a case insensitive (but case preserving) key.
@ -21,15 +22,16 @@ class KdbxKey {
}
class KdbxEntry extends KdbxObject {
KdbxEntry.create(this.parent) : super.create('Entry') {
KdbxEntry.create(KdbxFile file, this.parent) : super.create(file, 'Entry') {
icon.set(KdbxIcon.Key);
}
KdbxEntry.read(this.parent, XmlElement node) : super.read(node) {
_strings.addEntries(node.findElements('String').map((el) {
final key = KdbxKey(el.findElements('Key').single.text);
final valueNode = el.findElements('Value').single;
if (valueNode.getAttribute('Protected')?.toLowerCase() == 'true') {
_strings.addEntries(node.findElements(KdbxXml.NODE_STRING).map((el) {
final key = KdbxKey(el.findElements(KdbxXml.NODE_KEY).single.text);
final valueNode = el.findElements(KdbxXml.NODE_VALUE).single;
if (valueNode.getAttribute(KdbxXml.ATTR_PROTECTED)?.toLowerCase() ==
'true') {
return MapEntry(key, KdbxFile.protectedValueForNode(valueNode));
} else {
return MapEntry(key, PlainValue(valueNode.text));
@ -37,22 +39,52 @@ class KdbxEntry extends KdbxObject {
}));
}
List<KdbxEntry> _history;
List<KdbxEntry> get history =>
_history ??
(() {
return _historyElement
.findElements('Entry')
.map((entry) => KdbxEntry.read(parent, entry))
.toList();
})();
XmlElement get _historyElement => node
.findElements(KdbxXml.NODE_HISTORY)
.singleWhere((_) => true, orElse: () {
final el = XmlElement(XmlName(KdbxXml.NODE_HISTORY));
node.children.add(el);
return el;
});
@override
set isDirty(bool newDirty) {
if (!isDirty && newDirty) {
final history = _historyElement;
history.children.add(toXml());
}
super.isDirty = newDirty;
}
@override
XmlElement toXml() {
final el = super.toXml();
el.children.removeWhere((e) => e is XmlElement && e.name.local == 'String');
el.children.removeWhere(
(e) => e is XmlElement && e.name.local == KdbxXml.NODE_STRING);
el.children.addAll(stringEntries.map((stringEntry) {
final value = XmlElement(XmlName('Value'));
final value = XmlElement(XmlName(KdbxXml.NODE_VALUE));
if (stringEntry.value is ProtectedValue) {
value.attributes.add(XmlAttribute(XmlName('Protected'), 'true'));
value.attributes
.add(XmlAttribute(XmlName(KdbxXml.ATTR_PROTECTED), 'true'));
KdbxFile.setProtectedValueForNode(
value, stringEntry.value as ProtectedValue);
} else {
value.children.add(XmlText(stringEntry.value.getText()));
}
return XmlElement(XmlName('String'))
return XmlElement(XmlName(KdbxXml.NODE_STRING))
..children.addAll([
XmlElement(XmlName('Key'))
XmlElement(XmlName(KdbxXml.ATTR_PROTECTED))
..children.add(XmlText(stringEntry.key.key)),
value,
]);
@ -65,11 +97,13 @@ class KdbxEntry extends KdbxObject {
// Map<KdbxKey, StringValue> get strings => UnmodifiableMapView(_strings);
Iterable<MapEntry<KdbxKey, StringValue>> get stringEntries => _strings.entries;
Iterable<MapEntry<KdbxKey, StringValue>> get stringEntries =>
_strings.entries;
StringValue getString(KdbxKey key) => _strings[key];
void setString(KdbxKey key, StringValue value) {
isDirty = true;
_strings[key] = value;
}

13
lib/src/kdbx_format.dart

@ -6,7 +6,6 @@ import 'package:convert/convert.dart' as convert;
import 'package:crypto/crypto.dart' as crypto;
import 'package:kdbx/src/crypto/protected_salt_generator.dart';
import 'package:kdbx/src/crypto/protected_value.dart';
import 'package:kdbx/src/internal/async_utils.dart';
import 'package:kdbx/src/internal/byte_utils.dart';
import 'package:kdbx/src/internal/crypto_utils.dart';
import 'package:kdbx/src/kdbx_group.dart';
@ -36,7 +35,11 @@ class Credentials {
}
class KdbxFile {
KdbxFile(this.credentials, this.header, this.body);
KdbxFile(this.credentials, this.header, this.body) {
for (final obj in _allObjects) {
obj.file = this;
}
}
static final protectedValues = Expando<ProtectedValue>();
@ -52,6 +55,7 @@ class KdbxFile {
final Credentials credentials;
final KdbxHeader header;
final KdbxBody body;
final Set<KdbxObject> dirtyObjects = {};
Uint8List save() {
assert(header.versionMajor == 3);
@ -68,6 +72,7 @@ class KdbxFile {
(crypto.sha256.convert(writer.output.toBytes()).bytes as Uint8List)
.buffer);
body.writeV3(writer, this, gen);
dirtyObjects.clear();
return output.toBytes();
}
@ -76,6 +81,10 @@ class KdbxFile {
.cast<KdbxObject>()
.followedBy(body.rootGroup.getAllEntries());
void dirtyObject(KdbxObject kdbxObject) {
dirtyObjects.add(kdbxObject);
}
// void _subscribeToChildren() {
// final allObjects = _allObjects;
// for (final obj in allObjects) {

3
lib/src/kdbx_group.dart

@ -1,3 +1,4 @@
import 'package:kdbx/kdbx.dart';
import 'package:kdbx/src/internal/async_utils.dart';
import 'package:kdbx/src/kdbx_consts.dart';
import 'package:kdbx/src/kdbx_entry.dart';
@ -9,7 +10,7 @@ import 'kdbx_object.dart';
class KdbxGroup extends KdbxObject {
KdbxGroup.create({@required this.parent, @required String name})
: super.create('Group') {
: super.create(parent?.file, 'Group') {
this.name.set(name);
icon.set(KdbxIcon.Folder);
expanded.set(true);

13
lib/src/kdbx_object.dart

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:kdbx/src/kdbx_format.dart';
import 'package:kdbx/src/kdbx_times.dart';
import 'package:kdbx/src/kdbx_xml.dart';
import 'package:meta/meta.dart';
@ -30,7 +31,7 @@ mixin Changeable<T> {
abstract class KdbxNode with Changeable<KdbxNode> {
KdbxNode.create(String nodeName) : node = XmlElement(XmlName(nodeName)) {
isDirty = true;
_isDirty = true;
}
KdbxNode.read(this.node);
@ -48,13 +49,16 @@ abstract class KdbxNode with Changeable<KdbxNode> {
}
abstract class KdbxObject extends KdbxNode {
KdbxObject.create(String nodeName)
KdbxObject.create(this.file, String nodeName)
: times = KdbxTimes.create(), super.create(nodeName) {
_uuid.set(KdbxUuid.random());
}
KdbxObject.read(XmlElement node) : times = KdbxTimes.read(node.findElements('Times').single),super.read(node);
/// the file this object is part of. will be set AFTER loading, etc.
KdbxFile file;
final KdbxTimes times;
KdbxUuid get uuid => _uuid.get();
@ -65,7 +69,10 @@ abstract class KdbxObject extends KdbxNode {
@override
set isDirty(bool dirty) {
super.isDirty = dirty;
times.modifiedNow();
if (dirty) {
times.modifiedNow();
file.dirtyObject(this);
}
}
@override

8
lib/src/kdbx_xml.dart

@ -6,6 +6,14 @@ import 'package:kdbx/src/kdbx_consts.dart';
import 'package:meta/meta.dart';
import 'package:xml/xml.dart';
class KdbxXml {
static const NODE_STRING = 'String';
static const NODE_KEY = 'Key';
static const NODE_VALUE = 'Value';
static const ATTR_PROTECTED = 'Protected';
static const NODE_HISTORY = 'History';
}
abstract class KdbxSubNode<T> {
KdbxSubNode(this.node, this.name);

4
test/kdbx_test.dart

@ -43,7 +43,7 @@ void main() {
test('Create Entry', () {
final kdbx = KdbxFormat.create(Credentials(ProtectedValue.fromString('FooBar')), 'CreateTest');
final rootGroup = kdbx.body.rootGroup;
final entry = KdbxEntry.create(rootGroup);
final entry = KdbxEntry.create(kdbx, rootGroup);
rootGroup.addEntry(entry);
entry.setString(KdbxKey('Password'), ProtectedValue.fromString('LoremIpsum'));
print(kdbx.body.generateXml(FakeProtectedSaltGenerator()).toXmlString(pretty: true));
@ -56,7 +56,7 @@ void main() {
final Uint8List saved = (() {
final kdbx = KdbxFormat.create(credentials, 'CreateTest');
final rootGroup = kdbx.body.rootGroup;
final entry = KdbxEntry.create(rootGroup);
final entry = KdbxEntry.create(kdbx, rootGroup);
rootGroup.addEntry(entry);
entry.setString(
KdbxKey('Password'), ProtectedValue.fromString('LoremIpsum'));

Loading…
Cancel
Save