From 5446efca4a765ecc7bd804409c1c3d68103b97a0 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Sat, 9 Jan 2021 11:09:31 +0100 Subject: [PATCH 1/4] 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) { From 6b6f03b14322377bb8168e0c4c7e1cd716793786 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Sat, 9 Jan 2021 11:20:41 +0100 Subject: [PATCH 2/4] Remove small warning --- .../kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt index c13ba5884..16069a060 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt @@ -452,8 +452,6 @@ class DatabaseInputKDBX(cacheDirectory: File) val strData = readString(xpp) if (strData.isNotEmpty()) { customIconData = Base64.decode(strData, BASE_64_FLAG) - } else { - assert(false) } } else { readUnknown(xpp) From 45a847fa3ed3d62907da15c0d42d748572c31c7e Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Sat, 9 Jan 2021 11:26:19 +0100 Subject: [PATCH 3/4] Remove unused code --- .../java/com/kunzisoft/keepass/database/element/Database.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt index d0da8c171..c4da31aac 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt @@ -384,10 +384,6 @@ class Database { // Check if the file is writable this.isReadOnly = readOnly - if (uri.scheme == "file") { - val file = File(uri.path!!) - isReadOnly = !file.canWrite() - } // Pass KeyFile Uri as InputStreams var keyFileInputStream: InputStream? = null From 8e3ddd64d2c7c89333824fe3776713d6f2887b8d Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Sat, 9 Jan 2021 12:17:43 +0100 Subject: [PATCH 4/4] Better exception catching #794 --- .../keepass/database/element/Database.kt | 52 +++++++++++-------- .../database/exception/DatabaseException.kt | 1 - 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt index c4da31aac..cb4c3c977 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt @@ -392,31 +392,36 @@ class Database { keyfile?.let { keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyfile) } - } catch (e: Exception) { + + // Read database stream for the first time + readDatabaseStream(contentResolver, uri, + { databaseInputStream -> + DatabaseInputKDB(cacheDirectory) + .openDatabase(databaseInputStream, + password, + keyFileInputStream, + progressTaskUpdater, + fixDuplicateUUID) + }, + { databaseInputStream -> + DatabaseInputKDBX(cacheDirectory) + .openDatabase(databaseInputStream, + password, + keyFileInputStream, + progressTaskUpdater, + fixDuplicateUUID) + } + ) + } catch (e: FileNotFoundException) { + Log.e(TAG, "Unable to load keyfile", e) throw FileNotFoundDatabaseException() + } catch (e: LoadDatabaseException) { + throw e + } catch (e: Exception) { + throw LoadDatabaseException(e) } finally { keyFileInputStream?.close() } - - // Read database stream for the first time - readDatabaseStream(contentResolver, uri, - { databaseInputStream -> - DatabaseInputKDB(cacheDirectory) - .openDatabase(databaseInputStream, - password, - keyFileInputStream, - progressTaskUpdater, - fixDuplicateUUID) - }, - { databaseInputStream -> - DatabaseInputKDBX(cacheDirectory) - .openDatabase(databaseInputStream, - password, - keyFileInputStream, - progressTaskUpdater, - fixDuplicateUUID) - } - ) } @Throws(LoadDatabaseException::class) @@ -440,7 +445,10 @@ class Database { progressTaskUpdater) } ) - } ?: throw IODatabaseException() + } ?: run { + Log.e(TAG, "Database URI is null, database cannot be reloaded") + throw IODatabaseException() + } } fun isGroupSearchable(group: Group, omitBackup: Boolean): Boolean { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/exception/DatabaseException.kt b/app/src/main/java/com/kunzisoft/keepass/database/exception/DatabaseException.kt index 594154606..2b5fd59e4 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/exception/DatabaseException.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/exception/DatabaseException.kt @@ -67,7 +67,6 @@ class FileNotFoundDatabaseException : LoadDatabaseException { class InvalidAlgorithmDatabaseException : LoadDatabaseException { @StringRes override var errorId: Int = R.string.invalid_algorithm - constructor() : super() constructor(exception: Throwable) : super(exception) }