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).
library kdbx;
export 'src/crypto/key_encrypter_kdf.dart' show KeyEncrypterKdf, KdfType;
export 'src/crypto/protected_value.dart'
show ProtectedValue, StringValue, PlainValue;
export 'src/kdbx_binary.dart' show KdbxBinary;
@ -16,6 +17,7 @@ export 'src/kdbx_header.dart'
KdbxException,
KdbxInvalidKeyException,
KdbxCorruptedFileException,
KdbxUnsupportedException;
KdbxUnsupportedException,
KdbxVersion;
export 'src/kdbx_meta.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)';
}
extension Uint8ListExt on Uint8List {
String encodeBase64() => base64.encode(this);
}
class ReaderHelper {
factory ReaderHelper(Uint8List byteData) => KdbxFormat.dartWebWorkaround
? ReaderHelperDartWeb(byteData)

10
lib/src/internal/consts.dart

@ -1,7 +1,12 @@
import 'dart:typed_data';
import 'package:kdbx/src/kdbx_object.dart';
enum Cipher {
/// the only cipher supported in kdbx <= 3
aes,
/// Support since kdbx 4.
chaCha20,
}
@ -10,4 +15,9 @@ class CryptoConsts {
Cipher.aes: KdbxUuid('McHy5r9xQ1C+WAUhavxa/w=='),
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(
KdbxFile kdbxFile, Uint8List compressedBytes, Uint8List cipherKey) {
final header = kdbxFile.header;
final cipherId = base64.encode(header.fields[HeaderFields.CipherID].bytes);
if (cipherId == CryptoConsts.CIPHER_IDS[Cipher.aes].uuid) {
final cipher = header.cipher;
if (cipher == Cipher.aes) {
_logger.fine('We need AES');
final result = kdbxFile.kdbxFormat
._encryptContentV4Aes(header, cipherKey, compressedBytes);
// _logger.fine('Result: ${ByteUtils.toHexList(result)}');
return result;
} else if (cipherId == CryptoConsts.CIPHER_IDS[Cipher.chaCha20].uuid) {
} else if (cipher == Cipher.chaCha20) {
_logger.fine('We need chacha20');
return kdbxFile.kdbxFormat
.transformContentV4ChaCha20(header, compressedBytes, cipherKey);
} else {
throw UnsupportedError('Unsupported cipherId $cipherId');
throw UnsupportedError('Unsupported cipherId $cipher');
}
}
@ -308,7 +308,7 @@ class KdbxBody extends KdbxNode {
// final doc = xml.XmlDocument();
// doc.children.add(xml.XmlProcessing(
// 'xml', 'version="1.0" encoding="utf-8" standalone="yes"'));
final node = builder.build() as xml.XmlDocument;
final node = builder.buildDocument();
return node;
}
@ -533,17 +533,17 @@ class KdbxFormat {
Uint8List decrypt(
KdbxHeader header, Uint8List encrypted, Uint8List cipherKey) {
final cipherId = base64.encode(header.fields[HeaderFields.CipherID].bytes);
if (cipherId == CryptoConsts.CIPHER_IDS[Cipher.aes].uuid) {
final cipher = header.cipher;
if (cipher == Cipher.aes) {
_logger.fine('We need AES');
final result = _decryptContentV4(header, cipherKey, encrypted);
return result;
} else if (cipherId == CryptoConsts.CIPHER_IDS[Cipher.chaCha20].uuid) {
} else if (cipher == Cipher.chaCha20) {
_logger.fine('We need chacha20');
// throw UnsupportedError('chacha20 not yet supported $cipherId');
return transformContentV4ChaCha20(header, encrypted, cipherKey);
} 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 'package:crypto/crypto.dart' as crypto;
@ -49,6 +48,8 @@ enum ProtectedValueEncryption { plainText, arc4variant, salsa20, chaCha20 }
enum HeaderFields {
EndOfHeader,
Comment,
/// the cipher to use as defined by [Cipher]. in kdbx 3 this is always aes.
CipherID,
CompressionFlags,
MasterSeed,
@ -262,9 +263,8 @@ class KdbxHeader {
kdfParameters, ByteUtils.randomBytes(Consts.DefaultKdfSaltLength));
// var ivLength = this.dataCipherUuid.toString() === Consts.CipherId.ChaCha20 ? 12 : 16;
// this.encryptionIV = Random.getBytes(ivLength);
final cipherId = base64.encode(fields[HeaderFields.CipherID].bytes);
final ivLength =
cipherId == CryptoConsts.CIPHER_IDS[Cipher.chaCha20].uuid ? 12 : 16;
final cipher = this.cipher;
final ivLength = cipher == Cipher.chaCha20 ? 12 : 16;
_setHeaderField(
HeaderFields.EncryptionIV, ByteUtils.randomBytes(ivLength));
} else {
@ -471,6 +471,37 @@ class KdbxHeader {
/// end position of the header, if we have been reading from a stream.
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 {
final id =
ReaderHelper.singleUint32(fields[HeaderFields.CompressionFlags].bytes);

2
lib/src/kdbx_object.dart

@ -175,6 +175,8 @@ class KdbxUuid {
KdbxUuid.random()
: 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
/// > The nil UUID is special form of UUID that is specified to have all
/// 128 bits set to zero.

Loading…
Cancel
Save