Browse Source

fixed saving of protected values in history entries.

remove-cryptography-dependency
Herbert Poul 5 years ago
parent
commit
d914ff9024
  1. 9
      lib/src/crypto/protected_value.dart
  2. 35
      lib/src/kdbx_entry.dart
  3. 13
      lib/src/kdbx_format.dart
  4. 19
      lib/src/kdbx_object.dart

9
lib/src/crypto/protected_value.dart

@ -24,6 +24,11 @@ class PlainValue implements StringValue {
return 'PlainValue{text: $text}'; return 'PlainValue{text: $text}';
} }
@override
bool operator ==(dynamic other) => other is PlainValue && other.text == text;
@override
int get hashCode => text.hashCode;
} }
class ProtectedValue implements StringValue { class ProtectedValue implements StringValue {
@ -64,6 +69,10 @@ class ProtectedValue implements StringValue {
return utf8.decode(binaryValue); return utf8.decode(binaryValue);
} }
@override
bool operator ==(dynamic other) =>
other is ProtectedValue && other.getText() == getText();
@override @override
String toString() { String toString() {
return 'ProtectedValue{${base64.encode(hash)}}'; return 'ProtectedValue{${base64.encode(hash)}}';

35
lib/src/kdbx_entry.dart

@ -4,8 +4,11 @@ import 'package:kdbx/src/kdbx_format.dart';
import 'package:kdbx/src/kdbx_group.dart'; import 'package:kdbx/src/kdbx_group.dart';
import 'package:kdbx/src/kdbx_object.dart'; import 'package:kdbx/src/kdbx_object.dart';
import 'package:kdbx/src/kdbx_xml.dart'; import 'package:kdbx/src/kdbx_xml.dart';
import 'package:logging/logging.dart';
import 'package:xml/xml.dart'; import 'package:xml/xml.dart';
final _logger = Logger('kdbx.kdbx_entry');
/// Represents a case insensitive (but case preserving) key. /// Represents a case insensitive (but case preserving) key.
class KdbxKey { class KdbxKey {
KdbxKey(this.key) : _canonicalKey = key.toLowerCase(); KdbxKey(this.key) : _canonicalKey = key.toLowerCase();
@ -22,11 +25,14 @@ class KdbxKey {
} }
class KdbxEntry extends KdbxObject { class KdbxEntry extends KdbxObject {
KdbxEntry.create(KdbxFile file, this.parent) : super.create(file, 'Entry') { KdbxEntry.create(KdbxFile file, this.parent)
: isHistoryEntry = false,
super.create(file, 'Entry') {
icon.set(KdbxIcon.Key); icon.set(KdbxIcon.Key);
} }
KdbxEntry.read(this.parent, XmlElement node) : super.read(node) { KdbxEntry.read(this.parent, XmlElement node, {this.isHistoryEntry = false})
: super.read(node) {
_strings.addEntries(node.findElements(KdbxXml.NODE_STRING).map((el) { _strings.addEntries(node.findElements(KdbxXml.NODE_STRING).map((el) {
final key = KdbxKey(el.findElements(KdbxXml.NODE_KEY).single.text); final key = KdbxKey(el.findElements(KdbxXml.NODE_KEY).single.text);
final valueNode = el.findElements(KdbxXml.NODE_VALUE).single; final valueNode = el.findElements(KdbxXml.NODE_VALUE).single;
@ -39,14 +45,14 @@ class KdbxEntry extends KdbxObject {
})); }));
} }
final bool isHistoryEntry;
List<KdbxEntry> _history; List<KdbxEntry> _history;
List<KdbxEntry> get history => List<KdbxEntry> get history => _history ??= (() {
_history ??
(() {
return _historyElement return _historyElement
.findElements('Entry') .findElements('Entry')
.map((entry) => KdbxEntry.read(parent, entry)) .map((entry) => KdbxEntry.read(parent, entry, isHistoryEntry: true))
.toList(); .toList();
})(); })();
@ -70,26 +76,33 @@ class KdbxEntry extends KdbxObject {
@override @override
XmlElement toXml() { XmlElement toXml() {
final el = super.toXml(); final el = super.toXml();
XmlUtils.removeChildrenByName(el, KdbxXml.NODE_STRING);
XmlUtils.removeChildrenByName(el, KdbxXml.NODE_HISTORY);
el.children.removeWhere( el.children.removeWhere(
(e) => e is XmlElement && e.name.local == KdbxXml.NODE_STRING); (e) => e is XmlElement && e.name.local == KdbxXml.NODE_STRING);
el.children.addAll(stringEntries.map((stringEntry) { el.children.addAll(stringEntries.map((stringEntry) {
final value = XmlElement(XmlName(KdbxXml.NODE_VALUE)); final value = XmlElement(XmlName(KdbxXml.NODE_VALUE));
if (stringEntry.value is ProtectedValue) { if (stringEntry.value is ProtectedValue) {
value.attributes value.attributes
.add(XmlAttribute(XmlName(KdbxXml.ATTR_PROTECTED), 'true')); .add(XmlAttribute(XmlName(KdbxXml.ATTR_PROTECTED), 'True'));
KdbxFile.setProtectedValueForNode( KdbxFile.setProtectedValueForNode(
value, stringEntry.value as ProtectedValue); value, stringEntry.value as ProtectedValue);
} else { } else if (stringEntry.value is StringValue) {
value.children.add(XmlText(stringEntry.value.getText())); value.children.add(XmlText(stringEntry.value.getText()));
} }
return XmlElement(XmlName(KdbxXml.NODE_STRING)) return XmlElement(XmlName(KdbxXml.NODE_STRING))
..children.addAll([ ..children.addAll([
XmlElement(XmlName(KdbxXml.ATTR_PROTECTED)),
XmlElement(XmlName(KdbxXml.NODE_KEY)) XmlElement(XmlName(KdbxXml.NODE_KEY))
..children.add(XmlText(stringEntry.key.key)), ..children.add(XmlText(stringEntry.key.key)),
value, value,
]); ]);
})); }));
if (!isHistoryEntry) {
el.children.add(
XmlElement(XmlName(KdbxXml.NODE_HISTORY))
..children.addAll(history.map((e) => e.toXml())),
);
}
return el; return el;
} }
@ -104,6 +117,10 @@ class KdbxEntry extends KdbxObject {
StringValue getString(KdbxKey key) => _strings[key]; StringValue getString(KdbxKey key) => _strings[key];
void setString(KdbxKey key, StringValue value) { void setString(KdbxKey key, StringValue value) {
if (_strings[key] == value) {
_logger.finest('Value did not change for $key');
return;
}
isDirty = true; isDirty = true;
_strings[key] = value; _strings[key] = value;
} }

13
lib/src/kdbx_format.dart

@ -150,16 +150,21 @@ class KdbxBody extends KdbxNode {
xml.XmlDocument generateXml(ProtectedSaltGenerator saltGenerator) { xml.XmlDocument generateXml(ProtectedSaltGenerator saltGenerator) {
final rootGroupNode = rootGroup.toXml(); final rootGroupNode = rootGroup.toXml();
// update protected values... // update protected values...
for (final el in rootGroupNode for (final el in rootGroupNode.findAllElements(KdbxXml.NODE_VALUE).where(
.findAllElements('Value') (el) =>
.where((el) => el.getAttribute('Protected')?.toLowerCase() == 'true')) { el.getAttribute(KdbxXml.ATTR_PROTECTED)?.toLowerCase() == 'true')) {
final pv = KdbxFile.protectedValues[el]; final pv = KdbxFile.protectedValues[el];
if (pv != null) { if (pv != null) {
final newValue = saltGenerator.encryptToBase64(pv.getText()); final newValue = saltGenerator.encryptToBase64(pv.getText());
el.children.clear(); el.children.clear();
el.children.add(xml.XmlText(newValue)); el.children.add(xml.XmlText(newValue));
} else { } else {
_logger.warning('Unable to find protected value for $el ${el.parent}'); // assert((() {
// _logger.severe('Unable to find protected value for $el ${el.parent.parent} (children: ${el.children})');
// return false;
// })());
// this is always an error, not just during debug.
throw StateError('Unable to find protected value for $el ${el.parent}');
} }
} }

19
lib/src/kdbx_object.dart

@ -19,13 +19,16 @@ class ChangeEvent<T> {
mixin Changeable<T> { mixin Changeable<T> {
final _controller = StreamController<ChangeEvent<T>>.broadcast(); final _controller = StreamController<ChangeEvent<T>>.broadcast();
Stream<ChangeEvent<T>> get changes => _controller.stream; Stream<ChangeEvent<T>> get changes => _controller.stream;
bool _isDirty = false; bool _isDirty = false;
set isDirty(bool dirty) { set isDirty(bool dirty) {
_isDirty = dirty; _isDirty = dirty;
_controller.add(ChangeEvent(object: this as T, isDirty: dirty)); _controller.add(ChangeEvent(object: this as T, isDirty: dirty));
} }
bool get isDirty => _isDirty; bool get isDirty => _isDirty;
} }
@ -50,11 +53,14 @@ abstract class KdbxNode with Changeable<KdbxNode> {
abstract class KdbxObject extends KdbxNode { abstract class KdbxObject extends KdbxNode {
KdbxObject.create(this.file, String nodeName) KdbxObject.create(this.file, String nodeName)
: times = KdbxTimes.create(), super.create(nodeName) { : times = KdbxTimes.create(),
super.create(nodeName) {
_uuid.set(KdbxUuid.random()); _uuid.set(KdbxUuid.random());
} }
KdbxObject.read(XmlElement node) : times = KdbxTimes.read(node.findElements('Times').single),super.read(node); 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. /// the file this object is part of. will be set AFTER loading, etc.
KdbxFile file; KdbxFile file;
@ -62,6 +68,7 @@ abstract class KdbxObject extends KdbxNode {
final KdbxTimes times; final KdbxTimes times;
KdbxUuid get uuid => _uuid.get(); KdbxUuid get uuid => _uuid.get();
UuidNode get _uuid => UuidNode(this, 'UUID'); UuidNode get _uuid => UuidNode(this, 'UUID');
IconNode get icon => IconNode(this, 'IconID'); IconNode get icon => IconNode(this, 'IconID');
@ -88,12 +95,12 @@ abstract class KdbxObject extends KdbxNode {
class KdbxUuid { class KdbxUuid {
const KdbxUuid(this.uuid); const KdbxUuid(this.uuid);
KdbxUuid.random() : this(base64.encode(uuidGenerator.parse(uuidGenerator.v4())));
static final Uuid uuidGenerator = Uuid(options: <String, dynamic>{ KdbxUuid.random()
'grng': UuidUtil.cryptoRNG : this(base64.encode(uuidGenerator.parse(uuidGenerator.v4())));
});
static final Uuid uuidGenerator =
Uuid(options: <String, dynamic>{'grng': UuidUtil.cryptoRNG});
/// base64 representation of uuid. /// base64 representation of uuid.
final String uuid; final String uuid;

Loading…
Cancel
Save