Browse Source

extract kdbx_file.dart into it's own file. cache recycleBin, and do not resolve it every time by uuid.

remove-cryptography-dependency
Herbert Poul 5 years ago
parent
commit
4134d5e018
  1. 2
      example/pubspec.lock
  2. 1
      lib/kdbx.dart
  3. 23
      lib/src/kdbx_dao.dart
  4. 2
      lib/src/kdbx_entry.dart
  5. 111
      lib/src/kdbx_file.dart
  6. 126
      lib/src/kdbx_format.dart
  7. 10
      lib/src/kdbx_header.dart
  8. 2
      lib/src/kdbx_object.dart
  9. 12
      lib/src/kdbx_xml.dart

2
example/pubspec.lock

@ -117,7 +117,7 @@ packages:
path: ".." path: ".."
relative: true relative: true
source: path source: path
version: "0.3.0" version: "0.3.1"
logging: logging:
dependency: transitive dependency: transitive
description: description:

1
lib/kdbx.dart

@ -8,6 +8,7 @@ export 'src/kdbx_consts.dart';
export 'src/kdbx_custom_data.dart'; export 'src/kdbx_custom_data.dart';
export 'src/kdbx_dao.dart' show KdbxDao; export 'src/kdbx_dao.dart' show KdbxDao;
export 'src/kdbx_entry.dart'; export 'src/kdbx_entry.dart';
export 'src/kdbx_file.dart';
export 'src/kdbx_format.dart'; export 'src/kdbx_format.dart';
export 'src/kdbx_group.dart'; export 'src/kdbx_group.dart';
export 'src/kdbx_header.dart' export 'src/kdbx_header.dart'

23
lib/src/kdbx_dao.dart

@ -1,4 +1,5 @@
import 'package:kdbx/kdbx.dart'; import 'package:kdbx/kdbx.dart';
import 'package:kdbx/src/kdbx_file.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
@ -32,28 +33,6 @@ extension KdbxDao on KdbxFile {
return group; return group;
} }
/// Returns the recycle bin, if it exists, null otherwise.
KdbxGroup get recycleBin {
final uuid = body.meta.recycleBinUUID.get();
if (uuid?.isNil != false) {
return null;
}
try {
return findGroupByUuid(uuid);
} catch (e, stackTrace) {
_logger.warning(() {
final groupDebug = body.rootGroup
.getAllGroups()
.map((g) => '${g.uuid}: ${g.name}')
.join('\n');
return 'All Groups: $groupDebug';
});
_logger.severe('Inconsistency error, uuid $uuid not found in groups.', e,
stackTrace);
rethrow;
}
}
KdbxGroup getRecycleBinOrCreate() { KdbxGroup getRecycleBinOrCreate() {
return recycleBin ?? _createRecycleBin(); return recycleBin ?? _createRecycleBin();
} }

2
lib/src/kdbx_entry.dart

@ -1,6 +1,6 @@
import 'package:kdbx/src/crypto/protected_value.dart'; import 'package:kdbx/src/crypto/protected_value.dart';
import 'package:kdbx/src/kdbx_consts.dart'; import 'package:kdbx/src/kdbx_consts.dart';
import 'package:kdbx/src/kdbx_format.dart'; import 'package:kdbx/src/kdbx_file.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';

111
lib/src/kdbx_file.dart

