Kotlinized VariantDictionnary and fix database creation

This commit is contained in:
J-Jamet
2020-04-24 20:35:36 +02:00
parent 5532147992
commit 1efaf4e3ea
12 changed files with 393 additions and 386 deletions

View File

@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.crypto.finalkey
import com.kunzisoft.keepass.crypto.CipherFactory.deviceBlacklisted
object AESKeyTransformerFactory : KeyTransformer() {
override fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long): ByteArray? {
override fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long?): ByteArray? {
// Prefer the native final key implementation
val keyTransformer = if (!deviceBlacklisted()
&& NativeAESKeyTransformer.available()) {

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.finalkey;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;
public class AndroidAESKeyTransformer extends KeyTransformer {
@Override
public byte[] transformMasterKey(byte[] pKeySeed, byte[] pKey, long rounds) throws IOException {
Cipher cipher;
try {
cipher = Cipher.getInstance("AES/ECB/NoPadding");
} catch (NoSuchAlgorithmException e) {
throw new IOException("NoSuchAlgorithm: " + e.getMessage());
} catch (NoSuchPaddingException e) {
throw new IOException("NoSuchPadding: " + e.getMessage());
}
try {
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(pKeySeed, "AES"));
} catch (InvalidKeyException e) {
throw new IOException("InvalidPasswordException: " + e.getMessage());
}
// Encrypt key rounds times
byte[] newKey = new byte[pKey.length];
System.arraycopy(pKey, 0, newKey, 0, pKey.length);
byte[] destKey = new byte[pKey.length];
for (int i = 0; i < rounds; i++) {
try {
cipher.update(newKey, 0, newKey.length, destKey, 0);
System.arraycopy(destKey, 0, newKey, 0, newKey.length);
} catch (ShortBufferException e) {
throw new IOException("Short buffer: " + e.getMessage());
}
}
// Hash the key
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
assert true;
throw new IOException("SHA-256 not implemented here: " + e.getMessage());
}
md.update(newKey);
return md.digest();
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.finalkey
import java.io.IOException
import java.lang.Exception
import java.security.InvalidKeyException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
import javax.crypto.ShortBufferException
import javax.crypto.spec.SecretKeySpec
class AndroidAESKeyTransformer : KeyTransformer() {
@Throws(IOException::class)
override fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long?): ByteArray? {
val cipher: Cipher = try {
Cipher.getInstance("AES/ECB/NoPadding")
} catch (e: Exception) {
throw IOException("Unable to get the cipher", e)
}
try {
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(seed, "AES"))
} catch (e: InvalidKeyException) {
throw IOException("Unable to init the cipher", e)
}
if (key == null) {
throw IOException("Invalid key")
}
if (rounds == null) {
throw IOException("Invalid rounds")
}
// Encrypt key rounds times
val keyLength = key.size
val newKey = ByteArray(keyLength)
System.arraycopy(key, 0, newKey, 0, keyLength)
val destKey = ByteArray(keyLength)
for (i in 0 until rounds) {
try {
cipher.update(newKey, 0, newKey.size, destKey, 0)
System.arraycopy(destKey, 0, newKey, 0, newKey.size)
} catch (e: ShortBufferException) {
throw IOException("Short buffer", e)
}
}
// Hash the key
val messageDigest: MessageDigest = try {
MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("SHA-256 not implemented here: " + e.message)
}
messageDigest.update(newKey)
return messageDigest.digest()
}
}

View File

@@ -23,5 +23,5 @@ import java.io.IOException
abstract class KeyTransformer {
@Throws(IOException::class)
abstract fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long): ByteArray?
abstract fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long?): ByteArray?
}

View File

