Merge branch 'feature/OOM' into develop

This commit is contained in:
J-Jamet
2019-11-27 17:34:44 +01:00
33 changed files with 679 additions and 496 deletions

View File

@@ -20,6 +20,7 @@
android:allowBackup="true" android:allowBackup="true"
android:fullBackupContent="@xml/backup" android:fullBackupContent="@xml/backup"
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent" android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
android:largeHeap="true"
android:theme="@style/KeepassDXStyle.Night"> android:theme="@style/KeepassDXStyle.Night">
<!-- TODO backup API Key --> <!-- TODO backup API Key -->
<meta-data <meta-data

View File

@@ -243,9 +243,11 @@ class GroupActivity : LockingActivity(),
} }
if (!result.isSuccess) { if (!result.isSuccess) {
result.exception?.errorId?.let { errorId ->
coordinatorLayout?.let { coordinatorLayout -> coordinatorLayout?.let { coordinatorLayout ->
result.exception?.errorId?.let { errorId ->
Snackbar.make(coordinatorLayout, errorId, Snackbar.LENGTH_LONG).asError().show() Snackbar.make(coordinatorLayout, errorId, Snackbar.LENGTH_LONG).asError().show()
} ?: result.message?.let { message ->
Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_LONG).asError().show()
} }
} }
} }

View File

@@ -58,7 +58,7 @@ import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager
import com.kunzisoft.keepass.database.action.ProgressDialogThread import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.LoadDatabaseDuplicateUuidException import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.education.PasswordActivityEducation import com.kunzisoft.keepass.education.PasswordActivityEducation
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.CIPHER_ENTITY_KEY import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.CIPHER_ENTITY_KEY
@@ -189,7 +189,7 @@ class PasswordActivity : StylishActivity() {
resultError = resultException.getLocalizedMessage(resources) resultError = resultException.getLocalizedMessage(resources)
// Relaunch loading if we need to fix UUID // Relaunch loading if we need to fix UUID
if (resultException is LoadDatabaseDuplicateUuidException) { if (resultException is DuplicateUuidDatabaseException) {
showLoadDatabaseDuplicateUuidMessage { showLoadDatabaseDuplicateUuidMessage {
var databaseUri: Uri? = null var databaseUri: Uri? = null

View File

@@ -58,7 +58,7 @@ open class AssignPasswordInDatabaseRunnable (
database.retrieveMasterKey(mMasterPassword, uriInputStream) database.retrieveMasterKey(mMasterPassword, uriInputStream)
} catch (e: Exception) { } catch (e: Exception) {
erase(mBackupKey) erase(mBackupKey)
setError(e.message) setError(e)
} }
super.onStartRun() super.onStartRun()

View File

@@ -46,7 +46,7 @@ class CreateDatabaseRunnable(context: Context,
} }
} catch (e: Exception) { } catch (e: Exception) {
mDatabase.closeAndClear() mDatabase.closeAndClear()
setError(e.message) setError(e)
} }
super.onStartRun() super.onStartRun()

View File

@@ -25,7 +25,7 @@ import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.LoadDatabaseDuplicateUuidException import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.LoadDatabaseException import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.notifications.DatabaseOpenNotificationService import com.kunzisoft.keepass.notifications.DatabaseOpenNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
@@ -62,7 +62,7 @@ class LoadDatabaseRunnable(private val context: Context,
mFixDuplicateUUID, mFixDuplicateUUID,
progressTaskUpdater) progressTaskUpdater)
} }
catch (e: LoadDatabaseDuplicateUuidException) { catch (e: DuplicateUuidDatabaseException) {
mDuplicateUuidAction?.invoke(result) mDuplicateUuidAction?.invoke(result)
setError(e) setError(e)
} }

View File

@@ -21,9 +21,8 @@ package com.kunzisoft.keepass.database.action
import android.content.Context import android.content.Context
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.exception.DatabaseException
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import java.io.IOException
open class SaveDatabaseRunnable(protected var context: Context, open class SaveDatabaseRunnable(protected var context: Context,
protected var database: Database, protected var database: Database,
@@ -38,10 +37,8 @@ open class SaveDatabaseRunnable(protected var context: Context,
if (saveDatabase && result.isSuccess) { if (saveDatabase && result.isSuccess) {
try { try {
database.saveData(context.contentResolver) database.saveData(context.contentResolver)
} catch (e: IOException) { } catch (e: DatabaseException) {
setError(e.message) setError(e)
} catch (e: DatabaseOutputException) {
setError(e.message)
} }
} }
} }

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.PwCompressionAlgorithm
class UpdateCompressionBinariesDatabaseRunnable (
context: Context,
database: Database,
private val oldCompressionAlgorithm: PwCompressionAlgorithm,
private val newCompressionAlgorithm: PwCompressionAlgorithm,
saveDatabase: Boolean)
: SaveDatabaseRunnable(context, database, saveDatabase) {
override fun onStartRun() {
// Set new compression
if (database.allowDataCompression) {
try {
database.apply {
updateDataBinaryCompression(oldCompressionAlgorithm, newCompressionAlgorithm)
compressionAlgorithm = newCompressionAlgorithm
}
} catch (e: Exception) {
setError(e)
}
}
super.onStartRun()
}
override fun onFinishRun() {
super.onFinishRun()
if (database.allowDataCompression) {
if (!result.isSuccess) {
try {
database.apply {
compressionAlgorithm = oldCompressionAlgorithm
updateDataBinaryCompression(newCompressionAlgorithm, oldCompressionAlgorithm)
}
} catch (e: Exception) {
setError(e)
}
}
}
}
}

View File

@@ -22,8 +22,8 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.exception.CopyDatabaseEntryException import com.kunzisoft.keepass.database.exception.CopyEntryDatabaseException
import com.kunzisoft.keepass.database.exception.CopyDatabaseGroupException import com.kunzisoft.keepass.database.exception.CopyGroupDatabaseException
class CopyNodesRunnable constructor( class CopyNodesRunnable constructor(
context: Context, context: Context,
@@ -42,7 +42,7 @@ class CopyNodesRunnable constructor(
when (currentNode.type) { when (currentNode.type) {
Type.GROUP -> { Type.GROUP -> {
Log.e(TAG, "Copy not allowed for group")// Only finish thread Log.e(TAG, "Copy not allowed for group")// Only finish thread
setError(CopyDatabaseGroupException()) setError(CopyGroupDatabaseException())
break@foreachNode break@foreachNode
} }
Type.ENTRY -> { Type.ENTRY -> {
@@ -57,12 +57,12 @@ class CopyNodesRunnable constructor(
mEntriesCopied.add(entryCopied) mEntriesCopied.add(entryCopied)
} else { } else {
Log.e(TAG, "Unable to create a copy of the entry") Log.e(TAG, "Unable to create a copy of the entry")
setError(CopyDatabaseEntryException()) setError(CopyEntryDatabaseException())
break@foreachNode break@foreachNode
} }
} else { } else {
// Only finish thread // Only finish thread
setError(CopyDatabaseEntryException()) setError(CopyEntryDatabaseException())
break@foreachNode break@foreachNode
} }
} }

View File

@@ -22,9 +22,8 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.exception.LoadDatabaseException import com.kunzisoft.keepass.database.exception.EntryDatabaseException
import com.kunzisoft.keepass.database.exception.MoveDatabaseEntryException import com.kunzisoft.keepass.database.exception.MoveGroupDatabaseException
import com.kunzisoft.keepass.database.exception.MoveDatabaseGroupException
class MoveNodesRunnable constructor( class MoveNodesRunnable constructor(
context: Context, context: Context,
@@ -53,7 +52,7 @@ class MoveNodesRunnable constructor(
database.moveGroupTo(groupToMove, mNewParent) database.moveGroupTo(groupToMove, mNewParent)
} else { } else {
// Only finish thread // Only finish thread
setError(MoveDatabaseGroupException()) setError(MoveGroupDatabaseException())
break@foreachNode break@foreachNode
} }
} }
@@ -67,7 +66,7 @@ class MoveNodesRunnable constructor(
database.moveEntryTo(entryToMove, mNewParent) database.moveEntryTo(entryToMove, mNewParent)
} else { } else {
// Only finish thread // Only finish thread
setError(MoveDatabaseEntryException()) setError(EntryDatabaseException())
break@foreachNode break@foreachNode
} }
} }

View File