@ -0,0 +1,111 @@
import 'dart:async';
import 'dart:typed_data';
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:kdbx/src/kdbx_object.dart';
import 'package:logging/logging.dart';
import 'package:xml/xml.dart' as xml;
import 'package:kdbx/src/kdbx_dao.dart';
final _logger = Logger('kdbx_file');
class KdbxFile {
KdbxFile(this.kdbxFormat, this.credentials, this.header, this.body) {
for (final obj in _allObjects) {
obj.file = this;
}
}
static final protectedValues = Expando<ProtectedValue>();
static ProtectedValue protectedValueForNode(xml.XmlElement node) {
return protectedValues[node];
}
static void setProtectedValueForNode(
xml.XmlElement node, ProtectedValue value) {
protectedValues[node] = value;
}
final KdbxFormat kdbxFormat;
final Credentials credentials;
final KdbxHeader header;
final KdbxBody body;
final Set<KdbxObject> dirtyObjects = {};
final StreamController<Set<KdbxObject>> _dirtyObjectsChanged =
StreamController<Set<KdbxObject>>.broadcast();
Stream<Set<KdbxObject>> get dirtyObjectsChanged =>
_dirtyObjectsChanged.stream;
Future<Uint8List> save() async {
return kdbxFormat.save(this);
}
/// Marks all dirty objects as clean. Called by [KdbxFormat.save].
void onSaved() {
dirtyObjects.clear();
_dirtyObjectsChanged.add(dirtyObjects);
}
Iterable<KdbxObject> get _allObjects => body.rootGroup
.getAllGroups()
.cast<KdbxObject>()
.followedBy(body.rootGroup.getAllEntries());
void dirtyObject(KdbxObject kdbxObject) {
dirtyObjects.add(kdbxObject);
_dirtyObjectsChanged.add(dirtyObjects);
}
void dispose() {
_dirtyObjectsChanged.close();
}
KdbxGroup _recycleBin;
/// Returns the recycle bin, if it exists, null otherwise.
KdbxGroup get recycleBin => _recycleBin ??= _findRecycleBin();
KdbxGroup _findRecycleBin() {
final uuid = body.meta.recycleBinUUID.get();
if (uuid?.isNil != false) {
return null;
}
try {
return findGroupByUuid(uuid);
} catch (e, stackTrace) {
_logger.warning(() {
final groupDebug = body.rootGroup
.getAllGroups()
.map((g) => '${g.uuid}: ${g.name}')
.join('\n');
return 'All Groups: $groupDebug';
});
_logger.severe('Inconsistency error, uuid $uuid not found in groups.', e,
stackTrace);
rethrow;
}
}
// void _subscribeToChildren() {
// final allObjects = _allObjects;
// for (final obj in allObjects) {
// _subscriptions.handle(obj.changes.listen((event) {
// if (event.isDirty) {
// isDirty = true;
// if (event.object is KdbxGroup) {
// Future(() {
// // resubscribe, just in case some child groups/entries have changed.
// _subscriptions.cancelSubscriptions();
// _subscribeToChildren();
// });
// }
// }
// }));
// }
// }
}

126
lib/src/kdbx_format.dart

