14 changed files with 641 additions and 37 deletions
			
			
		@ -1,5 +1,5 @@ | 
				
			|||||||
import 'package:kdbx/kdbx.dart'; | 
					import 'package:kdbx/kdbx.dart'; | 
				
			||||||
 | 
					
 | 
				
			||||||
void main() { | 
					void main() { | 
				
			||||||
  KdbxFormat.read(null, null); | 
					  KdbxFormat().read(null, null); | 
				
			||||||
} | 
					} | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,113 @@ | 
				
			|||||||
 | 
					import 'dart:convert'; | 
				
			||||||
 | 
					import 'dart:typed_data'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:kdbx/kdbx.dart'; | 
				
			||||||
 | 
					import 'package:kdbx/src/internal/byte_utils.dart'; | 
				
			||||||
 | 
					import 'package:kdbx/src/kdbx_var_dictionary.dart'; | 
				
			||||||
 | 
					import 'package:logging/logging.dart'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final _logger = Logger('key_encrypter_kdf'); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum KdfType { | 
				
			||||||
 | 
					  Argon2, | 
				
			||||||
 | 
					  Aes, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class KdfField<T> { | 
				
			||||||
 | 
					  KdfField(this.field, this.type); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final String field; | 
				
			||||||
 | 
					  final ValueType<T> type; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static final salt = KdfField('S', ValueType.typeBytes); | 
				
			||||||
 | 
					  static final parallelism = KdfField('P', ValueType.typeUInt32); | 
				
			||||||
 | 
					  static final memory = KdfField('M', ValueType.typeUInt64); | 
				
			||||||
 | 
					  static final iterations = KdfField('I', ValueType.typeUInt64); | 
				
			||||||
 | 
					  static final version = KdfField('V', ValueType.typeUInt32); | 
				
			||||||
 | 
					  static final secretKey = KdfField('K', ValueType.typeBytes); | 
				
			||||||
 | 
					  static final assocData = KdfField('A', ValueType.typeBytes); | 
				
			||||||
 | 
					  static final rounds = KdfField('R', ValueType.typeInt64); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static final fields = [ | 
				
			||||||
 | 
					    salt, | 
				
			||||||
 | 
					    parallelism, | 
				
			||||||
 | 
					    memory, | 
				
			||||||
 | 
					    iterations, | 
				
			||||||
 | 
					    version, | 
				
			||||||
 | 
					    secretKey, | 
				
			||||||
 | 
					    assocData, | 
				
			||||||
 | 
					    rounds | 
				
			||||||
 | 
					  ]; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static void debugAll(VarDictionary dict) { | 
				
			||||||
 | 
					    _logger | 
				
			||||||
 | 
					        .fine('VarDictionary{\n${fields.map((f) => f.debug(dict)).join('\n')}'); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  T read(VarDictionary dict) => dict.get(type, field); | 
				
			||||||
 | 
					  String debug(VarDictionary dict) { | 
				
			||||||
 | 
					    final value = dict.get(type, field); | 
				
			||||||
 | 
					    final strValue = type == ValueType.typeBytes | 
				
			||||||
 | 
					        ? ByteUtils.toHexList(value as Uint8List) | 
				
			||||||
 | 
					        : value; | 
				
			||||||
 | 
					    return '$field=$strValue'; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class KeyEncrypterKdf { | 
				
			||||||
 | 
					  KeyEncrypterKdf(this.argon2); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static const kdfUuids = <String, KdfType>{ | 
				
			||||||
 | 
					    '72Nt34wpREuR96mkA+MKDA==': KdfType.Argon2, | 
				
			||||||
 | 
					    'ydnzmmKKRGC/dA0IwYpP6g==': KdfType.Aes, | 
				
			||||||
 | 
					  }; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final Argon2 argon2; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Uint8List encrypt(Uint8List key, VarDictionary kdfParameters) { | 
				
			||||||
 | 
					    final uuid = kdfParameters.get(ValueType.typeBytes, '\$UUID'); | 
				
			||||||
 | 
					    if (uuid == null) { | 
				
			||||||
 | 
					      throw KdbxCorruptedFileException('No Kdf UUID'); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    final kdfUuid = base64.encode(uuid); | 
				
			||||||
 | 
					    switch (kdfUuids[kdfUuid]) { | 
				
			||||||
 | 
					      case KdfType.Argon2: | 
				
			||||||
 | 
					        _logger.fine('Must be using argon2'); | 
				
			||||||
 | 
					        return encryptArgon2(key, kdfParameters); | 
				
			||||||
 | 
					        break; | 
				
			||||||
 | 
					      case KdfType.Aes: | 
				
			||||||
 | 
					        _logger.fine('Must be using aes'); | 
				
			||||||
 | 
					        break; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    throw UnsupportedError('unsupported encrypt stuff.'); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Uint8List encryptArgon2(Uint8List key, VarDictionary kdfParameters) { | 
				
			||||||
 | 
					    _logger.fine('argon2():'); | 
				
			||||||
 | 
					    _logger.fine('key: ${ByteUtils.toHexList(key)}'); | 
				
			||||||
 | 
					    KdfField.debugAll(kdfParameters); | 
				
			||||||
 | 
					    return argon2.argon2( | 
				
			||||||
 | 
					      key, | 
				
			||||||
 | 
					      KdfField.salt.read(kdfParameters), | 
				
			||||||
 | 
					      65536, //KdfField.memory.read(kdfParameters), | 
				
			||||||
 | 
					      KdfField.iterations.read(kdfParameters), | 
				
			||||||
 | 
					      32, | 
				
			||||||
 | 
					      KdfField.parallelism.read(kdfParameters), | 
				
			||||||
 | 
					      0, | 
				
			||||||
 | 
					      KdfField.version.read(kdfParameters), | 
				
			||||||
 | 
					    ); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					abstract class Argon2 { | 
				
			||||||
 | 
					  Uint8List argon2( | 
				
			||||||
 | 
					    Uint8List key, | 
				
			||||||
 | 
					    Uint8List salt, | 
				
			||||||
 | 
					    int memory, | 
				
			||||||
 | 
					    int iterations, | 
				
			||||||
 | 
					    int length, | 
				
			||||||
 | 
					    int parallelism, | 
				
			||||||
 | 
					    int type, | 
				
			||||||
 | 
					    int version, | 
				
			||||||
 | 
					  ); | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,127 @@ | 
				
			|||||||
 | 
					import 'package:kdbx/src/internal/byte_utils.dart'; | 
				
			||||||
 | 
					import 'package:logging/logging.dart'; | 
				
			||||||
 | 
					import 'package:meta/meta.dart'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final _logger = Logger('kdbx_var_dictionary'); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef Decoder<T> = T Function(ReaderHelper reader, int length); | 
				
			||||||
 | 
					typedef Encoder<T> = void Function(WriterHelper writer, T value); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extension on WriterHelper { | 
				
			||||||
 | 
					  LengthWriter _lengthWriter() => (int length) => writeInt32(length); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@immutable | 
				
			||||||
 | 
					class ValueType<T> { | 
				
			||||||
 | 
					  const ValueType(this.code, this.decoder, [this.encoder]); | 
				
			||||||
 | 
					  final int code; | 
				
			||||||
 | 
					  final Decoder<T> decoder; | 
				
			||||||
 | 
					  final Encoder<T> encoder; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static final typeUInt32 = ValueType( | 
				
			||||||
 | 
					    0x04, | 
				
			||||||
 | 
					    (reader, _) => reader.readUint32(), | 
				
			||||||
 | 
					    (writer, value) => writer.writeUint32(value, writer._lengthWriter()), | 
				
			||||||
 | 
					  ); | 
				
			||||||
 | 
					  static final typeUInt64 = ValueType( | 
				
			||||||
 | 
					    0x05, | 
				
			||||||
 | 
					    (reader, _) => reader.readUint64(), | 
				
			||||||
 | 
					    (writer, value) => writer.writeUint64(value, writer._lengthWriter()), | 
				
			||||||
 | 
					  ); | 
				
			||||||
 | 
					  static final typeBool = ValueType( | 
				
			||||||
 | 
					    0x08, | 
				
			||||||
 | 
					    (reader, _) => reader.readUint8() != 0, | 
				
			||||||
 | 
					    (writer, value) => writer.writeUint8(value ? 1 : 0, writer._lengthWriter()), | 
				
			||||||
 | 
					  ); | 
				
			||||||
 | 
					  static final typeInt32 = ValueType( | 
				
			||||||
 | 
					    0x0C, | 
				
			||||||
 | 
					    (reader, _) => reader.readInt32(), | 
				
			||||||
 | 
					    (writer, value) => writer.writeInt32(value, writer._lengthWriter()), | 
				
			||||||
 | 
					  ); | 
				
			||||||
 | 
					  static final typeInt64 = ValueType( | 
				
			||||||
 | 
					    0x0D, | 
				
			||||||
 | 
					    (reader, _) => reader.readInt64(), | 
				
			||||||
 | 
					    (writer, value) => writer.writeInt64(value, writer._lengthWriter()), | 
				
			||||||
 | 
					  ); | 
				
			||||||
 | 
					  static final typeString = ValueType( | 
				
			||||||
 | 
					    0x18, | 
				
			||||||
 | 
					    (reader, length) => reader.readString(length), | 
				
			||||||
 | 
					    (writer, value) => writer.writeString(value, writer._lengthWriter()), | 
				
			||||||
 | 
					  ); | 
				
			||||||
 | 
					  static final typeBytes = ValueType( | 
				
			||||||
 | 
					    0x42, | 
				
			||||||
 | 
					    (reader, length) => reader.readBytes(length), | 
				
			||||||
 | 
					    (writer, value) => writer.writeBytes(value, writer._lengthWriter()), | 
				
			||||||
 | 
					  ); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static ValueType typeByCode(int code) => | 
				
			||||||
 | 
					      values.firstWhere((t) => t.code == code); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static final values = [ | 
				
			||||||
 | 
					    typeUInt32, | 
				
			||||||
 | 
					    typeUInt64, | 
				
			||||||
 | 
					    typeBool, | 
				
			||||||
 | 
					    typeInt32, | 
				
			||||||
 | 
					    typeInt64, | 
				
			||||||
 | 
					    typeString, | 
				
			||||||
 | 
					    typeBytes, | 
				
			||||||
 | 
					  ]; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class VarDictionaryItem<T> { | 
				
			||||||
 | 
					  VarDictionaryItem(this._key, this._valueType, this._value); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final String _key; | 
				
			||||||
 | 
					  final ValueType<T> _valueType; | 
				
			||||||
 | 
					  final T _value; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  String toDebugString() { | 
				
			||||||
 | 
					    return 'VarDictionaryItem{key=$_key, valueType=$_valueType, value=${_value.runtimeType}}'; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class VarDictionary { | 
				
			||||||
 | 
					  VarDictionary(List<VarDictionaryItem<dynamic>> items) | 
				
			||||||
 | 
					      : assert(items != null), | 
				
			||||||
 | 
					        _items = items, | 
				
			||||||
 | 
					        _dict = Map.fromEntries(items.map((item) => MapEntry(item._key, item))); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  factory VarDictionary.read(ReaderHelper reader) { | 
				
			||||||
 | 
					    final items = <VarDictionaryItem>[]; | 
				
			||||||
 | 
					    final versionMinor = reader.readUint8(); | 
				
			||||||
 | 
					    final versionMajor = reader.readUint8(); | 
				
			||||||
 | 
					    _logger.finest('Reading VarDictionary $versionMajor.$versionMinor'); | 
				
			||||||
 | 
					    assert(versionMajor == 1); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while (true) { | 
				
			||||||
 | 
					      final item = _readItem(reader); | 
				
			||||||
 | 
					      if (item == null) { | 
				
			||||||
 | 
					        break; | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      items.add(item); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    return VarDictionary(items); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final List<VarDictionaryItem<dynamic>> _items; | 
				
			||||||
 | 
					  final Map<String, VarDictionaryItem<dynamic>> _dict; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  T get<T>(ValueType<T> type, String key) => _dict[key]?._value as T; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static VarDictionaryItem<dynamic> _readItem(ReaderHelper reader) { | 
				
			||||||
 | 
					    final type = reader.readUint8(); | 
				
			||||||
 | 
					    if (type == 0) { | 
				
			||||||
 | 
					      return null; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    final keyLength = reader.readUint32(); | 
				
			||||||
 | 
					    final key = reader.readString(keyLength); | 
				
			||||||
 | 
					    final valueLength = reader.readInt32(); | 
				
			||||||
 | 
					    final valueType = ValueType.typeByCode(type); | 
				
			||||||
 | 
					    return VarDictionaryItem<dynamic>( | 
				
			||||||
 | 
					        key, valueType, valueType.decoder(reader, valueLength)); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  String toDebugString() { | 
				
			||||||
 | 
					    return 'VarDictionary{${_items.map((item) => item.toDebugString())}'; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
									
										Binary file not shown.
									
								
							
						@ -0,0 +1,117 @@ | 
				
			|||||||
 | 
					import 'dart:convert'; | 
				
			||||||
 | 
					import 'dart:ffi'; | 
				
			||||||
 | 
					import 'dart:io'; | 
				
			||||||
 | 
					import 'dart:typed_data'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:ffi/ffi.dart'; | 
				
			||||||
 | 
					import 'package:ffi_helper/ffi_helper.dart'; | 
				
			||||||
 | 
					import 'package:kdbx/kdbx.dart'; | 
				
			||||||
 | 
					import 'package:kdbx/src/crypto/key_encrypter_kdf.dart'; | 
				
			||||||
 | 
					import 'package:logging/logging.dart'; | 
				
			||||||
 | 
					import 'package:logging_appenders/logging_appenders.dart'; | 
				
			||||||
 | 
					import 'package:test/test.dart'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final _logger = Logger('kdbx4_test'); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//typedef HashStuff = Pointer<Utf8> Function(Pointer<Utf8> str); | 
				
			||||||
 | 
					typedef Argon2HashNative = Pointer<Utf8> Function( | 
				
			||||||
 | 
					  Pointer<Uint8> key, | 
				
			||||||
 | 
					  IntPtr keyLen, | 
				
			||||||
 | 
					  Pointer<Uint8> salt, | 
				
			||||||
 | 
					  Uint64 saltlen, | 
				
			||||||
 | 
					  Uint32 m_cost, // memory cost | 
				
			||||||
 | 
					  Uint32 t_cost, // time cost (number iterations) | 
				
			||||||
 | 
					  Uint32 parallelism, | 
				
			||||||
 | 
					  IntPtr hashlen, | 
				
			||||||
 | 
					  Uint8 type, | 
				
			||||||
 | 
					  Uint32 version, | 
				
			||||||
 | 
					); | 
				
			||||||
 | 
					typedef Argon2Hash = Pointer<Utf8> Function( | 
				
			||||||
 | 
					  Pointer<Uint8> key, | 
				
			||||||
 | 
					  int keyLen, | 
				
			||||||
 | 
					  Pointer<Uint8> salt, | 
				
			||||||
 | 
					  int saltlen, | 
				
			||||||
 | 
					  int m_cost, // memory cost | 
				
			||||||
 | 
					  int t_cost, // time cost (number iterations) | 
				
			||||||
 | 
					  int parallelism, | 
				
			||||||
 | 
					  int hashlen, | 
				
			||||||
 | 
					  int type, | 
				
			||||||
 | 
					  int version, | 
				
			||||||
 | 
					); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Argon2Test implements Argon2 { | 
				
			||||||
 | 
					  Argon2Test() { | 
				
			||||||
 | 
					//    final argon2lib = DynamicLibrary.open('libargon2.1.dylib'); | 
				
			||||||
 | 
					    final argon2lib = DynamicLibrary.open('libargon2_ffi.dylib'); | 
				
			||||||
 | 
					    _argon2hash = argon2lib | 
				
			||||||
 | 
					        .lookup<NativeFunction<Argon2HashNative>>('hp_argon2_hash') | 
				
			||||||
 | 
					        .asFunction(); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  Argon2Hash _argon2hash; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override | 
				
			||||||
 | 
					  Uint8List argon2( | 
				
			||||||
 | 
					    Uint8List key, | 
				
			||||||
 | 
					    Uint8List salt, | 
				
			||||||
 | 
					    int memory, | 
				
			||||||
 | 
					    int iterations, | 
				
			||||||
 | 
					    int length, | 
				
			||||||
 | 
					    int parallelism, | 
				
			||||||
 | 
					    int type, | 
				
			||||||
 | 
					    int version, | 
				
			||||||
 | 
					  ) { | 
				
			||||||
 | 
					//    print('hash: ${hashStuff('abc')}'); | 
				
			||||||
 | 
					    final keyArray = Uint8Array.fromTypedList(key); | 
				
			||||||
 | 
					//    final saltArray = Uint8Array.fromTypedList(salt); | 
				
			||||||
 | 
					    final saltArray = allocate<Uint8>(count: salt.length); | 
				
			||||||
 | 
					    final saltList = saltArray.asTypedList(length); | 
				
			||||||
 | 
					    saltList.setAll(0, salt); | 
				
			||||||
 | 
					    const int memoryCost = 1 << 16; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//    _logger.fine('saltArray: ${ByteUtils.toHexList(saltArray.view)}'); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final result = _argon2hash( | 
				
			||||||
 | 
					      keyArray.rawPtr, | 
				
			||||||
 | 
					      keyArray.length, | 
				
			||||||
 | 
					      saltArray, | 
				
			||||||
 | 
					      salt.length, | 
				
			||||||
 | 
					      memoryCost, | 
				
			||||||
 | 
					      iterations, | 
				
			||||||
 | 
					      parallelism, | 
				
			||||||
 | 
					      length, | 
				
			||||||
 | 
					      type, | 
				
			||||||
 | 
					      version, | 
				
			||||||
 | 
					    ); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    keyArray.free(); | 
				
			||||||
 | 
					//    saltArray.free(); | 
				
			||||||
 | 
					    free(saltArray); | 
				
			||||||
 | 
					    final resultString = Utf8.fromUtf8(result); | 
				
			||||||
 | 
					    return base64.decode(resultString); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//  String hashStuff(String password) => | 
				
			||||||
 | 
					//      Utf8.fromUtf8(_hashStuff(Utf8.toUtf8(password))); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void main() { | 
				
			||||||
 | 
					  Logger.root.level = Level.ALL; | 
				
			||||||
 | 
					  PrintAppender().attachToLogger(Logger.root); | 
				
			||||||
 | 
					  final kdbxFormat = KdbxFormat(Argon2Test()); | 
				
			||||||
 | 
					  group('Reading', () { | 
				
			||||||
 | 
					    final argon2 = Argon2Test(); | 
				
			||||||
 | 
					    test('bubb', () async { | 
				
			||||||
 | 
					      final key = utf8.encode('asdf') as Uint8List; | 
				
			||||||
 | 
					      final salt = Uint8List(8); | 
				
			||||||
 | 
					//      final result = Argon2Test().argon2(key, salt, 1 << 16, 5, 16, 1, 0x13, 1); | 
				
			||||||
 | 
					//      _logger.fine('hashing: $result'); | 
				
			||||||
 | 
					      final data = await File('test/keepassxcpasswords.kdbx').readAsBytes(); | 
				
			||||||
 | 
					      final file = | 
				
			||||||
 | 
					          kdbxFormat.read(data, Credentials(ProtectedValue.fromString('asdf'))); | 
				
			||||||
 | 
					      final firstEntry = file.body.rootGroup.entries.first; | 
				
			||||||
 | 
					      final pwd = firstEntry.getString(KdbxKey('Password')).getText(); | 
				
			||||||
 | 
					      _logger.info('password: $pwd'); | 
				
			||||||
 | 
					      expect(pwd, 'MyPassword'); | 
				
			||||||
 | 
					    }); | 
				
			||||||
 | 
					  }); | 
				
			||||||
 | 
					} | 
				
			||||||
									
										Binary file not shown.
									
								
							
						
					Loading…
					
					
				
		Reference in new issue