Browse Source

generate times node (access times, etc.)

remove-cryptography-dependency
Herbert Poul 5 years ago
parent
commit
42bb6e61c4
  1. 3
      lib/src/kdbx_entry.dart
  2. 11
      lib/src/kdbx_format.dart
  3. 3
      lib/src/kdbx_group.dart
  4. 3
      lib/src/kdbx_header.dart
  5. 33
      lib/src/kdbx_object.dart
  6. 32
      lib/src/kdbx_times.dart
  7. 76
      lib/src/kdbx_xml.dart
  8. 1
      pubspec.yaml
  9. 7
      test/kdbx_test.dart

3
lib/src/kdbx_entry.dart

@ -38,8 +38,9 @@ class KdbxEntry extends KdbxObject {
}));
}
@override
XmlElement toXml() {
final el = node.copy() as XmlElement;
final el = super.toXml();
el.children.removeWhere((e) => e is XmlElement && e.name.local == 'String');
el.children.addAll(strings.entries.map((stringEntry) {
final value = XmlElement(XmlName('Value'));

11
lib/src/kdbx_format.dart

@ -92,7 +92,7 @@ class KdbxBody extends KdbxNode {
meta.headerHash.set(
(crypto.sha256.convert(writer.output.toBytes()).bytes as Uint8List)
.buffer);
final xml = toXml(saltGenerator);
final xml = generateXml(saltGenerator);
final xmlBytes = utf8.encode(xml.toXmlString());
final Uint8List compressedBytes = (kdbxFile.header.compression == Compression.gzip ?
GZipCodec().encode(xmlBytes) : xmlBytes) as Uint8List;
@ -108,7 +108,7 @@ class KdbxBody extends KdbxNode {
writer.writeBytes(encrypted);
}
xml.XmlDocument toXml(ProtectedSaltGenerator saltGenerator) {
xml.XmlDocument generateXml(ProtectedSaltGenerator saltGenerator) {
final rootGroupNode = rootGroup.toXml();
// update protected values...
for (final el in rootGroupNode
@ -150,8 +150,10 @@ class KdbxMeta extends KdbxNode {
Base64Node get headerHash => Base64Node(this, 'HeaderHash');
@override
// ignore: unnecessary_overrides
xml.XmlElement toXml() {
return node;
return super.toXml();
}
}
@ -223,7 +225,7 @@ class KdbxFormat {
decryptCipher.init(false,
ParametersWithIV(KeyParameter(masterKey), encryptionIv.asUint8List()));
final paddedDecrypted = AesHelper.processBlocks(decryptCipher, encryptedPayload);
final decrypted = paddedDecrypted;//AesHelper.unpad(paddedDecrypted);
final decrypted = AesHelper.unpad(paddedDecrypted);
final streamStart = header.fields[HeaderFields.StreamStartBytes].bytes;
@ -236,6 +238,7 @@ class KdbxFormat {
decrypted.sublist(0, streamStart.lengthInBytes))) {
throw KdbxInvalidKeyException();
}
// ignore: unnecessary_cast
final content = decrypted.sublist(streamStart.lengthInBytes) as Uint8List;
return content;
}

3
lib/src/kdbx_group.dart

@ -24,8 +24,9 @@ class KdbxGroup extends KdbxObject {
.forEach(_entries.add);
}
@override
XmlElement toXml() {
final el = node.copy() as XmlElement;
final el = super.toXml();
XmlUtils.removeChildrenByName(el, 'Group');
XmlUtils.removeChildrenByName(el, 'Entry');
el.children.addAll(groups.map((g) => g.toXml()));

3
lib/src/kdbx_header.dart

@ -1,4 +1,3 @@
import 'dart:math';
import 'dart:typed_data';
import 'package:crypto/crypto.dart' as crypto;
@ -6,7 +5,6 @@ import 'package:kdbx/src/internal/byte_utils.dart';
import 'package:kdbx/src/internal/consts.dart';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:pointycastle/api.dart';
final _logger = Logger('kdbx.header');
@ -266,6 +264,7 @@ class HashedBlockReader {
static Iterable<Uint8List> readNextBlock(ReaderHelper reader) sync* {
while (true) {
// ignore: unused_local_variable
final blockIndex = reader.readUint32();
final blockHash = reader.readBytes(HASH_SIZE);
final blockSize = reader.readUint32();

33
lib/src/kdbx_object.dart

@ -1,22 +1,13 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:kdbx/src/kdbx_times.dart';
import 'package:kdbx/src/kdbx_xml.dart';
import 'package:meta/meta.dart';
import 'package:uuid/uuid.dart';
import 'package:uuid/uuid_util.dart';
import 'package:xml/xml.dart';
class KdbxTimes {
KdbxTimes.read(this.node);
XmlElement node;
DateTime get creationTime => _readTime('CreationTime');
DateTime _readTime(String nodeName) =>
DateTime.parse(node.findElements(nodeName).single.text);
}
abstract class KdbxNode {
KdbxNode.create(String nodeName) : node = XmlElement(XmlName(nodeName));
@ -29,21 +20,35 @@ abstract class KdbxNode {
KdbxSubTextNode textNode(String nodeName) => StringNode(this, nodeName);
@mustCallSuper
XmlElement toXml() {
final el = node.copy() as XmlElement;
return el;
}
}
abstract class KdbxObject extends KdbxNode {
KdbxObject.create(String nodeName)
: super.create(nodeName) {
: times = KdbxTimes.create(), super.create(nodeName) {
_uuid.set(KdbxUuid.random());
}
KdbxObject.read(XmlElement node) : super.read(node);
KdbxObject.read(XmlElement node) : times = KdbxTimes.read(node.findElements('Times').single),super.read(node);
final KdbxTimes times;
KdbxUuid get uuid => _uuid.get();
UuidNode get _uuid => UuidNode(this, 'UUID');
IconNode get icon => IconNode(this, 'IconID');
@override
XmlElement toXml() {
final el = super.toXml();
XmlUtils.removeChildrenByName(el, 'Times');
el.children.add(times.toXml());
return el;
}
}
class KdbxUuid {

32
lib/src/kdbx_times.dart

@ -0,0 +1,32 @@
import 'package:clock/clock.dart';
import 'package:kdbx/src/kdbx_object.dart';
import 'package:kdbx/src/kdbx_xml.dart';
import 'package:xml/xml.dart';
class KdbxTimes extends KdbxNode {
KdbxTimes.create() : super.create('Times') {
final now = clock.now().toUtc();
creationTime.set(now);
lastModificationTime.set(now);
lastAccessTime.set(now);
expiryTime.set(now);
expires.set(false);
usageCount.set(0);
locationChanged.set(now);
}
KdbxTimes.read(XmlElement node) : super.read(node);
DateTimeUtcNode get creationTime => DateTimeUtcNode(this, 'CreationTime');
DateTimeUtcNode get lastModificationTime => DateTimeUtcNode(this, 'CreationTime');
DateTimeUtcNode get lastAccessTime => DateTimeUtcNode(this, 'CreationTime');
DateTimeUtcNode get expiryTime => DateTimeUtcNode(this, 'CreationTime');
BooleanNode get expires => BooleanNode(this, 'Expires');
IntNode get usageCount => IntNode(this, 'Usagecount');
DateTimeUtcNode get locationChanged => DateTimeUtcNode(this, 'LocationChanged');
void accessedNow() {
lastAccessTime.set(clock.now());
}
}

76
lib/src/kdbx_xml.dart

@ -26,8 +26,9 @@ abstract class KdbxSubTextNode<T> extends KdbxSubNode<T> {
@protected
T decode(String value);
XmlElement _opt(String nodeName) =>
node.node.findElements(nodeName).singleWhere((x) => true, orElse: () => null);
XmlElement _opt(String nodeName) => node.node
.findElements(nodeName)
.singleWhere((x) => true, orElse: () => null);
@override
T get() {
@ -58,6 +59,16 @@ abstract class KdbxSubTextNode<T> extends KdbxSubNode<T> {
}
}
class IntNode extends KdbxSubTextNode<int> {
IntNode(KdbxNode node, String name) : super(node, name);
@override
int decode(String value) => int.tryParse(value);
@override
String encode(int value) => value.toString();
}
class StringNode extends KdbxSubTextNode<String> {
StringNode(KdbxNode node, String name) : super(node, name);
@ -104,20 +115,73 @@ class BooleanNode extends KdbxSubTextNode<bool> {
@override
bool decode(String value) {
switch (value) {
case 'null': return null;
case 'true': return true;
case 'false': return false;
case 'null':
return null;
case 'true':
return true;
case 'false':
return false;
}
throw KdbxCorruptedFileException('Invalid boolean value $value for $name');
}
@override
String encode(bool value) => value ? 'true' : 'false';
}
class DateTimeUtcNode extends KdbxSubTextNode<DateTime> {
DateTimeUtcNode(KdbxNode node, String name) : super(node, name);
@override
DateTime decode(String value) => DateTime.parse(value);
@override
String encode(DateTime value) {
assert(value.isUtc);
// TODO for kdbx v4 we need to support binary/base64
return DateTimeUtils.toIso8601StringSeconds(value);
}
}
class XmlUtils {
static void removeChildrenByName(XmlNode node, String name) {
node.children.removeWhere((node) => node is XmlElement && node.name.local == name);
node.children
.removeWhere((node) => node is XmlElement && node.name.local == name);
}
}
class DateTimeUtils {
static String toIso8601StringSeconds(DateTime dateTime) {
final String y = _fourDigits(dateTime.year);
final String m = _twoDigits(dateTime.month);
final String d = _twoDigits(dateTime.hour);
final String h = _twoDigits(dateTime.hour);
final String min = _twoDigits(dateTime.minute);
final String sec = _twoDigits(dateTime.second);
return '$y-$m-${d}T$h:$min:${sec}Z';
}
static String _fourDigits(int n) {
final absN = n.abs();
final sign = n < 0 ? '-' : '';
// ignore: prefer_single_quotes
if (absN >= 1000) {
return '$n';
}
if (absN >= 100) {
return '${sign}0$absN';
}
if (absN >= 10) {
return '${sign}00$absN';
}
return '${sign}000$absN';
}
static String _twoDigits(int n) {
if (n >= 10) {
return '$n';
}
return '0$n';
}
}

1
pubspec.yaml

@ -15,6 +15,7 @@ dependencies:
xml: '>=3.5.0 <4.0.0'
uuid: '>=2.0.0 <3.0.0'
meta: '>=1.0.0 <2.0.0'
clock: '>=1.0.0 <2.0.0'
collection: '>=1.14.0 <2.0.0'

7
test/kdbx_test.dart

@ -38,7 +38,7 @@ void main() {
expect(kdbx.body.rootGroup, isNotNull);
expect(kdbx.body.rootGroup.name.get(), 'CreateTest');
expect(kdbx.body.meta.databaseName.get(), 'CreateTest');
print(kdbx.body.toXml(FakeProtectedSaltGenerator()).toXmlString(pretty: true));
print(kdbx.body.generateXml(FakeProtectedSaltGenerator()).toXmlString(pretty: true));
});
test('Create Entry', () {
final kdbx = KdbxFormat.create(Credentials(ProtectedValue.fromString('FooBar')), 'CreateTest');
@ -46,14 +46,14 @@ void main() {
final entry = KdbxEntry.create(rootGroup);
rootGroup.addEntry(entry);
entry.setString(KdbxKey('Password'), ProtectedValue.fromString('LoremIpsum'));
print(kdbx.body.toXml(FakeProtectedSaltGenerator()).toXmlString(pretty: true));
print(kdbx.body.generateXml(FakeProtectedSaltGenerator()).toXmlString(pretty: true));
});
});
group('Integration', () {
test('Simple save and load', () {
final credentials = Credentials(ProtectedValue.fromString('FooBar'));
Uint8List saved = (() {
final Uint8List saved = (() {
final kdbx = KdbxFormat.create(credentials, 'CreateTest');
final rootGroup = kdbx.body.rootGroup;
final entry = KdbxEntry.create(rootGroup);
@ -67,6 +67,7 @@ void main() {
final kdbx = KdbxFormat.read(saved, credentials);
expect(kdbx.body.rootGroup.entries.first.strings[KdbxKey('Password')].getText(), 'LoremIpsum');
File('test.kdbx').writeAsBytesSync(saved);
});
});
}

Loading…
Cancel
Save