@ -14,6 +14,7 @@ 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/consts.dart'; import 'package:kdbx/src/internal/consts.dart';
import 'package:kdbx/src/internal/crypto_utils.dart'; import 'package:kdbx/src/internal/crypto_utils.dart';
import 'package:kdbx/src/kdbx_file.dart';
import 'package:kdbx/src/kdbx_group.dart'; import 'package:kdbx/src/kdbx_group.dart';
import 'package:kdbx/src/kdbx_header.dart'; import 'package:kdbx/src/kdbx_header.dart';
import 'package:kdbx/src/kdbx_meta.dart'; import 'package:kdbx/src/kdbx_meta.dart';
@ -120,98 +121,6 @@ class HashCredentials implements Credentials {
Uint8List getHash() => hash; Uint8List getHash() => hash;
} }
class KdbxFile {
KdbxFile(this.kdbxFormat, this.credentials, this.header, this.body) {
for (final obj in _allObjects) {
obj.file = this;
}
}
static final protectedValues = Expando<ProtectedValue>();
static ProtectedValue protectedValueForNode(xml.XmlElement node) {
return protectedValues[node];
}
static void setProtectedValueForNode(
xml.XmlElement node, ProtectedValue value) {
protectedValues[node] = value;
}
final KdbxFormat kdbxFormat;
final Credentials credentials;
final KdbxHeader header;
final KdbxBody body;
final Set<KdbxObject> dirtyObjects = {};
final StreamController<Set<KdbxObject>> _dirtyObjectsChanged =
StreamController<Set<KdbxObject>>.broadcast();
Stream<Set<KdbxObject>> get dirtyObjectsChanged =>
_dirtyObjectsChanged.stream;
Future<Uint8List> save() async {
final output = BytesBuilder();
final writer = WriterHelper(output);
header.generateSalts();
header.write(writer);
final headerHash =
(crypto.sha256.convert(writer.output.toBytes()).bytes as Uint8List);
if (header.versionMajor <= 3) {
final streamKey = header.fields[HeaderFields.ProtectedStreamKey].bytes;
final gen = ProtectedSaltGenerator(streamKey);
body.meta.headerHash.set(headerHash.buffer);
await body.writeV3(writer, this, gen);
} else if (header.versionMajor <= 4) {
final headerBytes = writer.output.toBytes();
writer.writeBytes(headerHash);
final gen = kdbxFormat._createProtectedSaltGenerator(header);
final keys = await kdbxFormat._computeKeysV4(header, credentials);
final headerHmac = kdbxFormat._getHeaderHmac(headerBytes, keys.hmacKey);
writer.writeBytes(headerHmac.bytes as Uint8List);
body.writeV4(writer, this, gen, keys);
} else {
throw UnsupportedError('Unsupported version ${header.versionMajor}');
}
dirtyObjects.clear();
_dirtyObjectsChanged.add(dirtyObjects);
return output.toBytes();
}
Iterable<KdbxObject> get _allObjects => body.rootGroup
.getAllGroups()
.cast<KdbxObject>()
.followedBy(body.rootGroup.getAllEntries());
void dirtyObject(KdbxObject kdbxObject) {
dirtyObjects.add(kdbxObject);
_dirtyObjectsChanged.add(dirtyObjects);
}
void dispose() {
_dirtyObjectsChanged.close();
}
// void _subscribeToChildren() {
// final allObjects = _allObjects;
// for (final obj in allObjects) {
// _subscriptions.handle(obj.changes.listen((event) {
// if (event.isDirty) {
// isDirty = true;
// if (event.object is KdbxGroup) {
// Future(() {
// // resubscribe, just in case some child groups/entries have changed.
// _subscriptions.cancelSubscriptions();
// _subscribeToChildren();
// });
// }
// }
// }));
// }
// }
}
class KdbxBody extends KdbxNode { class KdbxBody extends KdbxNode {
KdbxBody.create(this.meta, this.rootGroup) : super.create('KeePassFile') { KdbxBody.create(this.meta, this.rootGroup) : super.create('KeePassFile') {
node.children.add(meta.node); node.children.add(meta.node);
@ -379,6 +288,39 @@ class KdbxFormat {
} }
} }
Future<Uint8List> save(KdbxFile file) async {
final body = file.body;
final header = file.header;
final output = BytesBuilder();
final writer = WriterHelper(output);
header.generateSalts();
header.write(writer);
final headerHash =
(crypto.sha256.convert(writer.output.toBytes()).bytes as Uint8List);
if (file.header.versionMajor <= 3) {
final streamKey =
file.header.fields[HeaderFields.ProtectedStreamKey].bytes;
final gen = ProtectedSaltGenerator(streamKey);
body.meta.headerHash.set(headerHash.buffer);
await body.writeV3(writer, file, gen);
} else if (header.versionMajor <= 4) {
final headerBytes = writer.output.toBytes();
writer.writeBytes(headerHash);
final gen = _createProtectedSaltGenerator(header);
final keys = await _computeKeysV4(header, file.credentials);
final headerHmac = _getHeaderHmac(headerBytes, keys.hmacKey);
writer.writeBytes(headerHmac.bytes as Uint8List);
body.writeV4(writer, file, gen, keys);
} else {
throw UnsupportedError('Unsupported version ${header.versionMajor}');
}
file.onSaved();
return output.toBytes();
}
Future<KdbxFile> _loadV3( Future<KdbxFile> _loadV3(
KdbxHeader header, ReaderHelper reader, Credentials credentials) async { KdbxHeader header, ReaderHelper reader, Credentials credentials) async {
// _getMasterKeyV3(header, credentials); // _getMasterKeyV3(header, credentials);

10
lib/src/kdbx_header.dart

@ -150,7 +150,7 @@ class KdbxHeader {
} }
void _validate() { void _validate() {
for (HeaderFields required in _requiredFields(versionMajor)) { for (final required in _requiredFields(versionMajor)) {
if (fields[required] == null) { if (fields[required] == null) {
throw KdbxCorruptedFileException('Missing header $required'); throw KdbxCorruptedFileException('Missing header $required');
} }
@ -351,14 +351,14 @@ class KdbxHeader {
List<TE> fields, T createField(TE field, Uint8List bytes)) sync* { List<TE> fields, T createField(TE field, Uint8List bytes)) sync* {
while (true) { while (true) {
final headerId = reader.readUint8(); final headerId = reader.readUint8();
final int bodySize = final bodySize =
versionMajor >= 4 ? reader.readUint32() : reader.readUint16(); versionMajor >= 4 ? reader.readUint32() : reader.readUint16();
// _logger.fine('Reading header with id $headerId (size: $bodySize)}'); // _logger.fine('Reading header with id $headerId (size: $bodySize)}');
final bodyBytes = bodySize > 0 ? reader.readBytes(bodySize) : null; final bodyBytes = bodySize > 0 ? reader.readBytes(bodySize) : null;
// _logger.finer( // _logger.finer(
// 'Read header ${fields[headerId]}: ${ByteUtils.toHexList(bodyBytes)}'); // 'Read header ${fields[headerId]}: ${ByteUtils.toHexList(bodyBytes)}');
if (headerId > 0) { if (headerId > 0) {
final TE field = fields[headerId]; final field = fields[headerId];
yield createField(field, bodyBytes); yield createField(field, bodyBytes);
/* else { /* else {
if (field == InnerHeaderFields.InnerRandomStreamID) { if (field == InnerHeaderFields.InnerRandomStreamID) {
@ -448,7 +448,7 @@ class HashedBlockReader {
Uint8List.fromList(readNextBlock(reader).expand((x) => x).toList()); Uint8List.fromList(readNextBlock(reader).expand((x) => x).toList());
static Iterable<Uint8List> readNextBlock(ReaderHelper reader) sync* { static Iterable<Uint8List> readNextBlock(ReaderHelper reader) sync* {
int expectedBlockIndex = 0; var expectedBlockIndex = 0;
while (true) { while (true) {
// ignore: unused_local_variable // ignore: unused_local_variable
final blockIndex = reader.readUint32(); final blockIndex = reader.readUint32();
@ -471,7 +471,7 @@ class HashedBlockReader {
// static Uint8List writeBlocks(WriterHelper writer) => // static Uint8List writeBlocks(WriterHelper writer) =>
static void writeBlocks(ReaderHelper reader, WriterHelper writer) { static void writeBlocks(ReaderHelper reader, WriterHelper writer) {
for (int blockIndex = 0;; blockIndex++) { for (var blockIndex = 0;; blockIndex++) {
final block = reader.readBytesUpTo(BLOCK_SIZE); final block = reader.readBytesUpTo(BLOCK_SIZE);
if (block.lengthInBytes == 0) { if (block.lengthInBytes == 0) {
// written all data, write a last empty block. // written all data, write a last empty block.

2
lib/src/kdbx_object.dart

@ -2,7 +2,7 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:kdbx/src/kdbx_format.dart'; import 'package:kdbx/src/kdbx_file.dart';
import 'package:kdbx/src/kdbx_group.dart'; import 'package:kdbx/src/kdbx_group.dart';
import 'package:kdbx/src/kdbx_times.dart'; import 'package:kdbx/src/kdbx_times.dart';
import 'package:kdbx/src/kdbx_xml.dart'; import 'package:kdbx/src/kdbx_xml.dart';

12
lib/src/kdbx_xml.dart

@ -200,12 +200,12 @@ class XmlUtils {
class DateTimeUtils { class DateTimeUtils {
static String toIso8601StringSeconds(DateTime dateTime) { static String toIso8601StringSeconds(DateTime dateTime) {
final String y = _fourDigits(dateTime.year); final y = _fourDigits(dateTime.year);
final String m = _twoDigits(dateTime.month); final m = _twoDigits(dateTime.month);
final String d = _twoDigits(dateTime.hour); final d = _twoDigits(dateTime.hour);
final String h = _twoDigits(dateTime.hour); final h = _twoDigits(dateTime.hour);
final String min = _twoDigits(dateTime.minute); final min = _twoDigits(dateTime.minute);
final String sec = _twoDigits(dateTime.second); final sec = _twoDigits(dateTime.second);
return '$y-$m-${d}T$h:$min:${sec}Z'; return '$y-$m-${d}T$h:$min:${sec}Z';
} }

Loading…
Cancel
Save