From 5446efca4a765ecc7bd804409c1c3d68103b97a0 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Sat, 9 Jan 2021 11:09:31 +0100 Subject: [PATCH] Output header refactor --- .../file/output/DatabaseHeaderOutput.kt | 25 ------ .../file/output/DatabaseHeaderOutputKDBX.kt | 5 +- .../output/DatabaseInnerHeaderOutputKDBX.kt | 76 ------------------- .../database/file/output/DatabaseOutput.kt | 2 +- .../database/file/output/DatabaseOutputKDB.kt | 4 +- .../file/output/DatabaseOutputKDBX.kt | 59 ++++++++++++-- 6 files changed, 58 insertions(+), 113 deletions(-) delete mode 100644 app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutput.kt delete mode 100644 app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseInnerHeaderOutputKDBX.kt diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutput.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutput.kt deleted file mode 100644 index 4cb13458d..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutput.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2019 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 . - * - */ -package com.kunzisoft.keepass.database.file.output - -open class DatabaseHeaderOutput { - var hashOfHeader: ByteArray? = null - protected set -} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutputKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutputKDBX.kt index bd18d8207..de76b662d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutputKDBX.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutputKDBX.kt @@ -40,13 +40,16 @@ import javax.crypto.spec.SecretKeySpec class DatabaseHeaderOutputKDBX @Throws(DatabaseOutputException::class) constructor(private val databaseKDBX: DatabaseKDBX, private val header: DatabaseHeaderKDBX, - outputStream: OutputStream) : DatabaseHeaderOutput() { + outputStream: OutputStream) { private val los: LittleEndianDataOutputStream private val mos: MacOutputStream private val dos: DigestOutputStream lateinit var headerHmac: ByteArray + var hashOfHeader: ByteArray? = null + private set + init { val md: MessageDigest diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseInnerHeaderOutputKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseInnerHeaderOutputKDBX.kt deleted file mode 100644 index fb3666e90..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseInnerHeaderOutputKDBX.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2019 Jeremy Jamet / Kunzisoft. - * - * This file is part of KeePassDX. - * - * KeePassDroid 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. - * - * KeePassDroid 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 KeePassDroid. If not, see . - * - */ -package com.kunzisoft.keepass.database.file.output - -import com.kunzisoft.keepass.database.element.database.DatabaseKDBX -import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BUFFER_SIZE_BYTES -import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX -import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream -import com.kunzisoft.keepass.stream.readBytes -import com.kunzisoft.keepass.utils.UnsignedInt -import java.io.IOException -import java.io.OutputStream -import kotlin.experimental.or - -class DatabaseInnerHeaderOutputKDBX(private val database: DatabaseKDBX, - private val header: DatabaseHeaderKDBX, - outputStream: OutputStream) { - - private val dataOutputStream: LittleEndianDataOutputStream = LittleEndianDataOutputStream(outputStream) - - @Throws(IOException::class) - fun output() { - dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID) - dataOutputStream.writeInt(4) - if (header.innerRandomStream == null) - throw IOException("Can't write innerRandomStream") - dataOutputStream.writeUInt(header.innerRandomStream!!.id) - - val streamKeySize = header.innerRandomStreamKey.size - dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey) - dataOutputStream.writeInt(streamKeySize) - dataOutputStream.write(header.innerRandomStreamKey) - - database.binaryPool.doForEachOrderedBinary { _, keyBinary -> - val protectedBinary = keyBinary.binary - // Force decompression to add binary in header - protectedBinary.decompress() - // Write type binary - dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary) - // Write size - dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(protectedBinary.length() + 1)) - // Write protected flag - var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None - if (protectedBinary.isProtected) { - flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected - } - dataOutputStream.writeByte(flag) - - protectedBinary.getInputDataStream().use { inputStream -> - inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer -> - dataOutputStream.write(buffer) - } - } - } - - dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader) - dataOutputStream.writeInt(0) - } -} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutput.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutput.kt index 4c0951393..4d456e538 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutput.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutput.kt @@ -26,7 +26,7 @@ import java.io.OutputStream import java.security.NoSuchAlgorithmException import java.security.SecureRandom -abstract class DatabaseOutput
protected constructor(protected var mOS: OutputStream) { +abstract class DatabaseOutput
protected constructor(protected var mOutputStream: OutputStream) { @Throws(DatabaseOutputException::class) protected open fun setIVs(header: Header): SecureRandom { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDB.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDB.kt index f71345bf8..417865c21 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDB.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDB.kt @@ -63,7 +63,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB, // and remove any orphaned nodes that are no longer part of the tree hierarchy sortGroupsForOutput() - val header = outputHeader(mOS) + val header = outputHeader(mOutputStream) val finalKey = getFinalKey(header) @@ -85,7 +85,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB, cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(finalKey, "AES"), IvParameterSpec(header.encryptionIV)) - val cos = CipherOutputStream(mOS, cipher) + val cos = CipherOutputStream(mOutputStream, cipher) val bos = BufferedOutputStream(cos) outputPlanGroupAndEntries(bos) bos.flush() diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt index 372623f2c..bfd88cd54 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt @@ -46,6 +46,7 @@ import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX import com.kunzisoft.keepass.database.file.DatabaseKDBXXML import com.kunzisoft.keepass.database.file.DateKDBXUtil import com.kunzisoft.keepass.stream.* +import com.kunzisoft.keepass.utils.UnsignedInt import org.bouncycastle.crypto.StreamCipher import org.joda.time.DateTime import org.xmlpull.v1.XmlSerializer @@ -57,6 +58,7 @@ import java.util.* import java.util.zip.GZIPOutputStream import javax.crypto.Cipher import javax.crypto.CipherOutputStream +import kotlin.experimental.or class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX, @@ -80,20 +82,19 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX, throw DatabaseOutputException("No such cipher", e) } - header = outputHeader(mOS) + header = outputHeader(mOutputStream) val osPlain: OutputStream osPlain = if (header!!.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) { - val cos = attachStreamEncryptor(header!!, mOS) + val cos = attachStreamEncryptor(header!!, mOutputStream) cos.write(header!!.streamStartBytes) HashedBlockOutputStream(cos) } else { - mOS.write(hashOfHeader!!) - mOS.write(headerHmac!!) + mOutputStream.write(hashOfHeader!!) + mOutputStream.write(headerHmac!!) - - attachStreamEncryptor(header!!, HmacBlockOutputStream(mOS, mDatabaseKDBX.hmacKey!!)) + attachStreamEncryptor(header!!, HmacBlockOutputStream(mOutputStream, mDatabaseKDBX.hmacKey!!)) } val osXml: OutputStream @@ -104,8 +105,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX, } if (header!!.version.toKotlinLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) { - val ihOut = DatabaseInnerHeaderOutputKDBX(mDatabaseKDBX, header!!, osXml) - ihOut.output() + outputInnerHeader(mDatabaseKDBX, header!!, osXml) } outputDatabase(osXml) @@ -121,6 +121,49 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX, } } + @Throws(IOException::class) + private fun outputInnerHeader(database: DatabaseKDBX, + header: DatabaseHeaderKDBX, + outputStream: OutputStream) { + val dataOutputStream = LittleEndianDataOutputStream(outputStream) + + dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID) + dataOutputStream.writeInt(4) + if (header.innerRandomStream == null) + throw IOException("Can't write innerRandomStream") + dataOutputStream.writeUInt(header.innerRandomStream!!.id) + + val streamKeySize = header.innerRandomStreamKey.size + dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey) + dataOutputStream.writeInt(streamKeySize) + dataOutputStream.write(header.innerRandomStreamKey) + + database.binaryPool.doForEachOrderedBinary { _, keyBinary -> + val protectedBinary = keyBinary.binary + // Force decompression to add binary in header + protectedBinary.decompress() + // Write type binary + dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary) + // Write size + dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(protectedBinary.length() + 1)) + // Write protected flag + var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None + if (protectedBinary.isProtected) { + flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected + } + dataOutputStream.writeByte(flag) + + protectedBinary.getInputDataStream().use { inputStream -> + inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer -> + dataOutputStream.write(buffer) + } + } + } + + dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader) + dataOutputStream.writeInt(0) + } + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) private fun outputDatabase(outputStream: OutputStream) {