Browse Source

export KdbxVersion and some KDF config options, cleanup usage of CipherID

pull/3/head
Herbert Poul 4 years ago
parent
commit
e641f276bf
  1. 4
      lib/kdbx.dart
  2. 4
      lib/src/internal/byte_utils.dart
  3. 10
      lib/src/internal/consts.dart
  4. 18
      lib/src/kdbx_format.dart
  5. 39
      lib/src/kdbx_header.dart
  6. 2
      lib/src/kdbx_object.dart

4
lib/kdbx.dart

@ -1,6 +1,7 @@
/// dart library for reading keepass file format (kdbx). /// dart library for reading keepass file format (kdbx).
library kdbx; library kdbx;
export 'src/crypto/key_encrypter_kdf.dart' show KeyEncrypterKdf, KdfType;
export 'src/crypto/protected_value.dart' export 'src/crypto/protected_value.dart'
show ProtectedValue, StringValue, PlainValue; show ProtectedValue, StringValue, PlainValue;
export 'src/kdbx_binary.dart' show KdbxBinary; export 'src/kdbx_binary.dart' show KdbxBinary;
@ -16,6 +17,7 @@ export 'src/kdbx_header.dart'
KdbxException, KdbxException,
KdbxInvalidKeyException, KdbxInvalidKeyException,
KdbxCorruptedFileException, KdbxCorruptedFileException,
KdbxUnsupportedException; KdbxUnsupportedException,
KdbxVersion;
export 'src/kdbx_meta.dart'; export 'src/kdbx_meta.dart';
export 'src/kdbx_object.dart'; export 'src/kdbx_object.dart';

4
lib/src/internal/byte_utils.dart

@ -35,6 +35,10 @@ class ByteUtils {
list?.map((val) => toHex(val))?.join(' ') ?? '(null)'; list?.map((val) => toHex(val))?.join(' ') ?? '(null)';
} }
extension Uint8ListExt on Uint8List {
String encodeBase64() => base64.encode(this);
}
class ReaderHelper { class ReaderHelper {
factory ReaderHelper(Uint8List byteData) => KdbxFormat.dartWebWorkaround factory ReaderHelper(Uint8List byteData) => KdbxFormat.dartWebWorkaround
? ReaderHelperDartWeb(byteData) ? ReaderHelperDartWeb(byteData)

10
lib/src/internal/consts.dart

@ -1,7 +1,12 @@
import 'dart:typed_data';
import 'package:kdbx/src/kdbx_object.dart'; import 'package:kdbx/src/kdbx_object.dart';
enum Cipher { enum Cipher {
/// the only cipher supported in kdbx <= 3
aes, aes,
/// Support since kdbx 4.
chaCha20, chaCha20,
} }
@ -10,4 +15,9 @@ class CryptoConsts {
Cipher.aes: KdbxUuid('McHy5r9xQ1C+WAUhavxa/w=='), Cipher.aes: KdbxUuid('McHy5r9xQ1C+WAUhavxa/w=='),
Cipher.chaCha20: KdbxUuid('1gOKK4tvTLWlJDOaMdu1mg=='), Cipher.chaCha20: KdbxUuid('1gOKK4tvTLWlJDOaMdu1mg=='),
}; };
static final cipherByUuid =
CIPHER_IDS.map((key, value) => MapEntry(value, key));
static Cipher cipherFromBytes(Uint8List bytes) =>
cipherByUuid[KdbxUuid.fromBytes(bytes)];
} }

18
lib/src/kdbx_format.dart

