mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Merge branch 'feature/OOM' into develop
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
app/src/main/java/com/kunzisoft/keepass/stream/ReadBytes.kt
Normal file
30
app/src/main/java/com/kunzisoft/keepass/stream/ReadBytes.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user