@@ -20,25 +20,27 @@
package com.kunzisoft.keepass.database.element package com.kunzisoft.keepass.database.element
import android.util.SparseArray import android.util.SparseArray
import com.kunzisoft.keepass.database.element.security.ProtectedBinary import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import java.io.IOException
class BinaryPool { class BinaryPool {
private val pool = SparseArray<ProtectedBinary>() private val pool = SparseArray<BinaryAttachment>()
operator fun get(key: Int): ProtectedBinary? { operator fun get(key: Int): BinaryAttachment? {
return pool[key] return pool[key]
} }
fun put(key: Int, value: ProtectedBinary) { fun put(key: Int, value: BinaryAttachment) {
pool.put(key, value) pool.put(key, value)
} }
fun doForEachBinary(action: (key: Int, binary: ProtectedBinary) -> Unit) { fun doForEachBinary(action: (key: Int, binary: BinaryAttachment) -> Unit) {
for (i in 0 until pool.size()) { for (i in 0 until pool.size()) {
action.invoke(i, pool.get(pool.keyAt(i))) action.invoke(i, pool.get(pool.keyAt(i)))
} }
} }
@Throws(IOException::class)
fun clear() { fun clear() {
doForEachBinary { _, binary -> doForEachBinary { _, binary ->
binary.clear() binary.clear()
@@ -46,9 +48,9 @@ class BinaryPool {
pool.clear() pool.clear()
} }
fun add(protectedBinary: ProtectedBinary) { fun add(fileBinary: BinaryAttachment) {
if (findKey(protectedBinary) == null) { if (findKey(fileBinary) == null) {
pool.put(findUnusedKey(), protectedBinary) pool.put(findUnusedKey(), fileBinary)
} }
} }
@@ -59,7 +61,7 @@ class BinaryPool {
return unusedKey return unusedKey
} }
fun findKey(pb: ProtectedBinary): Int? { fun findKey(pb: BinaryAttachment): Int? {
for (i in 0 until pool.size()) { for (i in 0 until pool.size()) {
if (pool.get(pool.keyAt(i)) == pb) return i if (pool.get(pool.keyAt(i)) == pb) return i
} }

View File

@@ -139,6 +139,11 @@ class Database {
} }
} }
fun updateDataBinaryCompression(oldCompression: PwCompressionAlgorithm,
newCompression: PwCompressionAlgorithm) {
pwDatabaseV4?.changeBinaryCompression(oldCompression, newCompression)
}
val allowNoMasterKey: Boolean val allowNoMasterKey: Boolean
get() = pwDatabaseV4 != null get() = pwDatabaseV4 != null
@@ -317,7 +322,7 @@ class Database {
inputStream = UriUtil.getUriInputStream(contentResolver, uri) inputStream = UriUtil.getUriInputStream(contentResolver, uri)
} catch (e: Exception) { } catch (e: Exception) {
Log.e("KPD", "Database::loadData", e) Log.e("KPD", "Database::loadData", e)
throw LoadDatabaseFileNotFoundException() throw FileNotFoundDatabaseException()
} }
// Pass KeyFile Uri as InputStreams // Pass KeyFile Uri as InputStreams
@@ -327,7 +332,7 @@ class Database {
keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyfile) keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyfile)
} catch (e: Exception) { } catch (e: Exception) {
Log.e("KPD", "Database::loadData", e) Log.e("KPD", "Database::loadData", e)
throw LoadDatabaseFileNotFoundException() throw FileNotFoundDatabaseException()
} }
} }
@@ -366,7 +371,7 @@ class Database {
progressTaskUpdater)) progressTaskUpdater))
// Header not recognized // Header not recognized
else -> throw LoadDatabaseSignatureException() else -> throw SignatureDatabaseException()
} }
this.mSearchHelper = SearchDbHelper(omitBackup) this.mSearchHelper = SearchDbHelper(omitBackup)
@@ -432,16 +437,20 @@ class Database {
return entry return entry
} }
@Throws(IOException::class, DatabaseOutputException::class) @Throws(DatabaseOutputException::class)
fun saveData(contentResolver: ContentResolver) { fun saveData(contentResolver: ContentResolver) {
try {
this.fileUri?.let { this.fileUri?.let {
saveData(contentResolver, it) saveData(contentResolver, it)
} }
} catch (e: Exception) {
Log.e(TAG, "Unable to save database", e)
throw DatabaseOutputException(e)
}
} }
@Throws(IOException::class, DatabaseOutputException::class) @Throws(IOException::class, DatabaseOutputException::class)
private fun saveData(contentResolver: ContentResolver, uri: Uri) { private fun saveData(contentResolver: ContentResolver, uri: Uri) {
val errorMessage = "Failed to store database."
if (uri.scheme == "file") { if (uri.scheme == "file") {
uri.path?.let { filename -> uri.path?.let { filename ->
@@ -454,8 +463,7 @@ class Database {
?: pwDatabaseV4?.let { PwDbV4Output(it, fileOutputStream) } ?: pwDatabaseV4?.let { PwDbV4Output(it, fileOutputStream) }
pmo?.output() pmo?.output()
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, errorMessage, e) throw IOException(e)
throw IOException(errorMessage, e)
} finally { } finally {
fileOutputStream?.close() fileOutputStream?.close()
} }
@@ -468,7 +476,7 @@ class Database {
} }
if (!tempFile.renameTo(File(filename))) { if (!tempFile.renameTo(File(filename))) {
throw IOException(errorMessage) throw IOException()
} }
} }
} else { } else {
@@ -480,8 +488,7 @@ class Database {
?: pwDatabaseV4?.let { PwDbV4Output(it, outputStream) } ?: pwDatabaseV4?.let { PwDbV4Output(it, outputStream) }
pmo?.output() pmo?.output()
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, errorMessage, e) throw IOException(e)
throw IOException(errorMessage, e)
} finally { } finally {
outputStream?.close() outputStream?.close()
} }

View File

@@ -20,17 +20,30 @@
package com.kunzisoft.keepass.database.element package com.kunzisoft.keepass.database.element
import android.content.res.Resources import android.content.res.Resources
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.ObjectNameResource import com.kunzisoft.keepass.utils.ObjectNameResource
import com.kunzisoft.keepass.utils.readEnum
import com.kunzisoft.keepass.utils.writeEnum
// Note: We can get away with using int's to store unsigned 32-bit ints // Note: We can get away with using int's to store unsigned 32-bit ints
// since we won't do arithmetic on these values (also unlikely to // since we won't do arithmetic on these values (also unlikely to
// reach negative ids). // reach negative ids).
enum class PwCompressionAlgorithm : ObjectNameResource { enum class PwCompressionAlgorithm : ObjectNameResource, Parcelable {
None, None,
GZip; GZip;
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeEnum(this)
}
override fun describeContents(): Int {
return 0
}
override fun getName(resources: Resources): String { override fun getName(resources: Resources): String {
return when (this) { return when (this) {
None -> resources.getString(R.string.compression_none) None -> resources.getString(R.string.compression_none)
@@ -38,4 +51,14 @@ enum class PwCompressionAlgorithm : ObjectNameResource {
} }
} }
companion object CREATOR : Parcelable.Creator<PwCompressionAlgorithm> {
override fun createFromParcel(parcel: Parcel): PwCompressionAlgorithm {
return parcel.readEnum<PwCompressionAlgorithm>() ?: None
}
override fun newArray(size: Int): Array<PwCompressionAlgorithm?> {
return arrayOfNulls(size)
}
}
} }

View File

@@ -20,8 +20,8 @@
package com.kunzisoft.keepass.database.element package com.kunzisoft.keepass.database.element
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.exception.LoadDatabaseDuplicateUuidException import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.LoadDatabaseKeyFileEmptyException import com.kunzisoft.keepass.database.exception.KeyFileEmptyDatabaseException
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import java.io.* import java.io.*
import java.security.MessageDigest import java.security.MessageDigest
@@ -134,7 +134,7 @@ abstract class PwDatabase<
} }
when (keyData.size.toLong()) { when (keyData.size.toLong()) {
0L -> throw LoadDatabaseKeyFileEmptyException() 0L -> throw KeyFileEmptyDatabaseException()
32L -> return keyData 32L -> return keyData
64L -> try { 64L -> try {
return hexStringToByteArray(String(keyData)) return hexStringToByteArray(String(keyData))
@@ -247,7 +247,7 @@ abstract class PwDatabase<
group.parent?.addChildGroup(group) group.parent?.addChildGroup(group)
this.groupIndexes[newGroupId] = group this.groupIndexes[newGroupId] = group
} else { } else {
throw LoadDatabaseDuplicateUuidException(Type.GROUP, groupId) throw DuplicateUuidDatabaseException(Type.GROUP, groupId)
} }
} else { } else {
this.groupIndexes[groupId] = group this.groupIndexes[groupId] = group
@@ -296,7 +296,7 @@ abstract class PwDatabase<
entry.parent?.addChildEntry(entry) entry.parent?.addChildEntry(entry)
this.entryIndexes[newEntryId] = entry this.entryIndexes[newEntryId] = entry
} else { } else {
throw LoadDatabaseDuplicateUuidException(Type.ENTRY, entryId) throw DuplicateUuidDatabaseException(Type.ENTRY, entryId)
} }
} else { } else {
this.entryIndexes[entryId] = entry this.entryIndexes[entryId] = entry

View File

@@ -31,6 +31,8 @@ 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.element.PwDatabaseV3.Companion.BACKUP_FOLDER_TITLE import com.kunzisoft.keepass.database.element.PwDatabaseV3.Companion.BACKUP_FOLDER_TITLE
import com.kunzisoft.keepass.database.exception.UnknownKDF import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.file.PwDbHeaderV4.Companion.FILE_VERSION_32_3
import com.kunzisoft.keepass.database.file.PwDbHeaderV4.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.utils.VariantDictionary import com.kunzisoft.keepass.utils.VariantDictionary
import org.w3c.dom.Node import org.w3c.dom.Node
import org.w3c.dom.Text import org.w3c.dom.Text
@@ -56,6 +58,7 @@ class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
private var numKeyEncRounds: Long = 0 private var numKeyEncRounds: Long = 0
var publicCustomData = VariantDictionary() var publicCustomData = VariantDictionary()
var kdbxVersion: Long = 0
var name = "" var name = ""
var nameChanged = PwDate() var nameChanged = PwDate()
// TODO change setting date // TODO change setting date
@@ -91,7 +94,7 @@ class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
val customIcons = ArrayList<PwIconCustom>() val customIcons = ArrayList<PwIconCustom>()
val customData = HashMap<String, String>() val customData = HashMap<String, String>()
var binPool = BinaryPool() var binaryPool = BinaryPool()
var localizedAppName = "KeePassDX" var localizedAppName = "KeePassDX"
@@ -116,7 +119,14 @@ class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
} }
override val version: String override val version: String
get() = "KeePass 2" get() {
val kdbxStringVersion = when(kdbxVersion) {
FILE_VERSION_32_3 -> "3.1"
FILE_VERSION_32_4 -> "4.0"
else -> "UNKNOWN"
}
return "KeePass 2 - KDBX$kdbxStringVersion"
}
override val kdfEngine: KdfEngine? override val kdfEngine: KdfEngine?
get() = try { get() = try {
@@ -151,6 +161,39 @@ class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
return list return list
} }
fun changeBinaryCompression(oldCompression: PwCompressionAlgorithm,
newCompression: PwCompressionAlgorithm) {
binaryPool.doForEachBinary { key, binary ->
try {
when (oldCompression) {
PwCompressionAlgorithm.None -> {
when (newCompression) {
PwCompressionAlgorithm.None -> {
}
PwCompressionAlgorithm.GZip -> {
// To compress, create a new binary with file
binary.compress()
}
}
}
PwCompressionAlgorithm.GZip -> {
when (newCompression) {
PwCompressionAlgorithm.None -> {
// To decompress, create a new binary with file
binary.decompress()
}
PwCompressionAlgorithm.GZip -> {
}
}
}
}
} catch (e: Exception) {
Log.e(TAG, "Unable to change compression for $key")
}
}
}
override val availableEncryptionAlgorithms: List<PwEncryptionAlgorithm> override val availableEncryptionAlgorithms: List<PwEncryptionAlgorithm>
get() { get() {
val list = ArrayList<PwEncryptionAlgorithm>() val list = ArrayList<PwEncryptionAlgorithm>()
@@ -491,8 +534,12 @@ class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
} }
override fun clearCache() { override fun clearCache() {
try {
super.clearCache() super.clearCache()
binPool.clear() binaryPool.clear()
} catch (e: Exception) {
Log.e(TAG, "Unable to clear cache", e)
}
} }
companion object { companion object {

View File

@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.database.element
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.security.ProtectedBinary import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.utils.ParcelableUtil import com.kunzisoft.keepass.utils.ParcelableUtil
import java.util.* import java.util.*
@@ -49,7 +49,7 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
var iconCustom = PwIconCustom.UNKNOWN_ICON var iconCustom = PwIconCustom.UNKNOWN_ICON
private var customData = HashMap<String, String>() private var customData = HashMap<String, String>()
var fields = HashMap<String, ProtectedString>() var fields = HashMap<String, ProtectedString>()
val binaries = HashMap<String, ProtectedBinary>() var binaries = HashMap<String, BinaryAttachment>()
var foregroundColor = "" var foregroundColor = ""
var backgroundColor = "" var backgroundColor = ""
var overrideURL = "" var overrideURL = ""
@@ -98,7 +98,7 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
locationChanged = parcel.readParcelable(PwDate::class.java.classLoader) ?: locationChanged locationChanged = parcel.readParcelable(PwDate::class.java.classLoader) ?: locationChanged
customData = ParcelableUtil.readStringParcelableMap(parcel) customData = ParcelableUtil.readStringParcelableMap(parcel)
fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java) fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java)
// TODO binaries = ParcelableUtil.readStringParcelableMap(parcel, ProtectedBinary.class); binaries = ParcelableUtil.readStringParcelableMap(parcel, BinaryAttachment::class.java)
foregroundColor = parcel.readString() ?: foregroundColor foregroundColor = parcel.readString() ?: foregroundColor
backgroundColor = parcel.readString() ?: backgroundColor backgroundColor = parcel.readString() ?: backgroundColor
overrideURL = parcel.readString() ?: overrideURL overrideURL = parcel.readString() ?: overrideURL
@@ -116,7 +116,7 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
dest.writeParcelable(locationChanged, flags) dest.writeParcelable(locationChanged, flags)
ParcelableUtil.writeStringParcelableMap(dest, customData) ParcelableUtil.writeStringParcelableMap(dest, customData)
ParcelableUtil.writeStringParcelableMap(dest, flags, fields) ParcelableUtil.writeStringParcelableMap(dest, flags, fields)
// TODO ParcelableUtil.writeStringParcelableMap(dest, flags, binaries); ParcelableUtil.writeStringParcelableMap(dest, flags, binaries)
dest.writeString(foregroundColor) dest.writeString(foregroundColor)
dest.writeString(backgroundColor) dest.writeString(backgroundColor)
dest.writeString(overrideURL) dest.writeString(overrideURL)
@@ -274,7 +274,7 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
fields[label] = value fields[label] = value
} }
fun putProtectedBinary(key: String, value: ProtectedBinary) { fun putProtectedBinary(key: String, value: BinaryAttachment) {
binaries[key] = value binaries[key] = value
} }