@ -258,19 +258,19 @@ class KdbxBody extends KdbxNode {
Uint8List _encryptV4( Uint8List _encryptV4(
KdbxFile kdbxFile, Uint8List compressedBytes, Uint8List cipherKey) { KdbxFile kdbxFile, Uint8List compressedBytes, Uint8List cipherKey) {
final header = kdbxFile.header; final header = kdbxFile.header;
final cipherId = base64.encode(header.fields[HeaderFields.CipherID].bytes); final cipher = header.cipher;
if (cipherId == CryptoConsts.CIPHER_IDS[Cipher.aes].uuid) { if (cipher == Cipher.aes) {
_logger.fine('We need AES'); _logger.fine('We need AES');
final result = kdbxFile.kdbxFormat final result = kdbxFile.kdbxFormat
._encryptContentV4Aes(header, cipherKey, compressedBytes); ._encryptContentV4Aes(header, cipherKey, compressedBytes);
// _logger.fine('Result: ${ByteUtils.toHexList(result)}'); // _logger.fine('Result: ${ByteUtils.toHexList(result)}');
return result; return result;
} else if (cipherId == CryptoConsts.CIPHER_IDS[Cipher.chaCha20].uuid) { } else if (cipher == Cipher.chaCha20) {
_logger.fine('We need chacha20'); _logger.fine('We need chacha20');
return kdbxFile.kdbxFormat return kdbxFile.kdbxFormat
.transformContentV4ChaCha20(header, compressedBytes, cipherKey); .transformContentV4ChaCha20(header, compressedBytes, cipherKey);
} else { } else {
throw UnsupportedError('Unsupported cipherId $cipherId'); throw UnsupportedError('Unsupported cipherId $cipher');
} }
} }
@ -308,7 +308,7 @@ class KdbxBody extends KdbxNode {
// final doc = xml.XmlDocument(); // final doc = xml.XmlDocument();
// doc.children.add(xml.XmlProcessing( // doc.children.add(xml.XmlProcessing(
// 'xml', 'version="1.0" encoding="utf-8" standalone="yes"')); // 'xml', 'version="1.0" encoding="utf-8" standalone="yes"'));
final node = builder.build() as xml.XmlDocument; final node = builder.buildDocument();
return node; return node;
} }
@ -533,17 +533,17 @@ class KdbxFormat {
Uint8List decrypt( Uint8List decrypt(
KdbxHeader header, Uint8List encrypted, Uint8List cipherKey) { KdbxHeader header, Uint8List encrypted, Uint8List cipherKey) {
final cipherId = base64.encode(header.fields[HeaderFields.CipherID].bytes); final cipher = header.cipher;
if (cipherId == CryptoConsts.CIPHER_IDS[Cipher.aes].uuid) { if (cipher == Cipher.aes) {
_logger.fine('We need AES'); _logger.fine('We need AES');
final result = _decryptContentV4(header, cipherKey, encrypted); final result = _decryptContentV4(header, cipherKey, encrypted);
return result; return result;
} else if (cipherId == CryptoConsts.CIPHER_IDS[Cipher.chaCha20].uuid) { } else if (cipher == Cipher.chaCha20) {
_logger.fine('We need chacha20'); _logger.fine('We need chacha20');
// throw UnsupportedError('chacha20 not yet supported $cipherId'); // throw UnsupportedError('chacha20 not yet supported $cipherId');
return transformContentV4ChaCha20(header, encrypted, cipherKey); return transformContentV4ChaCha20(header, encrypted, cipherKey);
} else { } else {
throw UnsupportedError('Unsupported cipherId $cipherId'); throw UnsupportedError('Unsupported cipherId $cipher');
} }
} }

39
lib/src/kdbx_header.dart

@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:crypto/crypto.dart' as crypto; import 'package:crypto/crypto.dart' as crypto;
@ -49,6 +48,8 @@ enum ProtectedValueEncryption { plainText, arc4variant, salsa20, chaCha20 }
enum HeaderFields { enum HeaderFields {
EndOfHeader, EndOfHeader,
Comment, Comment,
/// the cipher to use as defined by [Cipher]. in kdbx 3 this is always aes.
CipherID, CipherID,
CompressionFlags, CompressionFlags,
MasterSeed, MasterSeed,
@ -262,9 +263,8 @@ class KdbxHeader {
kdfParameters, ByteUtils.randomBytes(Consts.DefaultKdfSaltLength)); kdfParameters, ByteUtils.randomBytes(Consts.DefaultKdfSaltLength));
// var ivLength = this.dataCipherUuid.toString() === Consts.CipherId.ChaCha20 ? 12 : 16; // var ivLength = this.dataCipherUuid.toString() === Consts.CipherId.ChaCha20 ? 12 : 16;
// this.encryptionIV = Random.getBytes(ivLength); // this.encryptionIV = Random.getBytes(ivLength);
final cipherId = base64.encode(fields[HeaderFields.CipherID].bytes); final cipher = this.cipher;
final ivLength = final ivLength = cipher == Cipher.chaCha20 ? 12 : 16;
cipherId == CryptoConsts.CIPHER_IDS[Cipher.chaCha20].uuid ? 12 : 16;
_setHeaderField( _setHeaderField(
HeaderFields.EncryptionIV, ByteUtils.randomBytes(ivLength)); HeaderFields.EncryptionIV, ByteUtils.randomBytes(ivLength));
} else { } else {
@ -471,6 +471,37 @@ class KdbxHeader {
/// end position of the header, if we have been reading from a stream. /// end position of the header, if we have been reading from a stream.
final int endPos; final int endPos;
Cipher get cipher {
if (version < KdbxVersion.V4) {
assert(
CryptoConsts.cipherFromBytes(fields[HeaderFields.CipherID].bytes) ==
Cipher.aes);
return Cipher.aes;
}
try {
return CryptoConsts.cipherFromBytes(fields[HeaderFields.CipherID].bytes);
} catch (e, stackTrace) {
_logger.warning(
'Unable to find cipher. '
'${fields[HeaderFields.CipherID]?.bytes?.encodeBase64()}',
e,
stackTrace);
throw KdbxCorruptedFileException(
'Invalid cipher. '
'${fields[HeaderFields.CipherID]?.bytes?.encodeBase64()}',
);
}
}
set cipher(Cipher cipher) {
checkArgument(version >= KdbxVersion.V4 || cipher == Cipher.aes,
message: 'Kdbx 3 only supports aes, tried to set it to $cipher');
_setHeaderField(
HeaderFields.CipherID,
CryptoConsts.CIPHER_IDS[cipher].toBytes(),
);
}
Compression get compression { Compression get compression {
final id = final id =
ReaderHelper.singleUint32(fields[HeaderFields.CompressionFlags].bytes); ReaderHelper.singleUint32(fields[HeaderFields.CompressionFlags].bytes);

2
lib/src/kdbx_object.dart

@ -175,6 +175,8 @@ class KdbxUuid {
KdbxUuid.random() KdbxUuid.random()
: this(base64.encode(uuidGenerator.parse(uuidGenerator.v4()))); : this(base64.encode(uuidGenerator.parse(uuidGenerator.v4())));
KdbxUuid.fromBytes(Uint8List bytes) : this(base64.encode(bytes));
/// https://tools.ietf.org/html/rfc4122.html#section-4.1.7 /// https://tools.ietf.org/html/rfc4122.html#section-4.1.7
/// > The nil UUID is special form of UUID that is specified to have all /// > The nil UUID is special form of UUID that is specified to have all
/// 128 bits set to zero. /// 128 bits set to zero.

Loading…
Cancel
Save