Better KDBX version implementation

This commit is contained in:
J-Jamet
2021-03-24 21:00:41 +01:00
parent db467889b0
commit 9fae343668
7 changed files with 53 additions and 42 deletions

View File

@@ -43,6 +43,7 @@ import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDBX
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB
@@ -225,7 +226,7 @@ class Database {
// Default compression not necessary if stored in header
mDatabaseKDBX?.let {
return it.compressionAlgorithm == CompressionAlgorithm.GZip
&& it.kdbxVersion.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()
&& it.kdbxVersion.isBefore(FILE_VERSION_32_4)
}
return false
}

View File

@@ -187,7 +187,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
CompressionAlgorithm.GZip -> {
// Only in databaseV3.1, in databaseV4 the header is zipped during the save
if (kdbxVersion.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong()) {
if (kdbxVersion.isBefore(FILE_VERSION_32_4)) {
compressAllBinaries()
}
}
@@ -195,9 +195,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
CompressionAlgorithm.GZip -> {
// In databaseV4 the header is zipped during the save, so not necessary here
if (kdbxVersion.toKotlinLong() >= FILE_VERSION_32_4.toKotlinLong()) {
decompressAllBinaries()
} else {
if (kdbxVersion.isBefore(FILE_VERSION_32_4)) {
when (newCompression) {
CompressionAlgorithm.None -> {
decompressAllBinaries()
@@ -205,6 +203,8 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
CompressionAlgorithm.GZip -> {
}
}
} else {
decompressAllBinaries()
}
}
}

View File

@@ -168,7 +168,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
private fun readHeaderField(dis: InputStream): Boolean {
val fieldID = dis.read().toByte()
val fieldSize: Int = if (version.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong()) {
val fieldSize: Int = if (version.isBefore(FILE_VERSION_32_4)) {
dis.readBytes2ToUShort()
} else {
dis.readBytes4ToUInt().toKotlinInt()
@@ -195,20 +195,20 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
PwDbHeaderV4Fields.MasterSeed -> masterSeed = fieldData
PwDbHeaderV4Fields.TransformSeed -> if (version.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong())
PwDbHeaderV4Fields.TransformSeed -> if (version.isBefore(FILE_VERSION_32_4))
transformSeed = fieldData
PwDbHeaderV4Fields.TransformRounds -> if (version.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong())
PwDbHeaderV4Fields.TransformRounds -> if (version.isBefore(FILE_VERSION_32_4))
setTransformRound(fieldData)
PwDbHeaderV4Fields.EncryptionIV -> encryptionIV = fieldData
PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong())
PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version.isBefore(FILE_VERSION_32_4))
innerRandomStreamKey = fieldData
PwDbHeaderV4Fields.StreamStartBytes -> streamStartBytes = fieldData
PwDbHeaderV4Fields.InnerRandomStreamID -> if (version.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong())
PwDbHeaderV4Fields.InnerRandomStreamID -> if (version.isBefore(FILE_VERSION_32_4))
setRandomStreamID(fieldData)
PwDbHeaderV4Fields.KdfParameters -> databaseV4.kdfParameters = KdfParameters.deserialize(fieldData)
@@ -287,6 +287,10 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
FILE_VERSION_32_4.toKotlinInt() and FILE_VERSION_CRITICAL_MASK.toKotlinInt()
}
fun isVersionBefore(version: UnsignedInt) : Boolean {
return version.isBefore(version)
}
companion object {
val DBSIG_PRE2 = UnsignedInt(-0x4ab4049a)

View File

@@ -43,6 +43,7 @@ import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.stream.HashedBlockInputStream
@@ -158,7 +159,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
}
val plainInputStream: InputStream
if (mDatabase.kdbxVersion.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
if (mDatabase.kdbxVersion.isBefore(FILE_VERSION_32_4)) {
val dataDecrypted = CipherInputStream(databaseInputStream, cipher)
val storedStartBytes: ByteArray?
@@ -207,7 +208,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
else -> plainInputStream
}
if (mDatabase.kdbxVersion.toKotlinLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
if (!mDatabase.kdbxVersion.isBefore(FILE_VERSION_32_4)) {
readInnerHeader(inputStreamXml, header)
}
@@ -842,7 +843,13 @@ class DatabaseInputKDBX(cacheDirectory: File,
val sDate = readString(xpp)
var utcDate: Date? = null
if (mDatabase.kdbxVersion.toKotlinLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
if (mDatabase.kdbxVersion.isBefore(FILE_VERSION_32_4)) {
try {
utcDate = DatabaseKDBXXML.DateFormatter.parse(sDate)
} catch (e: ParseException) {
// Catch with null test below
}
} else {
var buf = Base64.decode(sDate, BASE_64_FLAG)
if (buf.size != 8) {
val buf8 = ByteArray(8)
@@ -852,14 +859,6 @@ class DatabaseInputKDBX(cacheDirectory: File,
val seconds = bytes64ToLong(buf)
utcDate = DateKDBXUtil.convertKDBX4Time(seconds)
} else {
try {
utcDate = DatabaseKDBXXML.DateFormatter.parse(sDate)
} catch (e: ParseException) {
// Catch with null test below
}
}
return utcDate ?: Date(0L)

View File

@@ -30,6 +30,7 @@ import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.stream.MacOutputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
@@ -77,7 +78,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CompressionFlags, uIntTo4Bytes(DatabaseHeaderKDBX.getFlagFromCompression(databaseKDBX.compressionAlgorithm)))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.MasterSeed, header.masterSeed)
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
if (header.isVersionBefore(FILE_VERSION_32_4)) {
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformSeed, header.transformSeed)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformRounds, longTo8Bytes(databaseKDBX.numberKeyEncryptionRounds))
} else {
@@ -88,7 +89,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.EncryptionIV, header.encryptionIV)
}
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
if (header.isVersionBefore(FILE_VERSION_32_4)) {
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomStreamID, uIntTo4Bytes(header.innerRandomStream!!.id))
@@ -122,7 +123,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
@Throws(IOException::class)
private fun writeHeaderFieldSize(size: Int) {
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
if (header.isVersionBefore(FILE_VERSION_32_4)) {
mos.write2BytesUShort(size)
} else {
mos.write4BytesUInt(UnsignedInt(size))

View File

@@ -42,6 +42,7 @@ import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.stream.HashedBlockOutputStream
@@ -65,10 +66,10 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
private var randomStream: StreamCipher? = null
private lateinit var xml: XmlSerializer
private var header: DatabaseHeaderKDBX? = null
private var hashOfHeader: ByteArray? = null
private var headerHmac: ByteArray? = null
private var engine: CipherEngine? = null
private var isVersionBelow4 = true
@Throws(DatabaseOutputException::class)
override fun output() {
@@ -80,33 +81,34 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
throw DatabaseOutputException("No such cipher", e)
}
header = outputHeader(mOutputStream)
val header = outputHeader(mOutputStream)
isVersionBelow4 = header.isVersionBefore(FILE_VERSION_32_4)
val osPlain: OutputStream = if (header!!.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
val cos = attachStreamEncryptor(header!!, mOutputStream)
cos.write(header!!.streamStartBytes)
val plainOutputStream: OutputStream = if (isVersionBelow4) {
val cos = attachStreamEncryptor(header, mOutputStream)
cos.write(header.streamStartBytes)
HashedBlockOutputStream(cos)
} else {
mOutputStream.write(hashOfHeader!!)
mOutputStream.write(headerHmac!!)
attachStreamEncryptor(header!!, HmacBlockOutputStream(mOutputStream, mDatabaseKDBX.hmacKey!!))
attachStreamEncryptor(header, HmacBlockOutputStream(mOutputStream, mDatabaseKDBX.hmacKey!!))
}
val osXml: OutputStream
val xmlOutpuStream: OutputStream
try {
osXml = when(mDatabaseKDBX.compressionAlgorithm) {
CompressionAlgorithm.GZip -> GZIPOutputStream(osPlain)
else -> osPlain
xmlOutpuStream = when(mDatabaseKDBX.compressionAlgorithm) {
CompressionAlgorithm.GZip -> GZIPOutputStream(plainOutputStream)
else -> plainOutputStream
}
if (header!!.version.toKotlinLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
outputInnerHeader(mDatabaseKDBX, header!!, osXml)
if (!isVersionBelow4) {
outputInnerHeader(mDatabaseKDBX, header, xmlOutpuStream)
}
outputDatabase(osXml)
osXml.close()
outputDatabase(xmlOutpuStream)
xmlOutpuStream.close()
} catch (e: IllegalArgumentException) {
throw DatabaseOutputException(e)
} catch (e: IllegalStateException) {
@@ -266,7 +268,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
writeUuid(DatabaseKDBXXML.ElemLastTopVisibleGroup, mDatabaseKDBX.lastTopVisibleGroupUUID)
// Seem to work properly if always in meta
if (header!!.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong())
if (isVersionBelow4)
writeMetaBinaries()
writeCustomData(mDatabaseKDBX.customData)
@@ -308,7 +310,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
Log.e(TAG, "Unable to retrieve header", unknownKDF)
}
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
if (header.isVersionBefore(FILE_VERSION_32_4)) {
header.innerRandomStream = CrsAlgorithm.Salsa20
header.innerRandomStreamKey = ByteArray(32)
} else {
@@ -323,7 +325,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
throw DatabaseOutputException(e)
}
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
if (header.isVersionBefore(FILE_VERSION_32_4)) {
random.nextBytes(header.streamStartBytes)
}
@@ -422,7 +424,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeObject(name: String, value: Date) {
if (header!!.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
if (isVersionBelow4) {
writeObject(name, DatabaseKDBXXML.DateFormatter.format(value))
} else {
val dt = DateTime(value)

View File

@@ -44,6 +44,10 @@ class UnsignedInt(private var unsignedValue: Int) {
return (unsignedValue and 0xFF).toByte()
}
fun isBefore(value: UnsignedInt): Boolean {
return toKotlinLong() < value.toKotlinLong()
}
override fun toString():String {
return toKotlinLong().toString()
}