View File

@@ -0,0 +1,184 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.security
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.PwDatabaseV4.Companion.BUFFER_SIZE_BYTES
import com.kunzisoft.keepass.stream.ReadBytes
import com.kunzisoft.keepass.stream.readFromStream
import java.io.*
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
class BinaryAttachment : Parcelable {
var isCompressed: Boolean? = null
private set
var isProtected: Boolean = false
private set
private var dataFile: File? = null
fun length(): Long {
if (dataFile != null)
return dataFile!!.length()
return 0
}
/**
* Empty protected binary
*/
constructor() {
this.isCompressed = null
this.isProtected = false
this.dataFile = null
}
constructor(dataFile: File, enableProtection: Boolean = false, compressed: Boolean? = null) {
this.isCompressed = compressed
this.isProtected = enableProtection
this.dataFile = dataFile
}
private constructor(parcel: Parcel) {
val compressedByte = parcel.readByte().toInt()
isCompressed = if (compressedByte == 2) null else compressedByte != 0
isProtected = parcel.readByte().toInt() != 0
dataFile = File(parcel.readString())
}
@Throws(IOException::class)
fun getInputDataStream(): InputStream {
return when {
dataFile != null -> FileInputStream(dataFile!!)
else -> ByteArrayInputStream(ByteArray(0))
}
}
@Throws(IOException::class)
fun compress() {
if (dataFile != null) {
// To compress, create a new binary with file
if (isCompressed != true) {
val fileBinaryCompress = File(dataFile!!.parent, dataFile!!.name + "_temp")
val outputStream = GZIPOutputStream(FileOutputStream(fileBinaryCompress))
readFromStream(getInputDataStream(), BUFFER_SIZE_BYTES,
object : ReadBytes {
override fun read(buffer: ByteArray) {
outputStream.write(buffer)
}
})
outputStream.close()
// Remove unGzip file
if (dataFile!!.delete()) {
if (fileBinaryCompress.renameTo(dataFile)) {
// Harmonize with database compression
isCompressed = true
}
}
}
}
}
@Throws(IOException::class)
fun decompress() {
if (dataFile != null) {
if (isCompressed != false) {
val fileBinaryDecompress = File(dataFile!!.parent, dataFile!!.name + "_temp")
val outputStream = FileOutputStream(fileBinaryDecompress)
readFromStream(GZIPInputStream(getInputDataStream()), BUFFER_SIZE_BYTES,
object : ReadBytes {
override fun read(buffer: ByteArray) {
outputStream.write(buffer)
}
})
outputStream.close()
// Remove gzip file
if (dataFile!!.delete()) {
if (fileBinaryDecompress.renameTo(dataFile)) {
// Harmonize with database compression
isCompressed = false
}
}
}
}
}
@Throws(IOException::class)
fun clear() {
if (dataFile != null && !dataFile!!.delete())
throw IOException("Unable to delete temp file " + dataFile!!.absolutePath)
}
override fun equals(other: Any?): Boolean {
if (this === other)
return true
if (other == null || javaClass != other.javaClass)
return false
if (other !is BinaryAttachment)
return false
var sameData = false
if (dataFile != null && dataFile == other.dataFile)
sameData = true
return isCompressed == other.isCompressed
&& isProtected == other.isProtected
&& sameData
}
override fun hashCode(): Int {
var result = 0
result = 31 * result + if (isCompressed == null) 2 else if (isCompressed!!) 1 else 0
result = 31 * result + if (isProtected) 1 else 0
result = 31 * result + dataFile!!.hashCode()
return result
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeByte((if (isCompressed == null) 2 else if (isCompressed!!) 1 else 0).toByte())
dest.writeByte((if (isProtected) 1 else 0).toByte())
dest.writeString(dataFile?.absolutePath)
}
companion object {
private val TAG = BinaryAttachment::class.java.name
@JvmField
val CREATOR: Parcelable.Creator<BinaryAttachment> = object : Parcelable.Creator<BinaryAttachment> {
override fun createFromParcel(parcel: Parcel): BinaryAttachment {
return BinaryAttachment(parcel)
}
override fun newArray(size: Int): Array<BinaryAttachment?> {
return arrayOfNulls(size)
}
}
}
}

View File

@@ -1,153 +0,0 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.security
import android.os.Parcel
import android.os.Parcelable
import android.util.Log
import java.io.ByteArrayInputStream
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.io.InputStream
import java.util.Arrays
class ProtectedBinary : Parcelable {
var isProtected: Boolean = false
private set
private var data: ByteArray? = null
private var dataFile: File? = null
fun length(): Long {
if (data != null)
return data!!.size.toLong()
if (dataFile != null)
return dataFile!!.length()
return 0
}
/**
* Empty protected binary
*/
constructor() {
this.isProtected = false
this.data = null
this.dataFile = null
}
constructor(protectedBinary: ProtectedBinary) {
this.isProtected = protectedBinary.isProtected
this.data = protectedBinary.data
this.dataFile = protectedBinary.dataFile
}
constructor(enableProtection: Boolean, data: ByteArray?) {
this.isProtected = enableProtection
this.data = data
this.dataFile = null
}
constructor(enableProtection: Boolean, dataFile: File) {
this.isProtected = enableProtection
this.data = null
this.dataFile = dataFile
}
private constructor(parcel: Parcel) {
isProtected = parcel.readByte().toInt() != 0
data = ByteArray(parcel.readInt())
parcel.readByteArray(data)
dataFile = File(parcel.readString())
}
@Throws(IOException::class)
fun getData(): InputStream? {
return when {
data != null -> ByteArrayInputStream(data)
dataFile != null -> FileInputStream(dataFile!!)
else -> null
}
}
fun clear() {
data = null
if (dataFile != null && !dataFile!!.delete())
Log.e(TAG, "Unable to delete temp file " + dataFile!!.absolutePath)
}
override fun equals(other: Any?): Boolean {
if (this === other)
return true
if (other == null || javaClass != other.javaClass)
return false
if (other !is ProtectedBinary)
return false
var sameData = false
if (data != null && Arrays.equals(data, other.data))
sameData = true
else if (dataFile != null && dataFile == other.dataFile)
sameData = true
else if (data == null && other.data == null
&& dataFile == null && other.dataFile == null)
sameData = true
return isProtected == other.isProtected && sameData
}
override fun hashCode(): Int {
var result = 0
result = 31 * result + if (isProtected) 1 else 0
result = 31 * result + dataFile!!.hashCode()
result = 31 * result + Arrays.hashCode(data)
return result
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeByte((if (isProtected) 1 else 0).toByte())
dest.writeInt(data?.size ?: 0)
dest.writeByteArray(data)
dest.writeString(dataFile?.absolutePath)
}
companion object {
private val TAG = ProtectedBinary::class.java.name
@JvmField
val CREATOR: Parcelable.Creator<ProtectedBinary> = object : Parcelable.Creator<ProtectedBinary> {
override fun createFromParcel(parcel: Parcel): ProtectedBinary {
return ProtectedBinary(parcel)
}
override fun newArray(size: Int): Array<ProtectedBinary?> {
return arrayOfNulls(size)
}
}
}
}

View File

