Fix #327 save to previous database version when conditions are required

This commit is contained in:
J-Jamet
2019-09-11 16:54:58 +02:00
parent 5bd9da9bb1
commit c5e2ca9907
9 changed files with 64 additions and 22 deletions

View File

@@ -296,6 +296,8 @@ abstract class PwDatabase<Group : PwGroup<*, Group, Entry>, Entry : PwEntry<Grou
abstract fun rootCanContainsEntry(): Boolean abstract fun rootCanContainsEntry(): Boolean
abstract fun containsCustomData(): Boolean
fun addGroupTo(newGroup: Group, parent: Group?) { fun addGroupTo(newGroup: Group, parent: Group?) {
// Add tree to parent tree // Add tree to parent tree
parent?.addChildGroup(newGroup) parent?.addChildGroup(newGroup)

View File

@@ -154,6 +154,10 @@ class PwDatabaseV3 : PwDatabase<PwGroupV3, PwEntryV3>() {
return false return false
} }
override fun containsCustomData(): Boolean {
return false
}
override fun isBackup(group: PwGroupV3): Boolean { override fun isBackup(group: PwGroupV3): Boolean {
var currentGroup: PwGroupV3? = group var currentGroup: PwGroupV3? = group
while (currentGroup != null) { while (currentGroup != null) {

View File

@@ -196,6 +196,10 @@ class PwDatabaseV4 : PwDatabase<PwGroupV4, PwEntryV4> {
this.customData[label] = value this.customData[label] = value
} }
override fun containsCustomData(): Boolean {
return getCustomData().isNotEmpty()
}
@Throws(InvalidKeyFileException::class, IOException::class) @Throws(InvalidKeyFileException::class, IOException::class)
public override fun getMasterKey(key: String?, keyInputStream: InputStream?): ByteArray { public override fun getMasterKey(key: String?, keyInputStream: InputStream?): ByteArray {

View File

@@ -26,7 +26,7 @@ import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.utils.MemoryUtil import com.kunzisoft.keepass.utils.MemoryUtil
import java.util.* import java.util.*
class PwEntryV4 : PwEntry<PwGroupV4, PwEntryV4>, NodeV4Interface { class PwEntryV4 : PwEntry<PwGroupV4, PwEntryV4>, PwNodeV4Interface {
// To decode each field not parcelable // To decode each field not parcelable
@Transient @Transient
@@ -275,12 +275,12 @@ class PwEntryV4 : PwEntry<PwGroupV4, PwEntryV4>, NodeV4Interface {
return history.size return history.size
} }
fun putCustomData(key: String, value: String) { override fun putCustomData(key: String, value: String) {
customData[key] = value customData[key] = value
} }
fun containsCustomData(): Boolean { override fun containsCustomData(): Boolean {
return customData.size > 0 return customData.isNotEmpty()
} }
fun addEntryToHistory(entry: PwEntryV4) { fun addEntryToHistory(entry: PwEntryV4) {

View File

@@ -25,7 +25,7 @@ import android.os.Parcelable
import java.util.HashMap import java.util.HashMap
import java.util.UUID import java.util.UUID
class PwGroupV4 : PwGroup<UUID, PwGroupV4, PwEntryV4>, NodeV4Interface { class PwGroupV4 : PwGroup<UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
// TODO Encapsulate // TODO Encapsulate
override var icon: PwIcon override var icon: PwIcon
@@ -123,12 +123,12 @@ class PwGroupV4 : PwGroup<UUID, PwGroupV4, PwEntryV4>, NodeV4Interface {
locationChanged = PwDate() locationChanged = PwDate()
} }
fun putCustomData(key: String, value: String) { override fun putCustomData(key: String, value: String) {
customData[key] = value customData[key] = value
} }
fun containsCustomData(): Boolean { override fun containsCustomData(): Boolean {
return customData.size > 0 return customData.isNotEmpty()
} }
override fun allowAddEntryIfIsRoot(): Boolean { override fun allowAddEntryIfIsRoot(): Boolean {

View File

@@ -19,10 +19,14 @@
*/ */
package com.kunzisoft.keepass.database.element package com.kunzisoft.keepass.database.element
interface NodeV4Interface : NodeTimeInterface { interface PwNodeV4Interface : NodeTimeInterface {
var usageCount: Long var usageCount: Long
var locationChanged: PwDate var locationChanged: PwDate
fun putCustomData(key: String, value: String)
fun containsCustomData(): Boolean
} }

View File

@@ -23,7 +23,11 @@ import com.kunzisoft.keepass.crypto.CrsAlgorithm
import com.kunzisoft.keepass.crypto.keyDerivation.AesKdf import com.kunzisoft.keepass.crypto.keyDerivation.AesKdf
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
import com.kunzisoft.keepass.database.NodeHandler
import com.kunzisoft.keepass.database.element.PwNodeV4Interface
import com.kunzisoft.keepass.database.element.PwDatabaseV4 import com.kunzisoft.keepass.database.element.PwDatabaseV4
import com.kunzisoft.keepass.database.element.PwEntryV4
import com.kunzisoft.keepass.database.element.PwGroupV4
import com.kunzisoft.keepass.database.exception.InvalidDBVersionException import com.kunzisoft.keepass.database.exception.InvalidDBVersionException
import com.kunzisoft.keepass.stream.CopyInputStream import com.kunzisoft.keepass.stream.CopyInputStream
import com.kunzisoft.keepass.stream.HmacBlockStream import com.kunzisoft.keepass.stream.HmacBlockStream
@@ -88,18 +92,42 @@ class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() {
this.masterSeed = ByteArray(32) this.masterSeed = ByteArray(32)
} }
private inner class NodeHasCustomData<T:PwNodeV4Interface> : NodeHandler<T>() {
internal var containsCustomData = false
override fun operate(node: T): Boolean {
if (node.containsCustomData()) {
containsCustomData = true
return false
}
return true
}
}
private fun getMinKdbxVersion(databaseV4: PwDatabaseV4): Long { private fun getMinKdbxVersion(databaseV4: PwDatabaseV4): Long {
// https://keepass.info/help/kb/kdbx_4.html
// Return v4 if AES is not use
if (databaseV4.kdfParameters != null
&& databaseV4.kdfParameters!!.uuid != AesKdf.CIPHER_UUID) {
return FILE_VERSION_32_4
}
if (databaseV4.rootGroup == null) { if (databaseV4.rootGroup == null) {
return FILE_VERSION_32_3 return FILE_VERSION_32_3
} }
// Return v4 if AES is not use val entryHandler = NodeHasCustomData<PwEntryV4>()
if (databaseV4.kdfParameters != null && databaseV4.kdfParameters!!.uuid != AesKdf.CIPHER_UUID) { val groupHandler = NodeHasCustomData<PwGroupV4>()
return FILE_VERSION_32_4 databaseV4.rootGroup?.doForEachChildAndForIt(entryHandler, groupHandler)
return if (databaseV4.containsCustomData()
|| entryHandler.containsCustomData
|| groupHandler.containsCustomData) {
FILE_VERSION_32_4
} else {
FILE_VERSION_32_3
} }
// Return V4 by default
return FILE_VERSION_32_4
} }
/** Assumes the input stream is at the beginning of the .kdbx file /** Assumes the input stream is at the beginning of the .kdbx file

View File

@@ -577,12 +577,12 @@ class ImporterV4(private val streamDir: File) : Importer<PwDatabaseV4>() {
} }
KdbContext.GroupTimes, KdbContext.EntryTimes -> { KdbContext.GroupTimes, KdbContext.EntryTimes -> {
val tl: NodeV4Interface? val tl: PwNodeV4Interface? =
if (ctx == KdbContext.GroupTimes) { if (ctx == KdbContext.GroupTimes) {
tl = ctxGroup ctxGroup
} else { } else {
tl = ctxEntry ctxEntry
} }
when { when {
name.equals(PwDatabaseV4XML.ElemLastModTime, ignoreCase = true) -> tl?.lastModificationTime = readPwTime(xpp) name.equals(PwDatabaseV4XML.ElemLastModTime, ignoreCase = true) -> tl?.lastModificationTime = readPwTime(xpp)

View File

@@ -653,7 +653,7 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeList(name: String?, it: NodeV4Interface) { private fun writeList(name: String?, it: PwNodeV4Interface) {
assert(name != null) assert(name != null)
xml.startTag(null, name) xml.startTag(null, name)