Browse Source

more exports, make it possible to retrieve all entries of a file, etc.

remove-cryptography-dependency
Herbert Poul 6 years ago
parent
commit
c279c7e649
  1. 10
      bin/kdbx.dart
  2. 9
      lib/kdbx.dart
  3. 6
      lib/src/internal/byte_utils.dart
  4. 10
      lib/src/kdbx_entry.dart
  5. 27
      lib/src/kdbx_format.dart
  6. 11
      lib/src/kdbx_group.dart
  7. 44
      lib/src/kdbx_header.dart
  8. 24
      lib/src/kdbx_object.dart
  9. 8
      test/kdbx_test.dart

10
bin/kdbx.dart

@ -1,12 +1,12 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
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';
import 'package:prompts/prompts.dart' as prompts;
@ -71,7 +71,7 @@ abstract class KdbxFileCommand extends Command<void> {
if (inputFile == null) {
usageException('Required argument: --input');
}
final bytes = await File(inputFile).readAsBytes();
final bytes = await File(inputFile).readAsBytes() as Uint8List;
final password = prompts.get('Password for $inputFile',
conceal: true, validate: (str) => str.isNotEmpty);
final file = await KdbxFormat.read(
@ -84,7 +84,8 @@ abstract class KdbxFileCommand extends Command<void> {
class CatCommand extends KdbxFileCommand {
CatCommand() {
argParser.addFlag('decrypt', help: 'Force decryption of all protected strings.');
argParser.addFlag('decrypt',
help: 'Force decryption of all protected strings.');
}
@override
@ -108,7 +109,8 @@ class CatCommand extends KdbxFileCommand {
}
for (final entry in group.entries) {
final value = entry.strings['Password'];
print('$indent `- ${entry.strings['Title']?.getText()}: ${forceDecrypt ? value?.getText() : value?.toString()}');
print(
'$indent `- ${entry.strings['Title']?.getText()}: ${forceDecrypt ? value?.getText() : value?.toString()}');
}
}
}

9
lib/kdbx.dart

@ -1,4 +1,13 @@
/// dart library for reading keepass file format (kdbx).
library kdbx;
export 'src/crypto/protected_value.dart' show ProtectedValue, StringValue;
export 'src/kdbx_entry.dart';
export 'src/kdbx_format.dart';
export 'src/kdbx_header.dart'
show
KdbxException,
KdbxInvalidKeyException,
KdbxCorruptedFileException,
KdbxUnsupportedException;
export 'src/kdbx_object.dart';

6
lib/src/internal/byte_utils.dart

@ -1,7 +1,5 @@
import 'dart:typed_data';
class ByteUtils {
static bool eq(Uint8List a, Uint8List b) {
static bool eq(List<int> a, List<int> b) {
if (a.length != b.length) {
return false;
}
@ -15,5 +13,5 @@ class ByteUtils {
static String toHex(int val) => '0x${val.toRadixString(16)}';
static String toHexList(Uint8List list) => list.map((val) => toHex(val)).join(' ');
static String toHexList(List<int> list) => list.map((val) => toHex(val)).join(' ');
}

10
lib/src/kdbx_entry.dart

@ -19,4 +19,14 @@ class KdbxEntry extends KdbxObject {
KdbxGroup parent;
Map<String, StringValue> strings;
String _plainValue(String key) {
final value = strings[key];
if (value is PlainValue) {
return value.getText();
}
return value?.toString();
}
String get label => _plainValue('Title');
}

27
lib/src/kdbx_format.dart

@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:convert/convert.dart' as convert;
import 'package:crypto/crypto.dart' as crypto;
import 'package:kdbx/src/crypto/protected_salt_generator.dart';
import 'package:kdbx/src/crypto/protected_value.dart';
@ -13,8 +14,24 @@ import 'package:logging/logging.dart';
import 'package:pointycastle/export.dart';
import 'package:xml/xml.dart' as xml;
import 'kdbx_object.dart';
final _logger = Logger('kdbx.format');
class Credentials {
Credentials(this._password);
final ProtectedValue _password;
Uint8List getHash() {
final output = convert.AccumulatorSink<crypto.Digest>();
final input = crypto.sha256.startChunkedConversion(output);
input.add(_password.hash);
input.close();
return output.events.single.bytes as Uint8List;
}
}
class KdbxFile {
KdbxFile(this.credentials, this.header, this.body);
@ -37,7 +54,11 @@ class KdbxBody {
final KdbxGroup rootGroup;
}
class KdbxMeta {}
class KdbxMeta extends KdbxNode {
KdbxMeta.read(xml.XmlElement node) : super.read(node);
String get databaseName => text('DatabaseName');
}
class KdbxFormat {
static Future<KdbxFile> read(Uint8List input, Credentials credentials) async {
@ -89,7 +110,7 @@ class KdbxFormat {
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(), rootGroup);
return KdbxBody(document, KdbxMeta.read(meta), rootGroup);
}
static Uint8List _decryptContent(
@ -111,7 +132,7 @@ class KdbxFormat {
decrypted.sublist(0, streamStart.lengthInBytes))) {
throw KdbxInvalidKeyException();
}
final content = decrypted.sublist(streamStart.lengthInBytes);
final content = decrypted.sublist(streamStart.lengthInBytes) as Uint8List;
return content;
}

11
lib/src/kdbx_group.dart

@ -3,8 +3,6 @@ import 'package:xml/xml.dart';
import 'kdbx_object.dart';
final _builder = XmlBuilder();
class KdbxGroup extends KdbxObject {
KdbxGroup(this.parent) : super.create('Group');
@ -19,6 +17,15 @@ class KdbxGroup extends KdbxObject {
.forEach(entries.add);
}
/// Returns all groups plus this group itself.
List<KdbxGroup> getAllGroups() => groups
.expand((g) => g.getAllGroups())
.followedBy([this]).toList(growable: false);
/// Returns all entries of this group and all sub groups.
List<KdbxEntry> getAllEntries() =>
getAllGroups().expand((g) => g.entries).toList(growable: false);
/// null if this is the root group.
final KdbxGroup parent;
final List<KdbxGroup> groups = [];

44
lib/src/kdbx_header.dart

@ -51,20 +51,14 @@ 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
@ -72,8 +66,7 @@ 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,
@ -83,12 +76,10 @@ 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) {
@ -117,22 +108,7 @@ class KdbxHeader {
}
PotectedValueEncryption get innerRandomStreamEncryption =>
PotectedValueEncryption.values[
fields[HeaderFields.InnerRandomStreamID].bytes.asUint32List().single];
}
class Credentials {
Credentials(this._password);
final ProtectedValue _password;
Uint8List getHash() {
final output = convert.AccumulatorSink<crypto.Digest>();
final input = crypto.sha256.startChunkedConversion(output);
input.add(_password.hash);
input.close();
return output.events.single.bytes as Uint8List;
}
PotectedValueEncryption.values[fields[HeaderFields.InnerRandomStreamID].bytes.asUint32List().single];
}
class KdbxException implements Exception {}
@ -158,8 +134,7 @@ 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;
@ -176,8 +151,7 @@ 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) as Uint8List).buffer;
int readUint32() => _nextByteBuffer(4).asUint32List().first;
@ -187,5 +161,5 @@ class ReaderHelper {
ByteBuffer readBytes(int size) => _nextByteBuffer(size);
Uint8List readRemaining() => data.sublist(pos);
Uint8List readRemaining() => data.sublist(pos) as Uint8List;
}

24
lib/src/kdbx_object.dart

@ -13,17 +13,11 @@ class KdbxTimes {
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;
}
abstract class KdbxNode {
KdbxNode.create(String nodeName) : node = XmlElement(XmlName(nodeName));
KdbxNode.read(this.node);
final XmlElement node;
String uuid;
@protected
String text(String nodeName) => _opt(nodeName)?.text;
@ -31,3 +25,15 @@ abstract class KdbxObject {
XmlElement _opt(String nodeName) =>
node.findElements(nodeName).singleWhere((x) => true, orElse: () => null);
}
abstract class KdbxObject extends KdbxNode {
KdbxObject.create(String nodeName)
: uuid = Uuid().v4(),
super.create(nodeName);
KdbxObject.read(XmlElement node) : super.read(node) {
uuid = node.findElements('UUID').single.text;
}
String uuid;
}

8
test/kdbx_test.dart

@ -1,4 +1,5 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:kdbx/kdbx.dart';
import 'package:kdbx/src/crypto/protected_value.dart';
@ -10,14 +11,13 @@ import 'package:test/test.dart';
void main() {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen(PrintAppender().logListener());
PrintAppender().attachToLogger(Logger.root);
group('A group of tests', () {
setUp(() {});
test('First Test', () async {
final data = await File('test/FooBar.kdbx').readAsBytes();
await KdbxFormat.read(
data, Credentials(ProtectedValue.fromString('FooBar')));
final data = await File('test/FooBar.kdbx').readAsBytes() as Uint8List;
await KdbxFormat.read(data, Credentials(ProtectedValue.fromString('FooBar')));
});
});
}

Loading…
Cancel
Save