@@ -12,7 +12,8 @@ abstract class DatabaseException : Exception {
var parameters: (Array<out String>)? = null var parameters: (Array<out String>)? = null
constructor() : super() constructor() : super()
constructor(message: String) : super(message)
constructor(message: String, throwable: Throwable) : super(message, throwable)
constructor(throwable: Throwable) : super(throwable) constructor(throwable: Throwable) : super(throwable)
fun getLocalizedMessage(resources: Resources): String { fun getLocalizedMessage(resources: Resources): String {
@@ -26,33 +27,25 @@ open class LoadDatabaseException : DatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.error_load_database override var errorId: Int = R.string.error_load_database
constructor() : super() constructor() : super()
constructor(vararg params: String) : super() {
parameters = params
}
constructor(throwable: Throwable) : super(throwable) constructor(throwable: Throwable) : super(throwable)
} }
class LoadDatabaseArcFourException : LoadDatabaseException { class ArcFourDatabaseException : LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.error_arc4 override var errorId: Int = R.string.error_arc4
constructor() : super() constructor() : super()
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }
class LoadDatabaseFileNotFoundException : LoadDatabaseException { class FileNotFoundDatabaseException : LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.file_not_found_content override var errorId: Int = R.string.file_not_found_content
constructor() : super() constructor() : super()
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }
class LoadDatabaseInvalidAlgorithmException : LoadDatabaseException { class InvalidAlgorithmDatabaseException : LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.invalid_algorithm override var errorId: Int = R.string.invalid_algorithm
@@ -60,102 +53,97 @@ class LoadDatabaseInvalidAlgorithmException : LoadDatabaseException {
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }
class LoadDatabaseDuplicateUuidException: LoadDatabaseException { class DuplicateUuidDatabaseException: LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.invalid_db_same_uuid override var errorId: Int = R.string.invalid_db_same_uuid
constructor(type: Type, uuid: PwNodeId<*>) : super() { constructor(type: Type, uuid: PwNodeId<*>) : super() {
parameters = arrayOf(type.name, uuid.toString()) parameters = arrayOf(type.name, uuid.toString())
} }
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }
class LoadDatabaseIOException : LoadDatabaseException { class IODatabaseException : LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.error_load_database override var errorId: Int = R.string.error_load_database
constructor() : super() constructor() : super()
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }
class LoadDatabaseKDFMemoryException : LoadDatabaseException { class KDFMemoryDatabaseException : LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.error_load_database_KDF_memory override var errorId: Int = R.string.error_load_database_KDF_memory
constructor() : super() constructor() : super()
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }
class LoadDatabaseSignatureException : LoadDatabaseException { class SignatureDatabaseException : LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.invalid_db_sig override var errorId: Int = R.string.invalid_db_sig
constructor() : super() constructor() : super()
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }
class LoadDatabaseVersionException : LoadDatabaseException { class VersionDatabaseException : LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.unsupported_db_version override var errorId: Int = R.string.unsupported_db_version
constructor() : super() constructor() : super()
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }
class LoadDatabaseInvalidCredentialsException : LoadDatabaseException { class InvalidCredentialsDatabaseException : LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.invalid_credentials override var errorId: Int = R.string.invalid_credentials
constructor() : super() constructor() : super()
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }
class LoadDatabaseKeyFileEmptyException : LoadDatabaseException { class KeyFileEmptyDatabaseException : LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.keyfile_is_empty override var errorId: Int = R.string.keyfile_is_empty
constructor() : super() constructor() : super()
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }
class LoadDatabaseNoMemoryException: LoadDatabaseException { class NoMemoryDatabaseException: LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.error_out_of_memory override var errorId: Int = R.string.error_out_of_memory
constructor() : super() constructor() : super()
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }
class MoveDatabaseEntryException: LoadDatabaseException { class EntryDatabaseException: LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.error_move_entry_here override var errorId: Int = R.string.error_move_entry_here
constructor() : super() constructor() : super()
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }
class MoveDatabaseGroupException: LoadDatabaseException { class MoveGroupDatabaseException: LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.error_move_folder_in_itself override var errorId: Int = R.string.error_move_folder_in_itself
constructor() : super() constructor() : super()
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }
class CopyDatabaseEntryException: LoadDatabaseException { class CopyEntryDatabaseException: LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.error_copy_entry_here override var errorId: Int = R.string.error_copy_entry_here
constructor() : super() constructor() : super()
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }
class CopyDatabaseGroupException: LoadDatabaseException { class CopyGroupDatabaseException: LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.error_copy_group_here override var errorId: Int = R.string.error_copy_group_here
constructor() : super() constructor() : super()
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }
class DatabaseOutputException : Exception { // TODO Output Exception
open class DatabaseOutputException : DatabaseException {
@StringRes
override var errorId: Int = R.string.error_save_database
constructor(string: String) : super(string) constructor(string: String) : super(string)
constructor(string: String, e: Exception) : super(string, e) constructor(string: String, e: Exception) : super(string, e)
constructor(e: Exception) : super(e) constructor(e: Exception) : super(e)
} }

View File

@@ -25,7 +25,7 @@ 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.NodeHandler
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.exception.LoadDatabaseVersionException import com.kunzisoft.keepass.database.exception.VersionDatabaseException
import com.kunzisoft.keepass.stream.CopyInputStream import com.kunzisoft.keepass.stream.CopyInputStream
import com.kunzisoft.keepass.stream.HmacBlockStream import com.kunzisoft.keepass.stream.HmacBlockStream
import com.kunzisoft.keepass.stream.LEDataInputStream import com.kunzisoft.keepass.stream.LEDataInputStream
@@ -130,9 +130,9 @@ class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() {
/** Assumes the input stream is at the beginning of the .kdbx file /** Assumes the input stream is at the beginning of the .kdbx file
* @param inputStream * @param inputStream
* @throws IOException * @throws IOException
* @throws LoadDatabaseVersionException * @throws VersionDatabaseException
*/ */
@Throws(IOException::class, LoadDatabaseVersionException::class) @Throws(IOException::class, VersionDatabaseException::class)
fun loadFromFile(inputStream: InputStream): HeaderAndHash { fun loadFromFile(inputStream: InputStream): HeaderAndHash {
val messageDigest: MessageDigest val messageDigest: MessageDigest
try { try {
@@ -150,12 +150,12 @@ class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() {
val sig2 = littleEndianDataInputStream.readInt() val sig2 = littleEndianDataInputStream.readInt()
if (!matchesHeader(sig1, sig2)) { if (!matchesHeader(sig1, sig2)) {
throw LoadDatabaseVersionException() throw VersionDatabaseException()
} }
version = littleEndianDataInputStream.readUInt() // Erase previous value version = littleEndianDataInputStream.readUInt() // Erase previous value
if (!validVersion(version)) { if (!validVersion(version)) {
throw LoadDatabaseVersionException() throw VersionDatabaseException()
} }
var done = false var done = false

View File

@@ -93,11 +93,11 @@ class ImporterV3 : Importer<PwDatabaseV3>() {
hdr.loadFromFile(filebuf, 0) hdr.loadFromFile(filebuf, 0)
if (hdr.signature1 != PwDbHeader.PWM_DBSIG_1 || hdr.signature2 != PwDbHeaderV3.DBSIG_2) { if (hdr.signature1 != PwDbHeader.PWM_DBSIG_1 || hdr.signature2 != PwDbHeaderV3.DBSIG_2) {
throw LoadDatabaseSignatureException() throw SignatureDatabaseException()
} }
if (!hdr.matchesVersion()) { if (!hdr.matchesVersion()) {
throw LoadDatabaseVersionException() throw VersionDatabaseException()
} }
progressTaskUpdater?.updateMessage(R.string.retrieving_db_key) progressTaskUpdater?.updateMessage(R.string.retrieving_db_key)
@@ -108,7 +108,7 @@ class ImporterV3 : Importer<PwDatabaseV3>() {
when { when {
hdr.flags and PwDbHeaderV3.FLAG_RIJNDAEL != 0 -> mDatabaseToOpen.encryptionAlgorithm = PwEncryptionAlgorithm.AESRijndael hdr.flags and PwDbHeaderV3.FLAG_RIJNDAEL != 0 -> mDatabaseToOpen.encryptionAlgorithm = PwEncryptionAlgorithm.AESRijndael
hdr.flags and PwDbHeaderV3.FLAG_TWOFISH != 0 -> mDatabaseToOpen.encryptionAlgorithm = PwEncryptionAlgorithm.Twofish hdr.flags and PwDbHeaderV3.FLAG_TWOFISH != 0 -> mDatabaseToOpen.encryptionAlgorithm = PwEncryptionAlgorithm.Twofish
else -> throw LoadDatabaseInvalidAlgorithmException() else -> throw InvalidAlgorithmDatabaseException()
} }
mDatabaseToOpen.numberKeyEncryptionRounds = hdr.numKeyEncRounds.toLong() mDatabaseToOpen.numberKeyEncryptionRounds = hdr.numKeyEncRounds.toLong()
@@ -151,7 +151,7 @@ class ImporterV3 : Importer<PwDatabaseV3>() {
} catch (e1: IllegalBlockSizeException) { } catch (e1: IllegalBlockSizeException) {
throw IOException("Invalid block size") throw IOException("Invalid block size")
} catch (e1: BadPaddingException) { } catch (e1: BadPaddingException) {
throw LoadDatabaseInvalidCredentialsException() throw InvalidCredentialsDatabaseException()
} }
val md: MessageDigest val md: MessageDigest
@@ -170,7 +170,7 @@ class ImporterV3 : Importer<PwDatabaseV3>() {
if (!Arrays.equals(hash, hdr.contentsHash)) { if (!Arrays.equals(hash, hdr.contentsHash)) {
Log.w(TAG, "Database file did not decrypt correctly. (checksum code is broken)") Log.w(TAG, "Database file did not decrypt correctly. (checksum code is broken)")
throw LoadDatabaseInvalidCredentialsException() throw InvalidCredentialsDatabaseException()
} }
// New manual root because V3 contains multiple root groups (here available with getRootGroups()) // New manual root because V3 contains multiple root groups (here available with getRootGroups())
@@ -223,9 +223,9 @@ class ImporterV3 : Importer<PwDatabaseV3>() {
} catch (e: LoadDatabaseException) { } catch (e: LoadDatabaseException) {
throw e throw e
} catch (e: IOException) { } catch (e: IOException) {
throw LoadDatabaseIOException(e) throw IODatabaseException(e)
} catch (e: OutOfMemoryError) { } catch (e: OutOfMemoryError) {
throw LoadDatabaseNoMemoryException(e) throw NoMemoryDatabaseException(e)
} catch (e: Exception) { } catch (e: Exception) {
throw LoadDatabaseException(e) throw LoadDatabaseException(e)
} }

View File

@@ -26,19 +26,14 @@ import com.kunzisoft.keepass.crypto.StreamCipherFactory
import com.kunzisoft.keepass.crypto.engine.CipherEngine import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.PwDatabaseV4.Companion.BASE_64_FLAG import com.kunzisoft.keepass.database.element.PwDatabaseV4.Companion.BASE_64_FLAG
import com.kunzisoft.keepass.database.element.PwDatabaseV4.Companion.BUFFER_SIZE_BYTES import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.ProtectedBinary
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.* import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.KDBX4DateUtil import com.kunzisoft.keepass.database.file.KDBX4DateUtil
import com.kunzisoft.keepass.database.file.PwDbHeaderV4 import com.kunzisoft.keepass.database.file.PwDbHeaderV4
import com.kunzisoft.keepass.stream.BetterCipherInputStream import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.stream.HashedBlockInputStream
import com.kunzisoft.keepass.stream.HmacBlockInputStream
import com.kunzisoft.keepass.stream.LEDataInputStream
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import org.apache.commons.io.IOUtils
import org.spongycastle.crypto.StreamCipher import org.spongycastle.crypto.StreamCipher
import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException import org.xmlpull.v1.XmlPullParserException
@@ -48,6 +43,7 @@ import java.nio.charset.Charset
import java.text.ParseException import java.text.ParseException
import java.util.* import java.util.*
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher import javax.crypto.Cipher
import kotlin.math.min import kotlin.math.min
@@ -58,10 +54,9 @@ class ImporterV4(private val streamDir: File,
private lateinit var mDatabase: PwDatabaseV4 private lateinit var mDatabase: PwDatabaseV4
private var hashOfHeader: ByteArray? = null private var hashOfHeader: ByteArray? = null
private var version: Long = 0
private val unusedCacheFileName: String private val unusedCacheFileName: String
get() = mDatabase.binPool.findUnusedKey().toString() get() = mDatabase.binaryPool.findUnusedKey().toString()
private var readNextNode = true private var readNextNode = true
private val ctxGroups = Stack<PwGroupV4>() private val ctxGroups = Stack<PwGroupV4>()
@@ -70,7 +65,7 @@ class ImporterV4(private val streamDir: File,
private var ctxStringName: String? = null private var ctxStringName: String? = null
private var ctxStringValue: ProtectedString? = null private var ctxStringValue: ProtectedString? = null
private var ctxBinaryName: String? = null private var ctxBinaryName: String? = null
private var ctxBinaryValue: ProtectedBinary? = null private var ctxBinaryValue: BinaryAttachment? = null
private var ctxATName: String? = null private var ctxATName: String? = null
private var ctxATSeq: String? = null private var ctxATSeq: String? = null
private var entryInHistory = false private var entryInHistory = false
@@ -102,7 +97,7 @@ class ImporterV4(private val streamDir: File,
val header = PwDbHeaderV4(mDatabase) val header = PwDbHeaderV4(mDatabase)
val headerAndHash = header.loadFromFile(databaseInputStream) val headerAndHash = header.loadFromFile(databaseInputStream)
version = header.version mDatabase.kdbxVersion = header.version
hashOfHeader = headerAndHash.hash hashOfHeader = headerAndHash.hash
val pbHeader = headerAndHash.header val pbHeader = headerAndHash.header
@@ -120,11 +115,11 @@ class ImporterV4(private val streamDir: File,
mDatabase.encryptionAlgorithm = engine.getPwEncryptionAlgorithm() mDatabase.encryptionAlgorithm = engine.getPwEncryptionAlgorithm()
cipher = engine.getCipher(Cipher.DECRYPT_MODE, mDatabase.finalKey!!, header.encryptionIV) cipher = engine.getCipher(Cipher.DECRYPT_MODE, mDatabase.finalKey!!, header.encryptionIV)
} catch (e: Exception) { } catch (e: Exception) {
throw LoadDatabaseInvalidAlgorithmException(e) throw InvalidAlgorithmDatabaseException(e)
} }
val isPlain: InputStream val isPlain: InputStream
if (version < PwDbHeaderV4.FILE_VERSION_32_4) { if (mDatabase.kdbxVersion < PwDbHeaderV4.FILE_VERSION_32_4) {
val decrypted = attachCipherStream(databaseInputStream, cipher) val decrypted = attachCipherStream(databaseInputStream, cipher)
val dataDecrypted = LEDataInputStream(decrypted) val dataDecrypted = LEDataInputStream(decrypted)
@@ -132,14 +127,14 @@ class ImporterV4(private val streamDir: File,
try { try {
storedStartBytes = dataDecrypted.readBytes(32) storedStartBytes = dataDecrypted.readBytes(32)
if (storedStartBytes == null || storedStartBytes.size != 32) { if (storedStartBytes == null || storedStartBytes.size != 32) {
throw LoadDatabaseInvalidCredentialsException() throw InvalidCredentialsDatabaseException()
} }
} catch (e: IOException) { } catch (e: IOException) {
throw LoadDatabaseInvalidCredentialsException() throw InvalidCredentialsDatabaseException()
} }
if (!Arrays.equals(storedStartBytes, header.streamStartBytes)) { if (!Arrays.equals(storedStartBytes, header.streamStartBytes)) {
throw LoadDatabaseInvalidCredentialsException() throw InvalidCredentialsDatabaseException()
} }
isPlain = HashedBlockInputStream(dataDecrypted) isPlain = HashedBlockInputStream(dataDecrypted)
@@ -147,18 +142,18 @@ class ImporterV4(private val streamDir: File,
val isData = LEDataInputStream(databaseInputStream) val isData = LEDataInputStream(databaseInputStream)
val storedHash = isData.readBytes(32) val storedHash = isData.readBytes(32)
if (!Arrays.equals(storedHash, hashOfHeader)) { if (!Arrays.equals(storedHash, hashOfHeader)) {
throw LoadDatabaseInvalidCredentialsException() throw InvalidCredentialsDatabaseException()
} }
val hmacKey = mDatabase.hmacKey ?: throw LoadDatabaseException() val hmacKey = mDatabase.hmacKey ?: throw LoadDatabaseException()
val headerHmac = PwDbHeaderV4.computeHeaderHmac(pbHeader, hmacKey) val headerHmac = PwDbHeaderV4.computeHeaderHmac(pbHeader, hmacKey)
val storedHmac = isData.readBytes(32) val storedHmac = isData.readBytes(32)
if (storedHmac == null || storedHmac.size != 32) { if (storedHmac == null || storedHmac.size != 32) {
throw LoadDatabaseInvalidCredentialsException() throw InvalidCredentialsDatabaseException()
} }
// Mac doesn't match // Mac doesn't match
if (!Arrays.equals(headerHmac, storedHmac)) { if (!Arrays.equals(headerHmac, storedHmac)) {
throw LoadDatabaseInvalidCredentialsException() throw InvalidCredentialsDatabaseException()
} }
val hmIs = HmacBlockInputStream(isData, true, hmacKey) val hmIs = HmacBlockInputStream(isData, true, hmacKey)
@@ -172,14 +167,14 @@ class ImporterV4(private val streamDir: File,
else -> isPlain else -> isPlain
} }
if (version >= PwDbHeaderV4.FILE_VERSION_32_4) { if (mDatabase.kdbxVersion >= PwDbHeaderV4.FILE_VERSION_32_4) {
loadInnerHeader(inputStreamXml, header) loadInnerHeader(inputStreamXml, header)
} }
randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey) randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey)
if (randomStream == null) { if (randomStream == null) {
throw LoadDatabaseArcFourException() throw ArcFourDatabaseException()
} }
readDocumentStreamed(createPullParser(inputStreamXml)) readDocumentStreamed(createPullParser(inputStreamXml))
@@ -187,14 +182,14 @@ class ImporterV4(private val streamDir: File,
} catch (e: LoadDatabaseException) { } catch (e: LoadDatabaseException) {
throw e throw e
} catch (e: XmlPullParserException) { } catch (e: XmlPullParserException) {
throw LoadDatabaseIOException(e) throw IODatabaseException(e)
} catch (e: IOException) { } catch (e: IOException) {
if (e.message?.contains("Hash failed with code") == true) if (e.message?.contains("Hash failed with code") == true)
throw LoadDatabaseKDFMemoryException(e) throw KDFMemoryDatabaseException(e)
else else
throw LoadDatabaseIOException(e) throw IODatabaseException(e)
} catch (e: OutOfMemoryError) { } catch (e: OutOfMemoryError) {
throw LoadDatabaseNoMemoryException(e) throw NoMemoryDatabaseException(e)
} catch (e: Exception) { } catch (e: Exception) {
throw LoadDatabaseException(e) throw LoadDatabaseException(e)
} }
@@ -241,10 +236,14 @@ class ImporterV4(private val streamDir: File,
// Read in a file // Read in a file
val file = File(streamDir, unusedCacheFileName) val file = File(streamDir, unusedCacheFileName)
FileOutputStream(file).use { outputStream -> FileOutputStream(file).use { outputStream ->
dataInputStream.readBytes(byteLength) { outputStream.write(it) } dataInputStream.readBytes(byteLength, object : ReadBytes {
override fun read(buffer: ByteArray) {
outputStream.write(buffer)
} }
val protectedBinary = ProtectedBinary(protectedFlag, file) })
mDatabase.binPool.add(protectedBinary) }
val protectedBinary = BinaryAttachment(file, protectedFlag)
mDatabase.binaryPool.add(protectedBinary)
} }
else -> { else -> {
return false return false
@@ -432,9 +431,9 @@ class ImporterV4(private val streamDir: File,
KdbContext.Binaries -> if (name.equals(PwDatabaseV4XML.ElemBinary, ignoreCase = true)) { KdbContext.Binaries -> if (name.equals(PwDatabaseV4XML.ElemBinary, ignoreCase = true)) {
val key = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrId) val key = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrId)
if (key != null) { if (key != null) {
val pbData = readProtectedBinary(xpp) val pbData = readBinary(xpp)
val id = Integer.parseInt(key) val id = Integer.parseInt(key)
mDatabase.binPool.put(id, pbData!!) mDatabase.binaryPool.put(id, pbData!!)
} else { } else {
readUnknown(xpp) readUnknown(xpp)
} }
@@ -606,7 +605,7 @@ class ImporterV4(private val streamDir: File,
KdbContext.EntryBinary -> if (name.equals(PwDatabaseV4XML.ElemKey, ignoreCase = true)) { KdbContext.EntryBinary -> if (name.equals(PwDatabaseV4XML.ElemKey, ignoreCase = true)) {
ctxBinaryName = readString(xpp) ctxBinaryName = readString(xpp)
} else if (name.equals(PwDatabaseV4XML.ElemValue, ignoreCase = true)) { } else if (name.equals(PwDatabaseV4XML.ElemValue, ignoreCase = true)) {
ctxBinaryValue = readProtectedBinary(xpp) ctxBinaryValue = readBinary(xpp)
} }
KdbContext.EntryAutoType -> if (name.equals(PwDatabaseV4XML.ElemAutoTypeEnabled, ignoreCase = true)) { KdbContext.EntryAutoType -> if (name.equals(PwDatabaseV4XML.ElemAutoTypeEnabled, ignoreCase = true)) {
@@ -806,7 +805,7 @@ class ImporterV4(private val streamDir: File,
val sDate = readString(xpp) val sDate = readString(xpp)
var utcDate: Date? = null var utcDate: Date? = null
if (version >= PwDbHeaderV4.FILE_VERSION_32_4) { if (mDatabase.kdbxVersion >= PwDbHeaderV4.FILE_VERSION_32_4) {
var buf = Base64.decode(sDate, BASE_64_FLAG) var buf = Base64.decode(sDate, BASE_64_FLAG)
if (buf.size != 8) { if (buf.size != 8) {
val buf8 = ByteArray(8) val buf8 = ByteArray(8)
@@ -939,16 +938,20 @@ class ImporterV4(private val streamDir: File,
} }
@Throws(XmlPullParserException::class, IOException::class) @Throws(XmlPullParserException::class, IOException::class)
private fun readProtectedBinary(xpp: XmlPullParser): ProtectedBinary? { private fun readBinary(xpp: XmlPullParser): BinaryAttachment? {
// Reference Id to a binary already present in binary pool
val ref = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrRef) val ref = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrRef)
if (ref != null) { if (ref != null) {
xpp.next() // Consume end tag xpp.next() // Consume end tag
val id = Integer.parseInt(ref) val id = Integer.parseInt(ref)
return mDatabase.binPool[id] return mDatabase.binaryPool[id]
} }
var compressed = false // New binary to retrieve
else {
var compressed: Boolean? = null
var protected = false var protected = false
if (xpp.attributeCount > 0) { if (xpp.attributeCount > 0) {
@@ -965,24 +968,21 @@ class ImporterV4(private val streamDir: File,
val base64 = readString(xpp) val base64 = readString(xpp)
if (base64.isEmpty()) if (base64.isEmpty())
return ProtectedBinary() return BinaryAttachment()
val data = Base64.decode(base64, BASE_64_FLAG) val data = Base64.decode(base64, BASE_64_FLAG)
return if (!compressed && data.size <= BUFFER_SIZE_BYTES) {
// Small data, don't need a file
ProtectedBinary(protected, data)
} else {
val file = File(streamDir, unusedCacheFileName) val file = File(streamDir, unusedCacheFileName)
if (compressed) { return FileOutputStream(file).use { outputStream ->
FileOutputStream(file).use { outputStream -> // Force compression in this specific case
IOUtils.copy(GZIPInputStream(ByteArrayInputStream(data)), outputStream) if (mDatabase.compressionAlgorithm == PwCompressionAlgorithm.GZip
} && compressed == false) {
GZIPOutputStream(outputStream).write(data)
BinaryAttachment(file, protected, true)
} else { } else {
FileOutputStream(file).use { outputStream ->
outputStream.write(data) outputStream.write(data)
BinaryAttachment(file, protected)
} }
} }
ProtectedBinary(protected, file)
} }
} }

View File

@@ -54,7 +54,7 @@ constructor(private val db: PwDatabaseV4, private val header: PwDbHeaderV4, os:
try { try {
md = MessageDigest.getInstance("SHA-256") md = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) { } catch (e: NoSuchAlgorithmException) {
throw DatabaseOutputException("SHA-256 not implemented here.") throw DatabaseOutputException("SHA-256 not implemented here.", e)
} }
try { try {

View File

@@ -22,10 +22,10 @@ package com.kunzisoft.keepass.database.file.save
import com.kunzisoft.keepass.database.element.PwDatabaseV4 import com.kunzisoft.keepass.database.element.PwDatabaseV4
import com.kunzisoft.keepass.database.element.PwDatabaseV4.Companion.BUFFER_SIZE_BYTES import com.kunzisoft.keepass.database.element.PwDatabaseV4.Companion.BUFFER_SIZE_BYTES
import com.kunzisoft.keepass.database.file.PwDbHeaderV4 import com.kunzisoft.keepass.database.file.PwDbHeaderV4
import com.kunzisoft.keepass.stream.ActionReadBytes import com.kunzisoft.keepass.stream.ReadBytes
import com.kunzisoft.keepass.stream.LEDataOutputStream import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.stream.readFromStream
import java.io.IOException import java.io.IOException
import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import kotlin.experimental.or import kotlin.experimental.or
@@ -48,7 +48,7 @@ class PwDbInnerHeaderOutputV4(private val database: PwDatabaseV4,
dataOutputStream.writeInt(streamKeySize) dataOutputStream.writeInt(streamKeySize)
dataOutputStream.write(header.innerRandomStreamKey) dataOutputStream.write(header.innerRandomStreamKey)
database.binPool.doForEachBinary { _, protectedBinary -> database.binaryPool.doForEachBinary { _, protectedBinary ->
var flag = PwDbHeaderV4.KdbxBinaryFlags.None var flag = PwDbHeaderV4.KdbxBinaryFlags.None
if (protectedBinary.isProtected) { if (protectedBinary.isProtected) {
flag = flag or PwDbHeaderV4.KdbxBinaryFlags.Protected flag = flag or PwDbHeaderV4.KdbxBinaryFlags.Protected
@@ -58,32 +58,16 @@ class PwDbInnerHeaderOutputV4(private val database: PwDatabaseV4,
dataOutputStream.writeInt(protectedBinary.length().toInt() + 1) // TODO verify dataOutputStream.writeInt(protectedBinary.length().toInt() + 1) // TODO verify
dataOutputStream.write(flag.toInt()) dataOutputStream.write(flag.toInt())
protectedBinary.getData()?.let { readFromStream(protectedBinary.getInputDataStream(), BUFFER_SIZE_BYTES,
readBytes(it, ActionReadBytes { buffer -> object : ReadBytes {
override fun read(buffer: ByteArray) {
dataOutputStream.write(buffer) dataOutputStream.write(buffer)
}) }
} ?: throw IOException("Can't write protected binary") }
)
} }
dataOutputStream.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.EndOfHeader.toInt()) dataOutputStream.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.EndOfHeader.toInt())
dataOutputStream.writeInt(0) dataOutputStream.writeInt(0)
} }
@Throws(IOException::class)
fun readBytes(inputStream: InputStream, actionReadBytes: ActionReadBytes) {
val buffer = ByteArray(BUFFER_SIZE_BYTES)
var read = 0
while (read != -1) {
read = inputStream.read(buffer, 0, buffer.size)
if (read != -1) {
val optimizedBuffer: ByteArray = if (buffer.size == read) {
buffer
} else {
buffer.copyOf(read)
}
actionReadBytes.doAction(optimizedBuffer)
}
}
}
} }

View File

@@ -34,7 +34,7 @@ abstract class PwDbOutput<Header : PwDbHeader> protected constructor(protected v
try { try {
random = SecureRandom.getInstance("SHA1PRNG") random = SecureRandom.getInstance("SHA1PRNG")
} catch (e: NoSuchAlgorithmException) { } catch (e: NoSuchAlgorithmException) {
throw DatabaseOutputException("Does not support secure random number generation.") throw DatabaseOutputException("Does not support secure random number generation.", e)
} }
random.nextBytes(header.encryptionIV) random.nextBytes(header.encryptionIV)

View File

@@ -31,15 +31,13 @@ import com.kunzisoft.keepass.database.NodeHandler
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.PwDatabaseV4.Companion.BASE_64_FLAG import com.kunzisoft.keepass.database.element.PwDatabaseV4.Companion.BASE_64_FLAG
import com.kunzisoft.keepass.database.element.PwDatabaseV4.Companion.BUFFER_SIZE_BYTES import com.kunzisoft.keepass.database.element.PwDatabaseV4.Companion.BUFFER_SIZE_BYTES
import com.kunzisoft.keepass.database.element.security.ProtectedBinary import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.exception.UnknownKDF import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.file.KDBX4DateUtil import com.kunzisoft.keepass.database.file.KDBX4DateUtil
import com.kunzisoft.keepass.database.file.PwDbHeaderV4 import com.kunzisoft.keepass.database.file.PwDbHeaderV4
import com.kunzisoft.keepass.stream.HashedBlockOutputStream import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.stream.HmacBlockOutputStream
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import org.joda.time.DateTime import org.joda.time.DateTime
import org.spongycastle.crypto.StreamCipher import org.spongycastle.crypto.StreamCipher
@@ -48,12 +46,14 @@ import java.io.*
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.security.SecureRandom import java.security.SecureRandom
import java.util.* import java.util.*
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.CipherOutputStream import javax.crypto.CipherOutputStream
class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputStream) : PwDbOutput<PwDbHeaderV4>(outputStream) { class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4,
outputStream: OutputStream) : PwDbOutput<PwDbHeaderV4>(outputStream) {
private var randomStream: StreamCipher? = null private var randomStream: StreamCipher? = null
private lateinit var xml: XmlSerializer private lateinit var xml: XmlSerializer
@@ -75,17 +75,16 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
header = outputHeader(mOS) header = outputHeader(mOS)
val osPlain: OutputStream val osPlain: OutputStream
if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4) { osPlain = if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4) {
val cos = attachStreamEncryptor(header!!, mOS) val cos = attachStreamEncryptor(header!!, mOS)
cos.write(header!!.streamStartBytes) cos.write(header!!.streamStartBytes)
osPlain = HashedBlockOutputStream(cos) HashedBlockOutputStream(cos)
} else { } else {
mOS.write(hashOfHeader!!) mOS.write(hashOfHeader!!)
mOS.write(headerHmac!!) mOS.write(headerHmac!!)
val hbos = HmacBlockOutputStream(mOS, mDatabaseV4.hmacKey) attachStreamEncryptor(header!!, HmacBlockOutputStream(mOS, mDatabaseV4.hmacKey))
osPlain = attachStreamEncryptor(header!!, hbos)
} }
val osXml: OutputStream val osXml: OutputStream
@@ -114,11 +113,11 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun outputDatabase(os: OutputStream) { private fun outputDatabase(outputStream: OutputStream) {
xml = Xml.newSerializer() xml = Xml.newSerializer()
xml.setOutput(os, "UTF-8") xml.setOutput(outputStream, "UTF-8")
xml.startDocument("UTF-8", true) xml.startDocument("UTF-8", true)
xml.startTag(null, PwDatabaseV4XML.ElemDocNode) xml.startTag(null, PwDatabaseV4XML.ElemDocNode)
@@ -210,18 +209,19 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
writeCustomIconList() writeCustomIconList()
writeObject(PwDatabaseV4XML.ElemRecycleBinEnabled, mDatabaseV4.isRecycleBinEnabled) writeObject(PwDatabaseV4XML.ElemRecycleBinEnabled, mDatabaseV4.isRecycleBinEnabled)
writeObject(PwDatabaseV4XML.ElemRecycleBinUuid, mDatabaseV4.recycleBinUUID) writeUuid(PwDatabaseV4XML.ElemRecycleBinUuid, mDatabaseV4.recycleBinUUID)
writeObject(PwDatabaseV4XML.ElemRecycleBinChanged, mDatabaseV4.recycleBinChanged) writeObject(PwDatabaseV4XML.ElemRecycleBinChanged, mDatabaseV4.recycleBinChanged)
writeObject(PwDatabaseV4XML.ElemEntryTemplatesGroup, mDatabaseV4.entryTemplatesGroup) writeUuid(PwDatabaseV4XML.ElemEntryTemplatesGroup, mDatabaseV4.entryTemplatesGroup)
writeObject(PwDatabaseV4XML.ElemEntryTemplatesGroupChanged, mDatabaseV4.entryTemplatesGroupChanged.date) writeObject(PwDatabaseV4XML.ElemEntryTemplatesGroupChanged, mDatabaseV4.entryTemplatesGroupChanged.date)
writeObject(PwDatabaseV4XML.ElemHistoryMaxItems, mDatabaseV4.historyMaxItems.toLong()) writeObject(PwDatabaseV4XML.ElemHistoryMaxItems, mDatabaseV4.historyMaxItems.toLong())
writeObject(PwDatabaseV4XML.ElemHistoryMaxSize, mDatabaseV4.historyMaxSize) writeObject(PwDatabaseV4XML.ElemHistoryMaxSize, mDatabaseV4.historyMaxSize)
writeObject(PwDatabaseV4XML.ElemLastSelectedGroup, mDatabaseV4.lastSelectedGroupUUID) writeUuid(PwDatabaseV4XML.ElemLastSelectedGroup, mDatabaseV4.lastSelectedGroupUUID)
writeObject(PwDatabaseV4XML.ElemLastTopVisibleGroup, mDatabaseV4.lastTopVisibleGroupUUID) writeUuid(PwDatabaseV4XML.ElemLastTopVisibleGroup, mDatabaseV4.lastTopVisibleGroupUUID)
// Seem to work properly if always in meta
// if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4)
writeMetaBinaries()
if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4) {
writeBinPool()
}
writeCustomData(mDatabaseV4.customData) writeCustomData(mDatabaseV4.customData)
xml.endTag(null, PwDatabaseV4XML.ElemMeta) xml.endTag(null, PwDatabaseV4XML.ElemMeta)
@@ -306,13 +306,13 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun startGroup(group: PwGroupV4) { private fun startGroup(group: PwGroupV4) {
xml.startTag(null, PwDatabaseV4XML.ElemGroup) xml.startTag(null, PwDatabaseV4XML.ElemGroup)
writeObject(PwDatabaseV4XML.ElemUuid, group.id) writeUuid(PwDatabaseV4XML.ElemUuid, group.id)
writeObject(PwDatabaseV4XML.ElemName, group.title) writeObject(PwDatabaseV4XML.ElemName, group.title)
writeObject(PwDatabaseV4XML.ElemNotes, group.notes) writeObject(PwDatabaseV4XML.ElemNotes, group.notes)
writeObject(PwDatabaseV4XML.ElemIcon, group.icon.iconId.toLong()) writeObject(PwDatabaseV4XML.ElemIcon, group.icon.iconId.toLong())
if (group.iconCustom != PwIconCustom.UNKNOWN_ICON) { if (group.iconCustom != PwIconCustom.UNKNOWN_ICON) {
writeObject(PwDatabaseV4XML.ElemCustomIconID, group.iconCustom.uuid) writeUuid(PwDatabaseV4XML.ElemCustomIconID, group.iconCustom.uuid)
} }
writeTimes(group) writeTimes(group)
@@ -320,7 +320,7 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
writeObject(PwDatabaseV4XML.ElemGroupDefaultAutoTypeSeq, group.defaultAutoTypeSequence) writeObject(PwDatabaseV4XML.ElemGroupDefaultAutoTypeSeq, group.defaultAutoTypeSequence)
writeObject(PwDatabaseV4XML.ElemEnableAutoType, group.enableAutoType) writeObject(PwDatabaseV4XML.ElemEnableAutoType, group.enableAutoType)
writeObject(PwDatabaseV4XML.ElemEnableSearching, group.enableSearching) writeObject(PwDatabaseV4XML.ElemEnableSearching, group.enableSearching)
writeObject(PwDatabaseV4XML.ElemLastTopVisibleEntry, group.lastTopVisibleEntry) writeUuid(PwDatabaseV4XML.ElemLastTopVisibleEntry, group.lastTopVisibleEntry)
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
@@ -333,11 +333,11 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
xml.startTag(null, PwDatabaseV4XML.ElemEntry) xml.startTag(null, PwDatabaseV4XML.ElemEntry)
writeObject(PwDatabaseV4XML.ElemUuid, entry.id) writeUuid(PwDatabaseV4XML.ElemUuid, entry.id)
writeObject(PwDatabaseV4XML.ElemIcon, entry.icon.iconId.toLong()) writeObject(PwDatabaseV4XML.ElemIcon, entry.icon.iconId.toLong())
if (entry.iconCustom != PwIconCustom.UNKNOWN_ICON) { if (entry.iconCustom != PwIconCustom.UNKNOWN_ICON) {
writeObject(PwDatabaseV4XML.ElemCustomIconID, entry.iconCustom.uuid) writeUuid(PwDatabaseV4XML.ElemCustomIconID, entry.iconCustom.uuid)
} }
writeObject(PwDatabaseV4XML.ElemFgColor, entry.foregroundColor) writeObject(PwDatabaseV4XML.ElemFgColor, entry.foregroundColor)
@@ -348,7 +348,7 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
writeTimes(entry) writeTimes(entry)
writeFields(entry.fields) writeFields(entry.fields)
writeList(entry.binaries) writeEntryBinaries(entry.binaries)
writeAutoType(entry.autoType) writeAutoType(entry.autoType)
if (!isHistory) { if (!isHistory) {
@@ -358,81 +358,6 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
xml.endTag(null, PwDatabaseV4XML.ElemEntry) xml.endTag(null, PwDatabaseV4XML.ElemEntry)
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeObject(key: String, binary: ProtectedBinary) {
xml.startTag(null, PwDatabaseV4XML.ElemBinary)
xml.startTag(null, PwDatabaseV4XML.ElemKey)
xml.text(safeXmlString(key))
xml.endTag(null, PwDatabaseV4XML.ElemKey)
xml.startTag(null, PwDatabaseV4XML.ElemValue)
val ref = mDatabaseV4.binPool.findKey(binary)
if (ref != null) {
xml.attribute(null, PwDatabaseV4XML.AttrRef, ref.toString())
} else {
writeBinary(binary)
}
xml.endTag(null, PwDatabaseV4XML.ElemValue)
xml.endTag(null, PwDatabaseV4XML.ElemBinary)
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeBinary(value: ProtectedBinary) {
val valLength = value.length().toInt()
if (valLength > 0) {
val buffer = ByteArray(valLength)
if (valLength == value.getData()!!.read(buffer, 0, valLength)) {
if (value.isProtected) {
xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue)
val encoded = ByteArray(valLength)
randomStream!!.processBytes(buffer, 0, valLength, encoded, 0)
xml.text(String(Base64.encode(encoded, BASE_64_FLAG)))
} else {
if (mDatabaseV4.compressionAlgorithm === PwCompressionAlgorithm.GZip) {
xml.attribute(null, PwDatabaseV4XML.AttrCompressed, PwDatabaseV4XML.ValTrue)
val byteArrayOutputStream = ByteArrayOutputStream()
val gzipOutputStream = GZIPOutputStream(byteArrayOutputStream)
copyStream(ByteArrayInputStream(buffer), gzipOutputStream)
// IOUtils.copy(ByteArrayInputStream(ByteArrayInputStream(buffer)), gzipOutputStream)
gzipOutputStream.close()
xml.text(String(Base64.encode(byteArrayOutputStream.toByteArray(), BASE_64_FLAG)))
} else {
xml.text(String(Base64.encode(buffer, BASE_64_FLAG)))
}
}
} else {
Log.e(TAG, "Unable to read the stream of the protected binary")
}
}
}
@Throws(IOException::class)
fun copyStream(inputStream: InputStream, out: OutputStream) {
val buffer = ByteArray(BUFFER_SIZE_BYTES)
try {
var read = inputStream.read(buffer)
while (read != -1) {
out.write(buffer, 0, read)
read = inputStream.read(buffer)
if (Thread.interrupted()) {
throw InterruptedException()
}
}
} catch (error: OutOfMemoryError) {
throw IOException(error)
}
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeObject(name: String, value: String, filterXmlChars: Boolean = false) { private fun writeObject(name: String, value: String, filterXmlChars: Boolean = false) {
var xmlString = value var xmlString = value
@@ -477,11 +402,52 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeObject(name: String, uuid: UUID) { private fun writeUuid(name: String, uuid: UUID) {
val data = DatabaseInputOutputUtils.uuidToBytes(uuid) val data = DatabaseInputOutputUtils.uuidToBytes(uuid)
writeObject(name, String(Base64.encode(data, BASE_64_FLAG))) writeObject(name, String(Base64.encode(data, BASE_64_FLAG)))
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeMetaBinaries() {
xml.startTag(null, PwDatabaseV4XML.ElemBinaries)
mDatabaseV4.binaryPool.doForEachBinary { key, binary ->
xml.startTag(null, PwDatabaseV4XML.ElemBinary)
xml.attribute(null, PwDatabaseV4XML.AttrId, key.toString())
// Force binary compression from database (compression was harmonized during import)
xml.attribute(null, PwDatabaseV4XML.AttrCompressed,
if (mDatabaseV4.compressionAlgorithm === PwCompressionAlgorithm.GZip) {
PwDatabaseV4XML.ValTrue
} else {
PwDatabaseV4XML.ValFalse
}
)
// Force decompression in this specific case
val binaryInputStream = if (mDatabaseV4.compressionAlgorithm == PwCompressionAlgorithm.None
&& binary.isCompressed == true) {
GZIPInputStream(binary.getInputDataStream())
} else {
binary.getInputDataStream()
}
// Write the XML
readFromStream(binaryInputStream, BUFFER_SIZE_BYTES,
object : ReadBytes {
override fun read(buffer: ByteArray) {
val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray()
xml.text(charArray, 0, charArray.size)
}
}
)
xml.endTag(null, PwDatabaseV4XML.ElemBinary)
}
xml.endTag(null, PwDatabaseV4XML.ElemBinaries)
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeObject(name: String, keyName: String, keyValue: String, valueName: String, valueValue: String) { private fun writeObject(name: String, keyName: String, keyValue: String, valueName: String, valueValue: String) {
xml.startTag(null, name) xml.startTag(null, name)
@@ -565,19 +531,57 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
private fun writeDeletedObject(value: PwDeletedObject) { private fun writeDeletedObject(value: PwDeletedObject) {
xml.startTag(null, PwDatabaseV4XML.ElemDeletedObject) xml.startTag(null, PwDatabaseV4XML.ElemDeletedObject)
writeObject(PwDatabaseV4XML.ElemUuid, value.uuid) writeUuid(PwDatabaseV4XML.ElemUuid, value.uuid)
writeObject(PwDatabaseV4XML.ElemDeletionTime, value.deletionTime) writeObject(PwDatabaseV4XML.ElemDeletionTime, value.deletionTime)
xml.endTag(null, PwDatabaseV4XML.ElemDeletedObject) xml.endTag(null, PwDatabaseV4XML.ElemDeletedObject)
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeList(binaries: Map<String, ProtectedBinary>) { private fun writeEntryBinaries(binaries: Map<String, BinaryAttachment>) {
for ((key, value) in binaries) { for ((key, binary) in binaries) {
writeObject(key, value) xml.startTag(null, PwDatabaseV4XML.ElemBinary)
} xml.startTag(null, PwDatabaseV4XML.ElemKey)
} xml.text(safeXmlString(key))
xml.endTag(null, PwDatabaseV4XML.ElemKey)
xml.startTag(null, PwDatabaseV4XML.ElemValue)
val ref = mDatabaseV4.binaryPool.findKey(binary)
if (ref != null) {
xml.attribute(null, PwDatabaseV4XML.AttrRef, ref.toString())
} else {
val binaryLength = binary.length()
if (binaryLength > 0) {
if (binary.isProtected) {
xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue)
readFromStream(binary.getInputDataStream(), BUFFER_SIZE_BYTES,
object : ReadBytes {
override fun read(buffer: ByteArray) {
val encoded = ByteArray(buffer.size)
randomStream!!.processBytes(buffer, 0, encoded.size, encoded, 0)
val charArray = String(Base64.encode(encoded, BASE_64_FLAG)).toCharArray()
xml.text(charArray, 0, charArray.size)
}
})
} else {
readFromStream(binary.getInputDataStream(), BUFFER_SIZE_BYTES,
object : ReadBytes {
override fun read(buffer: ByteArray) {
val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray()
xml.text(charArray, 0, charArray.size)
}
}
)
}
}
}
xml.endTag(null, PwDatabaseV4XML.ElemValue)
xml.endTag(null, PwDatabaseV4XML.ElemBinary)
}
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeDeletedObjects(value: List<PwDeletedObject>) { private fun writeDeletedObjects(value: List<PwDeletedObject>) {
@@ -658,7 +662,7 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
for (icon in customIcons) { for (icon in customIcons) {
xml.startTag(null, PwDatabaseV4XML.ElemCustomIconItem) xml.startTag(null, PwDatabaseV4XML.ElemCustomIconItem)
writeObject(PwDatabaseV4XML.ElemCustomIconItemID, icon.uuid) writeUuid(PwDatabaseV4XML.ElemCustomIconItemID, icon.uuid)
writeObject(PwDatabaseV4XML.ElemCustomIconItemData, String(Base64.encode(icon.imageData, BASE_64_FLAG))) writeObject(PwDatabaseV4XML.ElemCustomIconItemData, String(Base64.encode(icon.imageData, BASE_64_FLAG)))
xml.endTag(null, PwDatabaseV4XML.ElemCustomIconItem) xml.endTag(null, PwDatabaseV4XML.ElemCustomIconItem)
@@ -667,22 +671,6 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
xml.endTag(null, PwDatabaseV4XML.ElemCustomIcons) xml.endTag(null, PwDatabaseV4XML.ElemCustomIcons)
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeBinPool() {
xml.startTag(null, PwDatabaseV4XML.ElemBinaries)
mDatabaseV4.binPool.doForEachBinary { key, binary ->
xml.startTag(null, PwDatabaseV4XML.ElemBinary)
xml.attribute(null, PwDatabaseV4XML.AttrId, key.toString())
writeBinary(binary)
xml.endTag(null, PwDatabaseV4XML.ElemBinary)
}
xml.endTag(null, PwDatabaseV4XML.ElemBinaries)
}
private fun safeXmlString(text: String): String { private fun safeXmlString(text: String): String {
if (text.isEmpty()) { if (text.isEmpty()) {
return text return text

View File

@@ -8,10 +8,7 @@ import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.database.action.AssignPasswordInDatabaseRunnable import com.kunzisoft.keepass.database.action.*
import com.kunzisoft.keepass.database.action.CreateDatabaseRunnable
import com.kunzisoft.keepass.database.action.LoadDatabaseRunnable
import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable
import com.kunzisoft.keepass.database.action.node.* import com.kunzisoft.keepass.database.action.node.*
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
@@ -107,11 +104,11 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat
ACTION_DATABASE_COPY_NODES_TASK -> buildDatabaseCopyNodesActionTask(intent) ACTION_DATABASE_COPY_NODES_TASK -> buildDatabaseCopyNodesActionTask(intent)
ACTION_DATABASE_MOVE_NODES_TASK -> buildDatabaseMoveNodesActionTask(intent) ACTION_DATABASE_MOVE_NODES_TASK -> buildDatabaseMoveNodesActionTask(intent)
ACTION_DATABASE_DELETE_NODES_TASK -> buildDatabaseDeleteNodesActionTask(intent) ACTION_DATABASE_DELETE_NODES_TASK -> buildDatabaseDeleteNodesActionTask(intent)
ACTION_DATABASE_UPDATE_COMPRESSION_TASK -> buildDatabaseUpdateCompressionActionTask(intent)
ACTION_DATABASE_UPDATE_NAME_TASK, ACTION_DATABASE_UPDATE_NAME_TASK,
ACTION_DATABASE_UPDATE_DESCRIPTION_TASK, ACTION_DATABASE_UPDATE_DESCRIPTION_TASK,
ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK, ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK,
ACTION_DATABASE_UPDATE_COLOR_TASK, ACTION_DATABASE_UPDATE_COLOR_TASK,
ACTION_DATABASE_UPDATE_COMPRESSION_TASK,
ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK, ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK,
ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK, ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK,
ACTION_DATABASE_UPDATE_ENCRYPTION_TASK, ACTION_DATABASE_UPDATE_ENCRYPTION_TASK,
@@ -409,6 +406,25 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat
} }
} }
private fun buildDatabaseUpdateCompressionActionTask(intent: Intent): ActionRunnable? {
return if (intent.hasExtra(OLD_ELEMENT_KEY)
&& intent.hasExtra(NEW_ELEMENT_KEY)
&& intent.hasExtra(SAVE_DATABASE_KEY)) {
return UpdateCompressionBinariesDatabaseRunnable(this,
Database.getInstance(),
intent.getParcelableExtra(OLD_ELEMENT_KEY),
intent.getParcelableExtra(NEW_ELEMENT_KEY),
intent.getBooleanExtra(SAVE_DATABASE_KEY, false)
).apply {
mAfterSaveDatabase = { result ->
result.data = intent.extras
}
}
} else {
null
}
}
private fun buildDatabaseUpdateElementActionTask(intent: Intent): ActionRunnable? { private fun buildDatabaseUpdateElementActionTask(intent: Intent): ActionRunnable? {
return if (intent.hasExtra(SAVE_DATABASE_KEY)) { return if (intent.hasExtra(SAVE_DATABASE_KEY)) {
return SaveDatabaseRunnable(this, return SaveDatabaseRunnable(this,

View File

@@ -1,11 +0,0 @@
package com.kunzisoft.keepass.stream;
import java.io.IOException;
public interface ActionReadBytes {
/**
* Called after each buffer fill
* @param buffer filled
*/
void doAction(byte[] buffer) throws IOException;
}

View File

@@ -124,7 +124,7 @@ public class LEDataInputStream extends InputStream {
return buf; return buf;
} }
public void readBytes(int length, ActionReadBytes actionReadBytes) throws IOException { public void readBytes(int length, ReadBytes readBytes) throws IOException {
int bufferSize = 256 * 3; // TODO Buffer size int bufferSize = 256 * 3; // TODO Buffer size
byte[] buffer = new byte[bufferSize]; byte[] buffer = new byte[bufferSize];
@@ -146,7 +146,7 @@ public class LEDataInputStream extends InputStream {
} else { } else {
optimizedBuffer = buffer; optimizedBuffer = buffer;
} }
actionReadBytes.doAction(optimizedBuffer); readBytes.read(optimizedBuffer);
offset += read; offset += read;
} }
} }

View File

@@ -0,0 +1,30 @@
package com.kunzisoft.keepass.stream
import java.io.IOException
import java.io.InputStream
interface ReadBytes {
/**
* Called after each buffer fill
* @param buffer filled
*/
@Throws(IOException::class)
fun read(buffer: ByteArray)
}
@Throws(IOException::class)
fun readFromStream(inputStream: InputStream, bufferSize: Int, readBytes: ReadBytes) {
val buffer = ByteArray(bufferSize)
var read = 0
while (read != -1) {
read = inputStream.read(buffer, 0, buffer.size)
if (read != -1) {
val optimizedBuffer: ByteArray = if (buffer.size == read) {
buffer
} else {
buffer.copyOf(read)
}
readBytes.read(optimizedBuffer)
}
}
}

View File

@@ -20,7 +20,8 @@
package com.kunzisoft.keepass.tasks package com.kunzisoft.keepass.tasks
import android.os.Bundle import android.os.Bundle
import com.kunzisoft.keepass.database.exception.LoadDatabaseException import com.kunzisoft.keepass.database.exception.DatabaseException
import java.lang.Exception
/** /**
* Callback after a task is completed. * Callback after a task is completed.
@@ -44,24 +45,29 @@ abstract class ActionRunnable: Runnable {
*/ */
abstract fun onFinishRun() abstract fun onFinishRun()
protected fun setError(message: String? = null) { protected fun setError(message: String) {
setError(null, message)
}
protected fun setError(exception: LoadDatabaseException?,
message: String? = null) {
result.isSuccess = false result.isSuccess = false
result.exception = exception result.exception = null
result.message = message result.message = message
} }
protected fun setError(exception: Exception) {
result.isSuccess = false
result.exception = null
result.message = exception.message
}
protected fun setError(exception: DatabaseException) {
result.isSuccess = false
result.exception = exception
result.message = exception.message
}
/** /**
* Class to manage result from ActionRunnable * Class to manage result from ActionRunnable
*/ */
data class Result(var isSuccess: Boolean = true, data class Result(var isSuccess: Boolean = true,
var message: String? = null, var message: String? = null,
var exception: LoadDatabaseException? = null, var exception: DatabaseException? = null,
var data: Bundle? = null) var data: Bundle? = null)
} }

View File

@@ -96,3 +96,9 @@ object ParcelableUtil {
return map return map
} }
} }
inline fun <reified T : Enum<T>> Parcel.readEnum() =
readString()?.let { enumValueOf<T>(it) }
inline fun <T : Enum<T>> Parcel.writeEnum(value: T?) =
writeString(value?.name)

View File

@@ -121,6 +121,7 @@
<string name="error_copy_entry_here">You can not copy an entry here.</string> <string name="error_copy_entry_here">You can not copy an entry here.</string>
<string name="error_copy_group_here">You can not copy a group here.</string> <string name="error_copy_group_here">You can not copy a group here.</string>
<string name="error_create_database_file">Unable to create database with this password and key file.</string> <string name="error_create_database_file">Unable to create database with this password and key file.</string>
<string name="error_save_database">Could not save database.</string>
<string name="error_otp_secret_key">Secret key must be in Base32 format.</string> <string name="error_otp_secret_key">Secret key must be in Base32 format.</string>
<string name="error_otp_counter">Counter must be between %1$d and %2$d.</string> <string name="error_otp_counter">Counter must be between %1$d and %2$d.</string>
<string name="error_otp_period">Period must be between %1$d and %2$d seconds.</string> <string name="error_otp_period">Period must be between %1$d and %2$d seconds.</string>