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. 39
      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: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_header.dart';
import 'package:logging/logging.dart';
import 'package:logging_appenders/logging_appenders.dart';
@ -36,9 +37,11 @@ void main(List<String> arguments) {
}
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');
addCommand(CatCommand());
addCommand(DumpXmlCommand());
}
@override
@ -69,8 +72,10 @@ abstract class KdbxFileCommand extends Command<void> {
usageException('Required argument: --input');
}
final bytes = await File(inputFile).readAsBytes();
final password = prompts.get('Password for $inputFile', conceal: true, validate: (str) => str.isNotEmpty);
final file = await KdbxFormat.read(bytes, Credentials(ProtectedValue.fromString(password)));
final password = prompts.get('Password for $inputFile',
conceal: true, validate: (str) => str.isNotEmpty);
final file = await KdbxFormat.read(
bytes, Credentials(ProtectedValue.fromString(password)));
return runWithFile(file);
}
@ -78,14 +83,48 @@ abstract class KdbxFileCommand extends Command<void> {
}
class CatCommand extends KdbxFileCommand {
CatCommand() {
argParser.addFlag('decrypt', help: 'Force decryption of all protected strings.');
}
@override
String get description => 'outputs all entries from file.';
@override
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
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';
void main() {
var awesome = Awesome();
print('awesome: ${awesome.isAwesome}');
KdbxFormat.read(null, null);
}

8
lib/kdbx.dart

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

39
lib/src/crypto/protected_value.dart

@ -4,7 +4,29 @@ import 'dart:typed_data';
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);
factory ProtectedValue.fromString(String value) {
@ -20,11 +42,14 @@ class ProtectedValue {
final Uint8List _salt;
Uint8List get binaryValue => _xor(_value, _salt);
Uint8List get hash => sha256.convert(binaryValue).bytes as Uint8List;
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) {
assert(a.length == b.length);
final ret = Uint8List(a.length);
@ -33,4 +58,14 @@ class ProtectedValue {
}
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/internal/byte_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:logging/logging.dart';
import 'package:pointycastle/export.dart';
@ -19,24 +20,26 @@ class KdbxFile {
static final protectedValues = Expando<ProtectedValue>();
static ProtectedValue protectedValueForNode(xml.XmlElement node) {
return protectedValues[node];
}
final Credentials credentials;
final KdbxHeader header;
final KdbxBody body;
}
class KdbxBody {
KdbxBody(this.xmlDocument, this.meta);
KdbxBody(this.xmlDocument, this.meta, this.rootGroup);
final xml.XmlDocument xmlDocument;
final KdbxMeta meta;
final KdbxGroup rootGroup;
}
class KdbxMeta {
}
class KdbxMeta {}
class KdbxFormat {
static Future<KdbxFile> read(Uint8List input, Credentials credentials) async {
final reader = ReaderHelper(input);
final header = await KdbxHeader.read(reader);
@ -57,7 +60,8 @@ class KdbxFormat {
final string = utf8.decode(xml);
return KdbxFile(credentials, header, _loadXml(header, string));
} 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 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)}');
return KdbxBody(document, KdbxMeta());
return KdbxBody(document, KdbxMeta(), rootGroup);
}
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 {
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 {
// reading signature
final sig1 = reader.readUint32();
final sig2 = reader.readUint32();
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
@ -66,7 +72,8 @@ class KdbxHeader {
final versionMajor = reader.readUint16();
_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(
sig1: sig1,
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) {
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]}');
final bodyBytes = bodySize > 0 ? reader.readBytes(bodySize) : null;
if (headerId > 0) {
@ -108,7 +117,8 @@ class KdbxHeader {
}
PotectedValueEncryption get innerRandomStreamEncryption =>
PotectedValueEncryption.values[fields[HeaderFields.InnerRandomStreamID].bytes.asUint32List().single];
PotectedValueEncryption.values[
fields[HeaderFields.InnerRandomStreamID].bytes.asUint32List().single];
}
class Credentials {
@ -148,7 +158,8 @@ class HashedBlockReader {
final blockSize = reader.readUint32();
if (blockSize > 0) {
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();
}
yield blockData;
@ -165,7 +176,8 @@ class ReaderHelper {
final Uint8List data;
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;

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'
pointycastle: '>=1.0.1 <2.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/
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.onRecord.listen(PrintAppender().logListener());
group('A group of tests', () {
Awesome awesome;
setUp(() {
awesome = Awesome();
});
setUp(() {});
test('First Test', () async {
final data = await File('test/FooBar.kdbx').readAsBytes();
await KdbxFormat.read(data, Credentials(ProtectedValue.fromString('FooBar')));
expect(awesome.isAwesome, isTrue);
await KdbxFormat.read(
data, Credentials(ProtectedValue.fromString('FooBar')));
});
});
}

Loading…
Cancel
Save