Browse Source

started to parse groups and entries.

remove-cryptography-dependency
Herbert Poul 5 years ago
parent
commit
14fe0105ae
  1. 47
      bin/kdbx.dart
  2. 3
      example/kdbx_example.dart
  3. 8
      lib/kdbx.dart
  4. 41
      lib/src/crypto/protected_value.dart
  5. 6
      lib/src/kdbx_base.dart
  6. 21
      lib/src/kdbx_entry.dart
  7. 21
      lib/src/kdbx_format.dart
  8. 26
      lib/src/kdbx_group.dart
  9. 28
      lib/src/kdbx_header.dart
  10. 33
      lib/src/kdbx_object.dart
  11. 2
      pubspec.yaml
  12. 485
      test/FooBar.3.content.xml
  13. BIN
      test/FooBar.kdbx
  14. 10
      test/kdbx_test.dart

47
bin/kdbx.dart

@ -5,6 +5,7 @@ import 'package:args/args.dart';
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import 'package:kdbx/src/crypto/protected_value.dart'; import 'package:kdbx/src/crypto/protected_value.dart';
import 'package:kdbx/src/kdbx_format.dart'; import 'package:kdbx/src/kdbx_format.dart';
import 'package:kdbx/src/kdbx_group.dart';
import 'package:kdbx/src/kdbx_header.dart'; import 'package:kdbx/src/kdbx_header.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:logging_appenders/logging_appenders.dart'; import 'package:logging_appenders/logging_appenders.dart';
@ -36,9 +37,11 @@ void main(List<String> arguments) {
} }
class KdbxCommandRunner extends CommandRunner<void> { class KdbxCommandRunner extends CommandRunner<void> {
KdbxCommandRunner(String executableName, String description) : super(executableName, description) { KdbxCommandRunner(String executableName, String description)
: super(executableName, description) {
argParser.addFlag('verbose', abbr: 'v'); argParser.addFlag('verbose', abbr: 'v');
addCommand(CatCommand()); addCommand(CatCommand());
addCommand(DumpXmlCommand());
} }
@override @override
@ -69,8 +72,10 @@ abstract class KdbxFileCommand extends Command<void> {
usageException('Required argument: --input'); usageException('Required argument: --input');
} }
final bytes = await File(inputFile).readAsBytes(); final bytes = await File(inputFile).readAsBytes();
final password = prompts.get('Password for $inputFile', conceal: true, validate: (str) => str.isNotEmpty); final password = prompts.get('Password for $inputFile',
final file = await KdbxFormat.read(bytes, Credentials(ProtectedValue.fromString(password))); conceal: true, validate: (str) => str.isNotEmpty);
final file = await KdbxFormat.read(
bytes, Credentials(ProtectedValue.fromString(password)));
return runWithFile(file); return runWithFile(file);
} }
@ -78,14 +83,48 @@ abstract class KdbxFileCommand extends Command<void> {
} }
class CatCommand extends KdbxFileCommand { class CatCommand extends KdbxFileCommand {
CatCommand() {
argParser.addFlag('decrypt', help: 'Force decryption of all protected strings.');
}
@override @override
String get description => 'outputs all entries from file.'; String get description => 'outputs all entries from file.';
@override @override
String get name => 'cat'; String get name => 'cat';
bool get forceDecrypt => argResults['decrypt'] as bool;
@override
Future<void> runWithFile(KdbxFile file) async {
catGroup(file.body.rootGroup);
}
void catGroup(KdbxGroup group, {int depth = 0}) {
final indent = ' ' * depth;
print('$indent + ${group.name} (${group.uuid})');
for (final group in group.groups) {
catGroup(group, depth: depth + 1);
}
for (final entry in group.entries) {
final value = entry.strings['Password'];
print('$indent `- ${entry.strings['Title']?.getText()}: ${forceDecrypt ? value?.getText() : value?.toString()}');
}
}
}
class DumpXmlCommand extends KdbxFileCommand {
@override
String get description => 'Outputs the xml body unencrypted.';
@override
String get name => 'dumpXml';
@override
List<String> get aliases => ['dump', 'xml'];
@override @override
Future<void> runWithFile(KdbxFile file) async { Future<void> runWithFile(KdbxFile file) async {
_logger.severe('running'); print(file.body.xmlDocument.toXmlString(pretty: true));
} }
} }

3
example/kdbx_example.dart

@ -1,6 +1,5 @@
import 'package:kdbx/kdbx.dart'; import 'package:kdbx/kdbx.dart';
void main() { void main() {
var awesome = Awesome(); KdbxFormat.read(null, null);
print('awesome: ${awesome.isAwesome}');
} }