@@ -21,6 +21,8 @@ package com.kunzisoft.keepass.crypto.finalkey;
import com.kunzisoft.keepass.crypto.NativeLib;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
@@ -30,8 +32,9 @@ public class NativeAESKeyTransformer extends KeyTransformer {
return NativeLib.INSTANCE.init();
}
@Nullable
@Override
public byte[] transformMasterKey(byte[] seed, byte[] key, long rounds) throws IOException {
public byte[] transformMasterKey(@Nullable byte[] seed, @Nullable byte[] key, @Nullable Long rounds) throws IOException {
NativeLib.INSTANCE.init();
return nTransformMasterKey(seed, key, rounds);

View File

@@ -24,63 +24,63 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CryptoUtil
import com.kunzisoft.keepass.crypto.finalkey.AESKeyTransformerFactory
import com.kunzisoft.keepass.stream.bytes16ToUuid
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException
import java.security.SecureRandom
import java.util.*
class AesKdf internal constructor() : KdfEngine() {
class AesKdf : KdfEngine() {
init {
uuid = CIPHER_UUID
}
override val defaultParameters: KdfParameters
get() {
return KdfParameters(uuid!!).apply {
setParamUUID()
setUInt32(PARAM_ROUNDS, UnsignedInt.fromLong(defaultKeyRounds))
setUInt64(PARAM_ROUNDS, defaultKeyRounds)
}
}
override val defaultKeyRounds: Long = 6000L
init {
uuid = CIPHER_UUID
}
override fun getName(resources: Resources): String {
return resources.getString(R.string.kdf_AES)
}
@Throws(IOException::class)
override fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray {
var currentMasterKey = masterKey
val rounds = p.getUInt64(PARAM_ROUNDS)
var seed = p.getByteArray(PARAM_SEED)
override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray {
var seed = kdfParameters.getByteArray(PARAM_SEED)
if (seed != null && seed.size != 32) {
seed = CryptoUtil.hashSha256(seed)
}
var currentMasterKey = masterKey
if (currentMasterKey.size != 32) {
currentMasterKey = CryptoUtil.hashSha256(currentMasterKey)
}
if (seed.size != 32) {
seed = CryptoUtil.hashSha256(seed)
}
val rounds = kdfParameters.getUInt64(PARAM_ROUNDS)
return AESKeyTransformerFactory.transformMasterKey(seed, currentMasterKey, rounds) ?: ByteArray(0)
}
override fun randomize(p: KdfParameters) {
override fun randomize(kdfParameters: KdfParameters) {
val random = SecureRandom()
val seed = ByteArray(32)
random.nextBytes(seed)
p.setByteArray(PARAM_SEED, seed)
kdfParameters.setByteArray(PARAM_SEED, seed)
}
override fun getKeyRounds(p: KdfParameters): Long {
return p.getUInt64(PARAM_ROUNDS)
override fun getKeyRounds(kdfParameters: KdfParameters): Long {
return kdfParameters.getUInt64(PARAM_ROUNDS) ?: defaultKeyRounds
}
override fun setKeyRounds(p: KdfParameters, keyRounds: Long) {
p.setUInt64(PARAM_ROUNDS, keyRounds)
override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) {
kdfParameters.setUInt64(PARAM_ROUNDS, keyRounds)
}
companion object {
@@ -103,7 +103,7 @@ class AesKdf internal constructor() : KdfEngine() {
0x4F.toByte(),
0xEA.toByte()))
const val PARAM_ROUNDS = "R"
const val PARAM_SEED = "S"
const val PARAM_ROUNDS = "R" // UInt64
const val PARAM_SEED = "S" // Byte array
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
@@ -54,15 +54,23 @@ class Argon2Kdf internal constructor() : KdfEngine() {
}
@Throws(IOException::class)
override fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray {
override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray {
val salt = p.getByteArray(PARAM_SALT)
val parallelism = UnsignedInt(p.getUInt32(PARAM_PARALLELISM))
val memory = UnsignedInt.fromLong(p.getUInt64(PARAM_MEMORY) / MEMORY_BLOCK_SIZE)
val iterations = UnsignedInt.fromLong(p.getUInt64(PARAM_ITERATIONS))
val version = UnsignedInt(p.getUInt32(PARAM_VERSION))
val secretKey = p.getByteArray(PARAM_SECRET_KEY)
val assocData = p.getByteArray(PARAM_ASSOC_DATA)
val salt = kdfParameters.getByteArray(PARAM_SALT)
val parallelism = kdfParameters.getUInt32(PARAM_PARALLELISM)?.let {
UnsignedInt(it)
}
val memory = kdfParameters.getUInt64(PARAM_MEMORY)?.div(MEMORY_BLOCK_SIZE)?.let {
UnsignedInt.fromLong(it)
}
val iterations = kdfParameters.getUInt64(PARAM_ITERATIONS)?.let {
UnsignedInt.fromLong(it)
}
val version = kdfParameters.getUInt32(PARAM_VERSION)?.let {
UnsignedInt(it)
}
val secretKey = kdfParameters.getByteArray(PARAM_SECRET_KEY)
val assocData = kdfParameters.getByteArray(PARAM_ASSOC_DATA)
return Argon2Native.transformKey(masterKey,
salt,
@@ -74,21 +82,21 @@ class Argon2Kdf internal constructor() : KdfEngine() {
version)
}
override fun randomize(p: KdfParameters) {
override fun randomize(kdfParameters: KdfParameters) {
val random = SecureRandom()
val salt = ByteArray(32)
random.nextBytes(salt)
p.setByteArray(PARAM_SALT, salt)
kdfParameters.setByteArray(PARAM_SALT, salt)
}
override fun getKeyRounds(p: KdfParameters): Long {
return p.getUInt64(PARAM_ITERATIONS)
override fun getKeyRounds(kdfParameters: KdfParameters): Long {
return kdfParameters.getUInt64(PARAM_ITERATIONS) ?: defaultKeyRounds
}
override fun setKeyRounds(p: KdfParameters, keyRounds: Long) {
p.setUInt64(PARAM_ITERATIONS, keyRounds)
override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) {
kdfParameters.setUInt64(PARAM_ITERATIONS, keyRounds)
}
override val minKeyRounds: Long
@@ -97,12 +105,12 @@ class Argon2Kdf internal constructor() : KdfEngine() {
override val maxKeyRounds: Long
get() = MAX_ITERATIONS
override fun getMemoryUsage(p: KdfParameters): Long {
return p.getUInt64(PARAM_MEMORY)
override fun getMemoryUsage(kdfParameters: KdfParameters): Long {
return kdfParameters.getUInt64(PARAM_MEMORY) ?: defaultMemoryUsage
}
override fun setMemoryUsage(p: KdfParameters, memory: Long) {
p.setUInt64(PARAM_MEMORY, memory)
override fun setMemoryUsage(kdfParameters: KdfParameters, memory: Long) {
kdfParameters.setUInt64(PARAM_MEMORY, memory)
}
override val defaultMemoryUsage: Long
@@ -114,12 +122,14 @@ class Argon2Kdf internal constructor() : KdfEngine() {
override val maxMemoryUsage: Long
get() = MAX_MEMORY
override fun getParallelism(p: KdfParameters): Long {
return UnsignedInt(p.getUInt32(PARAM_PARALLELISM)).toLong()
override fun getParallelism(kdfParameters: KdfParameters): Long {
return kdfParameters.getUInt32(PARAM_PARALLELISM)?.let {
UnsignedInt(it).toLong()
} ?: defaultParallelism
}
override fun setParallelism(p: KdfParameters, parallelism: Long) {
p.setUInt32(PARAM_PARALLELISM, UnsignedInt.fromLong(parallelism))
override fun setParallelism(kdfParameters: KdfParameters, parallelism: Long) {
kdfParameters.setUInt32(PARAM_PARALLELISM, UnsignedInt.fromLong(parallelism))
}
override val defaultParallelism: Long

View File

@@ -34,17 +34,17 @@ abstract class KdfEngine : ObjectNameResource, Serializable {
abstract val defaultParameters: KdfParameters
@Throws(IOException::class)
abstract fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray
abstract fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray
abstract fun randomize(p: KdfParameters)
abstract fun randomize(kdfParameters: KdfParameters)
/*
* ITERATIONS
*/
abstract fun getKeyRounds(p: KdfParameters): Long
abstract fun getKeyRounds(kdfParameters: KdfParameters): Long
abstract fun setKeyRounds(p: KdfParameters, keyRounds: Long)
abstract fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long)
abstract val defaultKeyRounds: Long
@@ -58,11 +58,11 @@ abstract class KdfEngine : ObjectNameResource, Serializable {
* MEMORY
*/
open fun getMemoryUsage(p: KdfParameters): Long {
open fun getMemoryUsage(kdfParameters: KdfParameters): Long {
return UNKNOWN_VALUE
}
open fun setMemoryUsage(p: KdfParameters, memory: Long) {
open fun setMemoryUsage(kdfParameters: KdfParameters, memory: Long) {
// Do nothing by default
}
@@ -79,11 +79,11 @@ abstract class KdfEngine : ObjectNameResource, Serializable {
* PARALLELISM
*/
open fun getParallelism(p: KdfParameters): Long {
open fun getParallelism(kdfParameters: KdfParameters): Long {
return UNKNOWN_VALUE
}
open fun setParallelism(p: KdfParameters, parallelism: Long) {
open fun setParallelism(kdfParameters: KdfParameters, parallelism: Long) {
// Do nothing by default
}

View File

@@ -29,7 +29,7 @@ import java.io.ByteArrayOutputStream
import java.io.IOException
import java.util.*
class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
class KdfParameters(val uuid: UUID) : VariantDictionary() {
fun setParamUUID() {
setByteArray(PARAM_UUID, uuidTo16Bytes(uuid))
@@ -41,26 +41,25 @@ class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
@Throws(IOException::class)
fun deserialize(data: ByteArray): KdfParameters? {
val bis = ByteArrayInputStream(data)
val lis = LittleEndianDataInputStream(bis)
val inputStream = LittleEndianDataInputStream(ByteArrayInputStream(data))
val dictionary = deserialize(inputStream)
val d = deserialize(lis) ?: return null
val uuidBytes = dictionary.getByteArray(PARAM_UUID) ?: return null
val uuid = bytes16ToUuid(uuidBytes)
val uuid = bytes16ToUuid(d.getByteArray(PARAM_UUID))
val kdfP = KdfParameters(uuid)
kdfP.copyTo(d)
return kdfP
val kdfParameters = KdfParameters(uuid)
kdfParameters.copyTo(dictionary)
return kdfParameters
}
@Throws(IOException::class)
fun serialize(kdf: KdfParameters): ByteArray {
val bos = ByteArrayOutputStream()
val los = LittleEndianDataOutputStream(bos)
fun serialize(kdfParameters: KdfParameters): ByteArray {
val byteArrayOutputStream = ByteArrayOutputStream()
val outputStream = LittleEndianDataOutputStream(byteArrayOutputStream)
serialize(kdf, los)
serialize(kdfParameters, outputStream)
return bos.toByteArray()
return byteArrayOutputStream.toByteArray()
}
}

View File

@@ -54,7 +54,9 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
get() = databaseV4.kdfParameters?.getByteArray(AesKdf.PARAM_SEED)
private set(seed) {
assignAesKdfEngineIfNotExists()
databaseV4.kdfParameters?.setByteArray(AesKdf.PARAM_SEED, seed)
seed?.let {
databaseV4.kdfParameters?.setByteArray(AesKdf.PARAM_SEED, it)
}
}
object PwDbHeaderV4Fields {

View File

@@ -1,237 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.utils;
import com.kunzisoft.keepass.stream.LittleEndianDataInputStream;
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import static com.kunzisoft.keepass.stream.StreamBytesUtilsKt.bytes4ToUInt;
import static com.kunzisoft.keepass.stream.StreamBytesUtilsKt.bytes64ToLong;
public class VariantDictionary {
private static final int VdVersion = 0x0100;
private static final int VdmCritical = 0xFF00;
private static final int VdmInfo = 0x00FF;
private static Charset UTF8Charset = Charset.forName("UTF-8");
private Map<String, VdType> dict = new HashMap<>();
private class VdType {
public static final byte None = 0x00;
public static final byte UInt32 = 0x04;
public static final byte UInt64 =0x05;
public static final byte Bool =0x08;
public static final byte Int32 =0x0C;
public static final byte Int64 =0x0D;
public static final byte String =0x18;
public static final byte ByteArray =0x42;
public final byte type;
public final Object value;
VdType(byte type, Object value) {
this.type = type;
this.value = value;
}
}
private Object getValue(String name) {
VdType val = dict.get(name);
if (val == null) {
return null;
}
return val.value;
}
private void putType(byte type, String name, Object value) {
dict.put(name, new VdType(type, value));
}
public void setUInt32(String name, UnsignedInt value) { putType(VdType.UInt32, name, value); }
public UnsignedInt getUInt32(String name) { return (UnsignedInt)dict.get(name).value; }
public void setUInt64(String name, long value) { putType(VdType.UInt64, name, value); }
public long getUInt64(String name) { return (long)dict.get(name).value; }
public void setBool(String name, boolean value) { putType(VdType.Bool, name, value); }
public boolean getBool(String name) { return (boolean)dict.get(name).value; }
public void setInt32(String name, int value) { putType(VdType.Int32 ,name, value); }
public int getInt32(String name) { return (int)dict.get(name).value; }
public void setInt64(String name, long value) { putType(VdType.Int64 ,name, value); }
public long getInt64(String name) { return (long)dict.get(name).value; }
public void setString(String name, String value) { putType(VdType.String ,name, value); }
public String getString(String name) { return (String)getValue(name); }
public void setByteArray(String name, byte[] value) { putType(VdType.ByteArray, name, value); }
public byte[] getByteArray(String name) { return (byte[])getValue(name); }
public static VariantDictionary deserialize(LittleEndianDataInputStream lis) throws IOException {
VariantDictionary d = new VariantDictionary();
int version = lis.readUShort();
if ((version & VdmCritical) > (VdVersion & VdmCritical)) {
throw new IOException("Invalid format");
}
while(true) {
int type = lis.read();
if (type < 0) {
throw new IOException(("Invalid format"));
}
byte bType = (byte)type;
if (bType == VdType.None) {
break;
}
int nameLen = lis.readUInt().toInt();
byte[] nameBuf = lis.readBytes(nameLen);
if (nameLen != nameBuf.length) {
throw new IOException("Invalid format");
}
String name = new String(nameBuf, UTF8Charset);
int valueLen = lis.readUInt().toInt();
byte[] valueBuf = lis.readBytes(valueLen);
if (valueLen != valueBuf.length) {
throw new IOException("Invalid format");
}
switch (bType) {
case VdType.UInt32:
if (valueLen == 4) {
d.setUInt32(name, bytes4ToUInt(valueBuf));
}
break;
case VdType.UInt64:
if (valueLen == 8) {
d.setUInt64(name, bytes64ToLong(valueBuf));
}
break;
case VdType.Bool:
if (valueLen == 1) {
d.setBool(name, valueBuf[0] != 0);
}
break;
case VdType.Int32:
if (valueLen == 4) {
d.setInt32(name, bytes4ToUInt(valueBuf).toInt());
}
break;
case VdType.Int64:
if (valueLen == 8) {
d.setInt64(name, bytes64ToLong(valueBuf));
}
break;
case VdType.String:
d.setString(name, new String(valueBuf, UTF8Charset));
break;
case VdType.ByteArray:
d.setByteArray(name, valueBuf);
break;
default:
break;
}
}
return d;
}
public static void serialize(VariantDictionary d,
LittleEndianDataOutputStream los) throws IOException{
if (los == null) {
return;
}
los.writeUShort(VdVersion);
for (Map.Entry<String, VdType> entry: d.dict.entrySet()) {
String name = entry.getKey();
byte[] nameBuf = nameBuf = name.getBytes(UTF8Charset);
VdType vd = entry.getValue();
los.write(vd.type);
los.writeInt(nameBuf.length);
los.write(nameBuf);
byte[] buf;
switch (vd.type) {
case VdType.UInt32:
los.writeInt(4);
los.writeUInt((UnsignedInt) vd.value);
break;
case VdType.UInt64:
los.writeInt(8);
los.writeLong((long)vd.value);
break;
case VdType.Bool:
los.writeInt(1);
byte bool = (boolean)vd.value ? (byte)1 : (byte)0;
los.write(bool);
break;
case VdType.Int32:
los.writeInt(4);
los.writeInt((int)vd.value);
break;
case VdType.Int64:
los.writeInt(8);
los.writeLong((long)vd.value);
break;
case VdType.String:
String value = (String)vd.value;
buf = value.getBytes(UTF8Charset);
los.writeInt(buf.length);
los.write(buf);
break;
case VdType.ByteArray:
buf = (byte[])vd.value;
los.writeInt(buf.length);
los.write(buf);
break;
default:
break;
}
}
los.write(VdType.None);
}
public void copyTo(VariantDictionary d) {
for (Map.Entry<String, VdType> entry : d.dict.entrySet()) {
String key = entry.getKey();
VdType value = entry.getValue();
dict.put(key, value);
}
}
public int size() {
return dict.size();
}
}

View File

@@ -0,0 +1,233 @@
/*
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.utils
import com.kunzisoft.keepass.stream.LittleEndianDataInputStream
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
import com.kunzisoft.keepass.stream.bytes4ToUInt
import com.kunzisoft.keepass.stream.bytes64ToLong
import java.io.IOException
import java.nio.charset.Charset
import java.util.*
open class VariantDictionary {
private val dict: MutableMap<String, VdType> = HashMap()
private fun getValue(name: String): Any? {
return dict[name]?.value ?: return null
}
private fun putType(type: Byte, name: String, value: Any) {
dict[name] = VdType(type, value)
}
fun setUInt32(name: String, value: UnsignedInt) {
putType(VdType.UInt32, name, value)
}
fun getUInt32(name: String): UnsignedInt? {
return dict[name]?.value as UnsignedInt?
}
fun setUInt64(name: String, value: Long) {
putType(VdType.UInt64, name, value)
}
fun getUInt64(name: String): Long? {
return dict[name]?.value as Long?
}
fun setBool(name: String, value: Boolean) {
putType(VdType.Bool, name, value)
}
fun getBool(name: String): Boolean? {
return dict[name]?.value as Boolean?
}
fun setInt32(name: String, value: Int) {
putType(VdType.Int32, name, value)
}
fun getInt32(name: String): Int? {
return dict[name]?.value as Int?
}
fun setInt64(name: String, value: Long) {
putType(VdType.Int64, name, value)
}
fun getInt64(name: String): Long? {
return dict[name]?.value as Long?
}
fun setString(name: String, value: String) {
putType(VdType.String, name, value)
}
fun getString(name: String): String? {
return getValue(name) as String?
}
fun setByteArray(name: String, value: ByteArray) {
putType(VdType.ByteArray, name, value)
}
fun getByteArray(name: String): ByteArray? {
return getValue(name) as ByteArray?
}
fun copyTo(d: VariantDictionary) {
for ((key, value) in d.dict) {
dict[key] = value
}
}
fun size(): Int {
return dict.size
}
companion object {
private const val VdVersion = 0x0100
private const val VdmCritical = 0xFF00
private const val VdmInfo = 0x00FF
private val UTF8Charset = Charset.forName("UTF-8")
@Throws(IOException::class)
fun deserialize(inputStream: LittleEndianDataInputStream): VariantDictionary {
val dictionary = VariantDictionary()
val version = inputStream.readUShort()
if (version and VdmCritical > VdVersion and VdmCritical) {
throw IOException("Invalid format")
}
while (true) {
val type = inputStream.read()
if (type < 0) {
throw IOException("Invalid format")
}
val bType = type.toByte()
if (bType == VdType.None) {
break
}
val nameLen = inputStream.readUInt().toInt()
val nameBuf = inputStream.readBytes(nameLen)
if (nameLen != nameBuf.size) {
throw IOException("Invalid format")
}
val name = String(nameBuf, UTF8Charset)
val valueLen = inputStream.readUInt().toInt()
val valueBuf = inputStream.readBytes(valueLen)
if (valueLen != valueBuf.size) {
throw IOException("Invalid format")
}
when (bType) {
VdType.UInt32 -> if (valueLen == 4) {
dictionary.setUInt32(name, bytes4ToUInt(valueBuf))
}
VdType.UInt64 -> if (valueLen == 8) {
dictionary.setUInt64(name, bytes64ToLong(valueBuf))
}
VdType.Bool -> if (valueLen == 1) {
dictionary.setBool(name, valueBuf[0] != 0.toByte())
}
VdType.Int32 -> if (valueLen == 4) {
dictionary.setInt32(name, bytes4ToUInt(valueBuf).toInt())
}
VdType.Int64 -> if (valueLen == 8) {
dictionary.setInt64(name, bytes64ToLong(valueBuf))
}
VdType.String -> dictionary.setString(name, String(valueBuf, UTF8Charset))
VdType.ByteArray -> dictionary.setByteArray(name, valueBuf)
else -> {
}
}
}
return dictionary
}
@Throws(IOException::class)
fun serialize(variantDictionary: VariantDictionary,
outputStream: LittleEndianDataOutputStream?) {
if (outputStream == null) {
return
}
outputStream.writeUShort(VdVersion)
for ((name, vd) in variantDictionary.dict) {
val nameBuf = name.toByteArray(UTF8Charset)
outputStream.write(vd.type.toInt())
outputStream.writeInt(nameBuf.size)
outputStream.write(nameBuf)
var buf: ByteArray
when (vd.type) {
VdType.UInt32 -> {
outputStream.writeInt(4)
outputStream.writeUInt((vd.value as UnsignedInt))
}
VdType.UInt64 -> {
outputStream.writeInt(8)
outputStream.writeLong(vd.value as Long)
}
VdType.Bool -> {
outputStream.writeInt(1)
val bool = if (vd.value as Boolean) 1.toByte() else 0.toByte()
outputStream.write(bool.toInt())
}
VdType.Int32 -> {
outputStream.writeInt(4)
outputStream.writeInt(vd.value as Int)
}
VdType.Int64 -> {
outputStream.writeInt(8)
outputStream.writeLong(vd.value as Long)
}
VdType.String -> {
val value = vd.value as String
buf = value.toByteArray(UTF8Charset)
outputStream.writeInt(buf.size)
outputStream.write(buf)
}
VdType.ByteArray -> {
buf = vd.value as ByteArray
outputStream.writeInt(buf.size)
outputStream.write(buf)
}
else -> {
}
}
}
outputStream.write(VdType.None.toInt())
}
}
class VdType(val type: Byte, val value: Any) {
companion object {
const val None: Byte = 0x00
const val UInt32: Byte = 0x04
const val UInt64: Byte = 0x05
const val Bool: Byte = 0x08
const val Int32: Byte = 0x0C
const val Int64: Byte = 0x0D
const val String: Byte = 0x18
const val ByteArray: Byte = 0x42
}
}
}