Browse Source

parse kdbx file without futures.

remove-cryptography-dependency
Herbert Poul 5 years ago
parent
commit
a41901198d
  1. 2
      bin/kdbx.dart
  2. 8
      lib/src/kdbx_entry.dart
  3. 4
      lib/src/kdbx_format.dart
  4. 35
      lib/src/kdbx_header.dart
  5. 2
      pubspec.yaml

2
bin/kdbx.dart

@ -74,7 +74,7 @@ abstract class KdbxFileCommand extends Command<void> {
final bytes = await File(inputFile).readAsBytes() as Uint8List; final bytes = await File(inputFile).readAsBytes() as Uint8List;
final password = prompts.get('Password for $inputFile', final password = prompts.get('Password for $inputFile',
conceal: true, validate: (str) => str.isNotEmpty); conceal: true, validate: (str) => str.isNotEmpty);
final file = await KdbxFormat.read( final file = KdbxFormat.read(
bytes, Credentials(ProtectedValue.fromString(password))); bytes, Credentials(ProtectedValue.fromString(password)));
return runWithFile(file); return runWithFile(file);
} }

8
lib/src/kdbx_entry.dart

@ -1,12 +1,15 @@
import 'package:collection/collection.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_group.dart';
import 'package:kdbx/src/kdbx_object.dart'; import 'package:kdbx/src/kdbx_object.dart';
import 'package:xml/xml.dart'; import 'package:xml/xml.dart';
String _canonicalizeKey(String key) => key?.toLowerCase();
class KdbxEntry extends KdbxObject { class KdbxEntry extends KdbxObject {
KdbxEntry.read(this.parent, XmlElement node) : super.read(node) { KdbxEntry.read(this.parent, XmlElement node) : super.read(node) {
strings = Map.fromEntries(node.findElements('String').map((el) { strings.addEntries(node.findElements('String').map((el) {
final key = el.findElements('Key').single.text; final key = el.findElements('Key').single.text;
final valueNode = el.findElements('Value').single; final valueNode = el.findElements('Value').single;
if (valueNode.getAttribute('Protected')?.toLowerCase() == 'true') { if (valueNode.getAttribute('Protected')?.toLowerCase() == 'true') {
@ -18,7 +21,8 @@ class KdbxEntry extends KdbxObject {
} }
KdbxGroup parent; KdbxGroup parent;
Map<String, StringValue> strings; Map<String, StringValue> strings =
CanonicalizedMap<String, String, StringValue>(_canonicalizeKey);
String _plainValue(String key) { String _plainValue(String key) {
final value = strings[key]; final value = strings[key];

4
lib/src/kdbx_format.dart

@ -61,9 +61,9 @@ class KdbxMeta extends KdbxNode {
} }
class KdbxFormat { class KdbxFormat {
static Future<KdbxFile> read(Uint8List input, Credentials credentials) async { static KdbxFile read(Uint8List input, Credentials credentials) {
final reader = ReaderHelper(input); final reader = ReaderHelper(input);
final header = await KdbxHeader.read(reader); final header = KdbxHeader.read(reader);
return _loadV3(header, reader, credentials); return _loadV3(header, reader, credentials);
} }

35
lib/src/kdbx_header.dart

@ -1,8 +1,6 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:convert/convert.dart' as convert;
import 'package:crypto/crypto.dart' as crypto; import 'package:crypto/crypto.dart' as crypto;
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:logging/logging.dart'; import 'package:logging/logging.dart';
@ -51,14 +49,21 @@ class HeaderField {
} }
class KdbxHeader { class KdbxHeader {
KdbxHeader({this.sig1, this.sig2, this.versionMinor, this.versionMajor, this.fields}); KdbxHeader(
{this.sig1,
static Future<KdbxHeader> read(ReaderHelper reader) async { this.sig2,
this.versionMinor,
this.versionMajor,
this.fields});
static KdbxHeader read(ReaderHelper reader) {
// 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 +71,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 +82,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 +116,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 KdbxException implements Exception {} class KdbxException implements Exception {}
@ -134,7 +143,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;
@ -151,7 +161,8 @@ class ReaderHelper {
final Uint8List data; final Uint8List data;
int pos = 0; int pos = 0;
ByteBuffer _nextByteBuffer(int byteCount) => (data.sublist(pos, pos += byteCount) as Uint8List).buffer; ByteBuffer _nextByteBuffer(int byteCount) =>
(data.sublist(pos, pos += byteCount) as Uint8List).buffer;
int readUint32() => _nextByteBuffer(4).asUint32List().first; int readUint32() => _nextByteBuffer(4).asUint32List().first;

2
pubspec.yaml

@ -16,6 +16,8 @@ dependencies:
uuid: '>=2.0.0 <3.0.0' uuid: '>=2.0.0 <3.0.0'
meta: '>=1.0.0 <2.0.0' meta: '>=1.0.0 <2.0.0'
collection: '>=1.14.0 <2.0.0'
# required for bin/ # required for bin/
args: '>1.5.0 <2.0.0' args: '>1.5.0 <2.0.0'
prompts: '>=1.3.0 <2.0.0' prompts: '>=1.3.0 <2.0.0'

Loading…
Cancel
Save