8
lib/kdbx.dart

@ -1,8 +1,4 @@
/// Support for doing something awesome. /// dart library for reading keepass file format (kdbx).
///
/// More dartdocs go here.
library kdbx; library kdbx;
export 'src/kdbx_base.dart'; export 'src/kdbx_format.dart';
// TODO: Export any libraries intended for clients of this package.

41
lib/src/crypto/protected_value.dart

@ -4,7 +4,29 @@ import 'dart:typed_data';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
class ProtectedValue { abstract class StringValue {
/// retrieves the (decrypted) stored value.
String getText();
}
class PlainValue implements StringValue {
PlainValue(this.text);
final String text;
@override
String getText() {
return text;
}
@override
String toString() {
return 'PlainValue{text: $text}';
}
}
class ProtectedValue implements StringValue {
ProtectedValue(this._value, this._salt); ProtectedValue(this._value, this._salt);
factory ProtectedValue.fromString(String value) { factory ProtectedValue.fromString(String value) {
@ -20,17 +42,30 @@ class ProtectedValue {
final Uint8List _salt; final Uint8List _salt;
Uint8List get binaryValue => _xor(_value, _salt); Uint8List get binaryValue => _xor(_value, _salt);
Uint8List get hash => sha256.convert(binaryValue).bytes as Uint8List; Uint8List get hash => sha256.convert(binaryValue).bytes as Uint8List;
static Uint8List _randomBytes(int length) { static Uint8List _randomBytes(int length) {
return Uint8List.fromList(List.generate(length, (i) => random.nextInt(0xff))); return Uint8List.fromList(
List.generate(length, (i) => random.nextInt(0xff)));
} }
static Uint8List _xor(Uint8List a, Uint8List b) { static Uint8List _xor(Uint8List a, Uint8List b) {
assert(a.length == b.length); assert(a.length == b.length);
final ret = Uint8List(a.length); final ret = Uint8List(a.length);
for (int i = 0 ; i < a.length ; i++) { for (int i = 0; i < a.length; i++) {
ret[i] = a[i] ^ b[i]; ret[i] = a[i] ^ b[i];
} }
return ret; return ret;
} }
@override
String getText() {
return utf8.decode(binaryValue);
}
@override
String toString() {
return 'ProtectedValue{${base64.encode(hash)}}';
}
} }

6
lib/src/kdbx_base.dart

@ -1,6 +0,0 @@
// TODO: Put public facing types in this file.
/// Checks if you are awesome. Spoiler: you are.
class Awesome {
bool get isAwesome => true;
}

21
lib/src/kdbx_entry.dart

@ -1,5 +1,22 @@
import 'package:kdbx/src/crypto/protected_value.dart';
import 'package:kdbx/src/kdbx_format.dart';
import 'package:kdbx/src/kdbx_group.dart';
import 'package:kdbx/src/kdbx_object.dart';
import 'package:xml/xml.dart';
class KdbxEntry extends KdbxObject {
KdbxEntry.read(this.parent, XmlElement node) : super.read(node) {
strings = Map.fromEntries(node.findElements('String').map((el) {
final key = el.findElements('Key').single.text;
final valueNode = el.findElements('Value').single;
if (valueNode.getAttribute('Protected')?.toLowerCase() == 'true') {
return MapEntry(key, KdbxFile.protectedValueForNode(valueNode));
} else {
return MapEntry(key, PlainValue(valueNode.text));
}
}));
}
class KdbxEntry { KdbxGroup parent;
Map<String, StringValue> strings;
} }

21
lib/src/kdbx_format.dart

@ -7,6 +7,7 @@ import 'package:kdbx/src/crypto/protected_salt_generator.dart';
import 'package:kdbx/src/crypto/protected_value.dart'; import 'package:kdbx/src/crypto/protected_value.dart';
import 'package:kdbx/src/internal/byte_utils.dart'; import 'package:kdbx/src/internal/byte_utils.dart';
import 'package:kdbx/src/internal/crypto_utils.dart'; import 'package:kdbx/src/internal/crypto_utils.dart';
import 'package:kdbx/src/kdbx_group.dart';
import 'package:kdbx/src/kdbx_header.dart'; import 'package:kdbx/src/kdbx_header.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:pointycastle/export.dart'; import 'package:pointycastle/export.dart';
@ -19,24 +20,26 @@ class KdbxFile {
static final protectedValues = Expando<ProtectedValue>(); static final protectedValues = Expando<ProtectedValue>();
static ProtectedValue protectedValueForNode(xml.XmlElement node) {
return protectedValues[node];
}
final Credentials credentials; final Credentials credentials;
final KdbxHeader header; final KdbxHeader header;
final KdbxBody body; final KdbxBody body;
} }
class KdbxBody { class KdbxBody {
KdbxBody(this.xmlDocument, this.meta); KdbxBody(this.xmlDocument, this.meta, this.rootGroup);
final xml.XmlDocument xmlDocument; final xml.XmlDocument xmlDocument;
final KdbxMeta meta; final KdbxMeta meta;
final KdbxGroup rootGroup;
} }
class KdbxMeta { class KdbxMeta {}
}
class KdbxFormat { class KdbxFormat {
static Future<KdbxFile> read(Uint8List input, Credentials credentials) async { static Future<KdbxFile> read(Uint8List input, Credentials credentials) async {
final reader = ReaderHelper(input); final reader = ReaderHelper(input);
final header = await KdbxHeader.read(reader); final header = await KdbxHeader.read(reader);
@ -57,7 +60,8 @@ class KdbxFormat {
final string = utf8.decode(xml); final string = utf8.decode(xml);
return KdbxFile(credentials, header, _loadXml(header, string)); return KdbxFile(credentials, header, _loadXml(header, string));
} else { } else {
return KdbxFile(credentials, header, _loadXml(header, utf8.decode(blocks))); return KdbxFile(
credentials, header, _loadXml(header, utf8.decode(blocks)));
} }
} }
@ -82,9 +86,10 @@ class KdbxFormat {
final keePassFile = document.findElements('KeePassFile').single; final keePassFile = document.findElements('KeePassFile').single;
final meta = keePassFile.findElements('Meta').single; final meta = keePassFile.findElements('Meta').single;
final groupRoot = keePassFile.findElements('Root').single; final root = keePassFile.findElements('Root').single;
final rootGroup = KdbxGroup.read(null, root.findElements('Group').single);
_logger.fine('got meta: ${meta.toXmlString(pretty: true)}'); _logger.fine('got meta: ${meta.toXmlString(pretty: true)}');
return KdbxBody(document, KdbxMeta()); return KdbxBody(document, KdbxMeta(), rootGroup);
} }
static Uint8List _decryptContent( static Uint8List _decryptContent(

26
lib/src/kdbx_group.dart

@ -1,4 +1,28 @@
import 'package:kdbx/src/kdbx_entry.dart';
import 'package:xml/xml.dart';
class KdbxGroup { import 'kdbx_object.dart';
final _builder = XmlBuilder();
class KdbxGroup extends KdbxObject {
KdbxGroup(this.parent) : super.create('Group');
KdbxGroup.read(this.parent, XmlElement node) : super.read(node) {
node
.findElements('Group')
.map((el) => KdbxGroup.read(this, el))
.forEach(groups.add);
node
.findElements('Entry')
.map((el) => KdbxEntry.read(this, el))
.forEach(entries.add);
}
/// null if this is the root group.
final KdbxGroup parent;
final List<KdbxGroup> groups = [];
final List<KdbxEntry> entries = [];
String get name => text('Name') ?? '';
} }

28
lib/src/kdbx_header.dart

@ -51,14 +51,20 @@ class HeaderField {
} }
class KdbxHeader { class KdbxHeader {
KdbxHeader({this.sig1, this.sig2, this.versionMinor, this.versionMajor, this.fields}); KdbxHeader(
{this.sig1,
this.sig2,
this.versionMinor,
this.versionMajor,
this.fields});
static Future<KdbxHeader> read(ReaderHelper reader) async { static Future<KdbxHeader> read(ReaderHelper reader) async {
// reading signature // reading signature
final sig1 = reader.readUint32(); final sig1 = reader.readUint32();
final sig2 = reader.readUint32(); final sig2 = reader.readUint32();
if (!(sig1 == Consts.FileMagic && sig2 == Consts.Sig2Kdbx)) { if (!(sig1 == Consts.FileMagic && sig2 == Consts.Sig2Kdbx)) {
throw UnsupportedError('Unsupported file structure. ${ByteUtils.toHex(sig1)}, ${ByteUtils.toHex(sig2)}'); throw UnsupportedError(
'Unsupported file structure. ${ByteUtils.toHex(sig1)}, ${ByteUtils.toHex(sig2)}');
} }
// reading version // reading version
@ -66,7 +72,8 @@ class KdbxHeader {
final versionMajor = reader.readUint16(); final versionMajor = reader.readUint16();
_logger.finer('Reading version: $versionMajor.$versionMinor'); _logger.finer('Reading version: $versionMajor.$versionMinor');
final headerFields = Map.fromEntries(readField(reader, versionMajor).map((field) => MapEntry(field.field, field))); final headerFields = Map.fromEntries(readField(reader, versionMajor)
.map((field) => MapEntry(field.field, field)));
return KdbxHeader( return KdbxHeader(
sig1: sig1, sig1: sig1,
sig2: sig2, sig2: sig2,
@ -76,10 +83,12 @@ class KdbxHeader {
); );
} }
static Iterable<HeaderField> readField(ReaderHelper reader, int versionMajor) sync* { static Iterable<HeaderField> readField(
ReaderHelper reader, int versionMajor) sync* {
while (true) { while (true) {
final headerId = reader.readUint8(); final headerId = reader.readUint8();
final int bodySize = versionMajor >= 4 ? reader.readUint32() : reader.readUint16(); final int bodySize =
versionMajor >= 4 ? reader.readUint32() : reader.readUint16();
_logger.finer('Read header ${HeaderFields.values[headerId]}'); _logger.finer('Read header ${HeaderFields.values[headerId]}');
final bodyBytes = bodySize > 0 ? reader.readBytes(bodySize) : null; final bodyBytes = bodySize > 0 ? reader.readBytes(bodySize) : null;
if (headerId > 0) { if (headerId > 0) {
@ -108,7 +117,8 @@ class KdbxHeader {
} }
PotectedValueEncryption get innerRandomStreamEncryption => PotectedValueEncryption get innerRandomStreamEncryption =>
PotectedValueEncryption.values[fields[HeaderFields.InnerRandomStreamID].bytes.asUint32List().single]; PotectedValueEncryption.values[
fields[HeaderFields.InnerRandomStreamID].bytes.asUint32List().single];
} }
class Credentials { class Credentials {
@ -148,7 +158,8 @@ class HashedBlockReader {
final blockSize = reader.readUint32(); final blockSize = reader.readUint32();
if (blockSize > 0) { if (blockSize > 0) {
final blockData = reader.readBytes(blockSize).asUint8List(); final blockData = reader.readBytes(blockSize).asUint8List();
if (!ByteUtils.eq(crypto.sha256.convert(blockData).bytes as Uint8List, blockHash.asUint8List())) { if (!ByteUtils.eq(crypto.sha256.convert(blockData).bytes as Uint8List,
blockHash.asUint8List())) {
throw KdbxCorruptedFileException(); throw KdbxCorruptedFileException();
} }
yield blockData; yield blockData;
@ -165,7 +176,8 @@ class ReaderHelper {
final Uint8List data; final Uint8List data;
int pos = 0; int pos = 0;
ByteBuffer _nextByteBuffer(int byteCount) => data.sublist(pos, pos += byteCount).buffer; ByteBuffer _nextByteBuffer(int byteCount) =>
data.sublist(pos, pos += byteCount).buffer;
int readUint32() => _nextByteBuffer(4).asUint32List().first; int readUint32() => _nextByteBuffer(4).asUint32List().first;

33
lib/src/kdbx_object.dart

@ -0,0 +1,33 @@
import 'package:meta/meta.dart';
import 'package:uuid/uuid.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 KdbxObject {
KdbxObject.create(String nodeName)
: uuid = Uuid().v4(),
node = XmlElement(XmlName(nodeName));
KdbxObject.read(this.node) {
uuid = node.findElements('UUID').single.text;
}
final XmlElement node;
String uuid;
@protected
String text(String nodeName) => _opt(nodeName)?.text;
XmlElement _opt(String nodeName) =>
node.findElements(nodeName).singleWhere((x) => true, orElse: () => null);
}

2
pubspec.yaml

@ -13,6 +13,8 @@ dependencies:
crypto: '>=2.0.0 <3.0.0' crypto: '>=2.0.0 <3.0.0'
pointycastle: '>=1.0.1 <2.0.0' pointycastle: '>=1.0.1 <2.0.0'
xml: '>=3.5.0 <4.0.0' xml: '>=3.5.0 <4.0.0'
uuid: '>=2.0.0 <3.0.0'
meta: '>=1.0.0 <2.0.0'
# required for bin/ # required for bin/
args: '>1.5.0 <2.0.0' args: '>1.5.0 <2.0.0'

485
test/FooBar.3.content.xml

@ -0,0 +1,485 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<KeePassFile>
<Meta>
<Generator>KdbxWeb</Generator>
<HeaderHash>DS/kVWzLk+yB13QmD3d3Si1zn9t445cp9ZVnwxhp+ro=</HeaderHash>
<DatabaseName>FooBar</DatabaseName>
<DatabaseNameChanged>2019-08-20T13:16:06Z</DatabaseNameChanged>
<DatabaseDescription/>
<DatabaseDescriptionChanged>2019-08-20T13:15:47Z</DatabaseDescriptionChanged>
<DefaultUserName/>
<DefaultUserNameChanged>2019-08-20T13:15:47Z</DefaultUserNameChanged>
<MaintenanceHistoryDays>365</MaintenanceHistoryDays>
<Color/>
<MasterKeyChanged>2019-08-20T13:16:03Z</MasterKeyChanged>
<MasterKeyChangeRec>-1</MasterKeyChangeRec>
<MasterKeyChangeForce>-1</MasterKeyChangeForce>
<RecycleBinEnabled>True</RecycleBinEnabled>
<RecycleBinUUID>dVSBC/BAx70qcsy6XkrGJA==</RecycleBinUUID>
<RecycleBinChanged>2019-08-20T13:15:47Z</RecycleBinChanged>
<EntryTemplatesGroup>AAAAAAAAAAAAAAAAAAAAAA==</EntryTemplatesGroup>
<EntryTemplatesGroupChanged>2019-08-20T13:15:47Z</EntryTemplatesGroupChanged>
<HistoryMaxItems>10</HistoryMaxItems>
<HistoryMaxSize>6291456</HistoryMaxSize>
<LastSelectedGroup/>
<LastTopVisibleGroup/>
<MemoryProtection>
<ProtectTitle>False</ProtectTitle>
<ProtectUserName>False</ProtectUserName>
<ProtectPassword>True</ProtectPassword>
<ProtectURL>False</ProtectURL>
<ProtectNotes>False</ProtectNotes>
</MemoryProtection>
<CustomIcons/>
<Binaries/>
<CustomData/>
</Meta>
<Root>
<Group>
<UUID>LAQMkihXTkxhA2D2tE40Fg==</UUID>
<Name>FooBar</Name>
<Notes/>
<IconID>49</IconID>
<Times>
<CreationTime>2019-08-20T13:15:47Z</CreationTime>
<LastModificationTime>2019-08-20T13:16:06Z</LastModificationTime>
<LastAccessTime>2019-08-20T13:16:06Z</LastAccessTime>
<ExpiryTime>2019-08-20T13:15:47Z</ExpiryTime>
<Expires>False</Expires>
<UsageCount>0</UsageCount>
<LocationChanged>2019-08-20T13:15:47Z</LocationChanged>
</Times>
<IsExpanded>True</IsExpanded>
<DefaultAutoTypeSequence/>
<EnableAutoType>null</EnableAutoType>
<EnableSearching>null</EnableSearching>
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry>
<Group>
<UUID>dVSBC/BAx70qcsy6XkrGJA==</UUID>
<Name>Recycle Bin</Name>
<Notes/>
<IconID>43</IconID>
<Times>
<CreationTime>2019-08-20T13:15:47Z</CreationTime>
<LastModificationTime>2019-08-20T13:15:47Z</LastModificationTime>
<LastAccessTime>2019-08-20T13:15:47Z</LastAccessTime>
<ExpiryTime>2019-08-20T13:15:47Z</ExpiryTime>
<Expires>False</Expires>
<UsageCount>0</UsageCount>
<LocationChanged>2019-08-20T13:15:47Z</LocationChanged>
</Times>
<IsExpanded>True</IsExpanded>
<DefaultAutoTypeSequence/>
<EnableAutoType>False</EnableAutoType>
<EnableSearching>False</EnableSearching>
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry>
<Group>
<UUID>QQywiz/v9idlTOqUJQsHSw==</UUID>
<Name>Sub Group 2 delete me</Name>
<Notes/>
<IconID>48</IconID>
<Times>
<CreationTime>2019-08-21T20:52:19Z</CreationTime>
<LastModificationTime>2019-08-21T20:52:34Z</LastModificationTime>
<LastAccessTime>2019-08-21T20:52:34Z</LastAccessTime>
<ExpiryTime>2019-08-21T20:52:19Z</ExpiryTime>
<Expires>False</Expires>
<UsageCount>0</UsageCount>
<LocationChanged>2019-08-21T20:52:43Z</LocationChanged>
</Times>
<IsExpanded>True</IsExpanded>
<DefaultAutoTypeSequence/>
<EnableAutoType>null</EnableAutoType>
<EnableSearching>null</EnableSearching>
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry>
</Group>
<Entry>
<UUID>5j0iOoJDoaGNVDsuZ16EkQ==</UUID>
<IconID>0</IconID>
<ForegroundColor/>
<BackgroundColor/>
<OverrideURL/>
<Tags/>
<Times>
<CreationTime>2019-08-21T20:51:50Z</CreationTime>
<LastModificationTime>2019-08-21T20:52:01Z</LastModificationTime>
<LastAccessTime>2019-08-21T20:52:01Z</LastAccessTime>
<ExpiryTime>2019-08-21T20:51:50Z</ExpiryTime>
<Expires>False</Expires>
<UsageCount>0</UsageCount>
<LocationChanged>2019-08-21T20:52:03Z</LocationChanged>
</Times>
<String>
<Key>Title</Key>
<Value>something to delete</Value>
</String>
<String>
<Key>UserName</Key>
<Value>deleteme</Value>
</String>
<String>
<Key>Password</Key>
<Value Protected="True">KHEormXPgg==</Value>
</String>
<String>
<Key>URL</Key>
<Value/>
</String>
<String>
<Key>Notes</Key>
<Value/>
</String>
<AutoType>
<Enabled>True</Enabled>
<DataTransferObfuscation>0</DataTransferObfuscation>
</AutoType>
<History>
<Entry>
<UUID>5j0iOoJDoaGNVDsuZ16EkQ==</UUID>
<IconID>0</IconID>
<ForegroundColor/>
<BackgroundColor/>
<OverrideURL/>
<Tags/>
<Times>
<CreationTime>2019-08-21T20:51:50Z</CreationTime>
<LastModificationTime>2019-08-21T20:51:58Z</LastModificationTime>
<LastAccessTime>2019-08-21T20:51:58Z</LastAccessTime>
<ExpiryTime>2019-08-21T20:51:50Z</ExpiryTime>
<Expires>False</Expires>
<UsageCount>0</UsageCount>
<LocationChanged>2019-08-21T20:51:50Z</LocationChanged>
</Times>
<String>
<Key>Title</Key>
<Value>something to delete</Value>
</String>
<String>
<Key>UserName</Key>
<Value>deleteme</Value>
</String>
<String>
<Key>Password</Key>
<Value Protected="True"/>
</String>
<String>
<Key>URL</Key>
<Value/>
</String>
<String>
<Key>Notes</Key>
<Value/>
</String>
<AutoType>
<Enabled>True</Enabled>
<DataTransferObfuscation>0</DataTransferObfuscation>
</AutoType>
</Entry>
</History>
</Entry>
</Group>
<Group>
<UUID>NUCSPwdmXYRkOdpvLVAK+Q==</UUID>
<Name>First Custom Group</Name>
<Notes/>
<IconID>48</IconID>
<Times>
<CreationTime>2019-08-21T20:49:18Z</CreationTime>
<LastModificationTime>2019-08-21T20:49:31Z</LastModificationTime>
<LastAccessTime>2019-08-21T20:49:31Z</LastAccessTime>
<ExpiryTime>2019-08-21T20:49:18Z</ExpiryTime>
<Expires>False</Expires>
<UsageCount>0</UsageCount>
<LocationChanged>2019-08-21T20:49:18Z</LocationChanged>
</Times>
<IsExpanded>True</IsExpanded>
<DefaultAutoTypeSequence/>
<EnableAutoType>null</EnableAutoType>
<EnableSearching>null</EnableSearching>
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry>
<Entry>
<UUID>EGhdPVJumK8MjlRPaK+bvQ==</UUID>
<IconID>0</IconID>
<ForegroundColor/>
<BackgroundColor/>
<OverrideURL/>
<Tags/>
<Times>
<CreationTime>2019-08-21T20:49:55Z</CreationTime>
<LastModificationTime>2019-08-21T20:51:47Z</LastModificationTime>
<LastAccessTime>2019-08-21T20:51:47Z</LastAccessTime>
<ExpiryTime>2019-08-21T20:49:55Z</ExpiryTime>
<Expires>False</Expires>
<UsageCount>0</UsageCount>
<LocationChanged>2019-08-21T20:49:55Z</LocationChanged>
</Times>
<String>
<Key>Title</Key>
<Value>testing stuff</Value>
</String>
<String>
<Key>UserName</Key>
<Value>umläut üser</Value>
</String>
<String>
<Key>Password</Key>
<Value Protected="True">oTIZFyRSWhcaZElzhb3cvw==</Value>
</String>
<String>
<Key>URL</Key>
<Value/>
</String>
<String>
<Key>Notes</Key>
<Value/>
</String>
<String>
<Key>emojifun</Key>
<Value>😂</Value>
</String>
<String>
<Key>emojifun1</Key>
<Value>blubb</Value>
</String>
<String>
<Key>New Field</Key>
<Value>another new field.</Value>
</String>
<String>
<Key>New Field1</Key>
<Value Protected="True">di8rpVnnLJxwBXiKhPXH</Value>
</String>
<AutoType>
<Enabled>True</Enabled>
<DataTransferObfuscation>0</DataTransferObfuscation>
</AutoType>
<History>
<Entry>
<UUID>EGhdPVJumK8MjlRPaK+bvQ==</UUID>
<IconID>0</IconID>
<ForegroundColor/>
<BackgroundColor/>
<OverrideURL/>
<Tags/>
<Times>
<CreationTime>2019-08-21T20:49:55Z</CreationTime>
<LastModificationTime>2019-08-21T20:50:46Z</LastModificationTime>
<LastAccessTime>2019-08-21T20:50:46Z</LastAccessTime>
<ExpiryTime>2019-08-21T20:49:55Z</ExpiryTime>
<Expires>False</Expires>
<UsageCount>0</UsageCount>
<LocationChanged>2019-08-21T20:49:55Z</LocationChanged>
</Times>
<String>
<Key>Title</Key>
<Value>testing deleting stuff</Value>
</String>
<String>
<Key>UserName</Key>
<Value>umläut üser</Value>
</String>
<String>
<Key>Password</Key>
<Value Protected="True">k3Oqaa7OIHvRgwc/7nXu</Value>
</String>
<String>
<Key>URL</Key>
<Value/>
</String>
<String>
<Key>Notes</Key>
<Value/>
</String>
<String>
<Key>emojifun</Key>
<Value>😂</Value>
</String>
<String>
<Key>emojifun1</Key>
<Value>blubb</Value>
</String>
<AutoType>
<Enabled>True</Enabled>
<DataTransferObfuscation>0</DataTransferObfuscation>
</AutoType>
</Entry>
<Entry>
<UUID>EGhdPVJumK8MjlRPaK+bvQ==</UUID>
<IconID>0</IconID>
<ForegroundColor/>
<BackgroundColor/>
<OverrideURL/>
<Tags/>
<Times>
<CreationTime>2019-08-21T20:49:55Z</CreationTime>
<LastModificationTime>2019-08-21T20:51:06Z</LastModificationTime>
<LastAccessTime>2019-08-21T20:51:06Z</LastAccessTime>
<ExpiryTime>2019-08-21T20:49:55Z</ExpiryTime>
<Expires>False</Expires>
<UsageCount>0</UsageCount>
<LocationChanged>2019-08-21T20:49:55Z</LocationChanged>
</Times>
<String>
<Key>Title</Key>
<Value>testing deleting stuff</Value>
</String>
<String>
<Key>UserName</Key>
<Value>umläut üser</Value>
</String>
<String>
<Key>Password</Key>
<Value Protected="True">KaYaF6SQwa4SpHhxRPT0Cw==</Value>
</String>
<String>
<Key>URL</Key>
<Value/>
</String>
<String>
<Key>Notes</Key>
<Value/>
</String>
<String>
<Key>emojifun</Key>
<Value>😂</Value>
</String>
<String>
<Key>emojifun1</Key>
<Value>blubb</Value>
</String>
<AutoType>
<Enabled>True</Enabled>
<DataTransferObfuscation>0</DataTransferObfuscation>
</AutoType>
</Entry>
</History>
</Entry>
</Group>
<Group>
<UUID>W3rvv7uZya9KAUDF5QFx5Q==</UUID>
<Name>Second Group</Name>
<Notes/>
<IconID>48</IconID>
<Times>
<CreationTime>2019-08-21T20:49:46Z</CreationTime>
<LastModificationTime>2019-08-21T20:49:48Z</LastModificationTime>
<LastAccessTime>2019-08-21T20:49:48Z</LastAccessTime>
<ExpiryTime>2019-08-21T20:49:46Z</ExpiryTime>
<Expires>False</Expires>
<UsageCount>0</UsageCount>
<LocationChanged>2019-08-21T20:49:46Z</LocationChanged>
</Times>
<IsExpanded>True</IsExpanded>
<DefaultAutoTypeSequence/>
<EnableAutoType>null</EnableAutoType>
<EnableSearching>null</EnableSearching>
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry>
<Group>
<UUID>Dqa10jVPgVTLhXCje3BODw==</UUID>
<Name>Sub Group</Name>
<Notes/>
<IconID>48</IconID>
<Times>
<CreationTime>2019-08-21T20:52:08Z</CreationTime>
<LastModificationTime>2019-08-21T20:52:11Z</LastModificationTime>
<LastAccessTime>2019-08-21T20:52:11Z</LastAccessTime>
<ExpiryTime>2019-08-21T20:52:08Z</ExpiryTime>
<Expires>False</Expires>
<UsageCount>0</UsageCount>
<LocationChanged>2019-08-21T20:52:08Z</LocationChanged>
</Times>
<IsExpanded>True</IsExpanded>
<DefaultAutoTypeSequence/>
<EnableAutoType>null</EnableAutoType>
<EnableSearching>null</EnableSearching>
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry>
<Entry>
<UUID>0d2zDwBH5OniOoRWstAQZg==</UUID>
<IconID>0</IconID>
<ForegroundColor/>
<BackgroundColor/>
<OverrideURL/>
<Tags/>
<Times>
<CreationTime>2019-08-21T20:52:48Z</CreationTime>
<LastModificationTime>2019-08-21T20:52:52Z</LastModificationTime>
<LastAccessTime>2019-08-21T20:52:52Z</LastAccessTime>
<ExpiryTime>2019-08-21T20:52:48Z</ExpiryTime>
<Expires>False</Expires>
<UsageCount>0</UsageCount>
<LocationChanged>2019-08-21T20:52:48Z</LocationChanged>
</Times>
<String>
<Key>Title</Key>
<Value>Entry in Sub Group</Value>
</String>
<String>
<Key>UserName</Key>
<Value/>
</String>
<String>
<Key>Password</Key>
<Value Protected="True"/>
</String>
<String>
<Key>URL</Key>
<Value/>
</String>
<String>
<Key>Notes</Key>
<Value/>
</String>
<AutoType>
<Enabled>True</Enabled>
<DataTransferObfuscation>0</DataTransferObfuscation>
</AutoType>
<History/>
</Entry>
</Group>
</Group>
<Entry>
<UUID>RMUeiV24MDcrIY8qQYRAlg==</UUID>
<IconID>0</IconID>
<ForegroundColor/>
<BackgroundColor/>
<OverrideURL/>
<Tags/>
<Times>
<CreationTime>2019-08-21T00:17:25Z</CreationTime>
<LastModificationTime>2019-08-21T00:17:36Z</LastModificationTime>
<LastAccessTime>2019-08-21T00:17:36Z</LastAccessTime>
<ExpiryTime>2019-08-21T00:17:25Z</ExpiryTime>
<Expires>False</Expires>
<UsageCount>0</UsageCount>
<LocationChanged>2019-08-21T00:17:25Z</LocationChanged>
</Times>
<String>
<Key>Title</Key>
<Value>loremipsum.com</Value>
</String>
<String>
<Key>UserName</Key>
<Value>foo</Value>
</String>
<String>
<Key>Password</Key>
<Value Protected="True">CRTX</Value>
</String>
<String>
<Key>URL</Key>
<Value/>
</String>
<String>
<Key>Notes</Key>
<Value/>
</String>
<AutoType>
<Enabled>True</Enabled>
<DataTransferObfuscation>0</DataTransferObfuscation>
</AutoType>
<History/>
</Entry>
</Group>
<DeletedObjects/>
</Root>
</KeePassFile>

BIN
test/FooBar.kdbx

Binary file not shown.

10
test/kdbx_test.dart

@ -12,16 +12,12 @@ void main() {
Logger.root.level = Level.ALL; Logger.root.level = Level.ALL;
Logger.root.onRecord.listen(PrintAppender().logListener()); Logger.root.onRecord.listen(PrintAppender().logListener());
group('A group of tests', () { group('A group of tests', () {
Awesome awesome; setUp(() {});
setUp(() {
awesome = Awesome();
});
test('First Test', () async { test('First Test', () async {
final data = await File('test/FooBar.kdbx').readAsBytes(); final data = await File('test/FooBar.kdbx').readAsBytes();
await KdbxFormat.read(data, Credentials(ProtectedValue.fromString('FooBar'))); await KdbxFormat.read(
expect(awesome.isAwesome, isTrue); data, Credentials(ProtectedValue.fromString('FooBar')));
}); });
}); });
} }

Loading…
Cancel
Save