mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Better code encapsulation
This commit is contained in:
@@ -55,7 +55,7 @@ import com.kunzisoft.keepass.autofill.AutofillComponent
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
import com.kunzisoft.keepass.model.RegisterInfo
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
|
||||
|
||||
@@ -69,7 +69,7 @@ import com.kunzisoft.keepass.database.search.SearchParameters
|
||||
import com.kunzisoft.keepass.education.GroupActivityEducation
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
|
||||
import com.kunzisoft.keepass.model.GroupInfo
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
import com.kunzisoft.keepass.model.RegisterInfo
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
|
||||
|
||||
@@ -57,6 +57,7 @@ import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment
|
||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
|
||||
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
||||
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
|
||||
import com.kunzisoft.keepass.education.PasswordActivityEducation
|
||||
|
||||
@@ -27,7 +27,7 @@ import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.view.MainCredentialView
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import android.os.Bundle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
|
||||
class PasswordEncodingDialogFragment : DialogFragment() {
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
|
||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||
import com.kunzisoft.keepass.hardware.HardwareKeyResponseHelper
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
import com.kunzisoft.keepass.password.PasswordEntropy
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.view.HardwareKeySelectionView
|
||||
|
||||
@@ -7,7 +7,7 @@ import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ import com.kunzisoft.keepass.database.element.node.Node
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.icons.IconDrawableFactory
|
||||
import com.kunzisoft.keepass.model.GroupInfo
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
|
||||
@@ -25,14 +25,14 @@ import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
|
||||
open class AssignMainCredentialInDatabaseRunnable (
|
||||
context: Context,
|
||||
database: Database,
|
||||
protected val mDatabaseUri: Uri,
|
||||
mainCredential: MainCredential,
|
||||
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
|
||||
context: Context,
|
||||
database: Database,
|
||||
protected val mDatabaseUri: Uri,
|
||||
mainCredential: MainCredential,
|
||||
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
|
||||
: SaveDatabaseRunnable(context, database, true, mainCredential, challengeResponseRetriever) {
|
||||
|
||||
private var mBackupKey: ByteArray? = null
|
||||
|
||||
@@ -25,7 +25,7 @@ import android.util.Log
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
|
||||
class CreateDatabaseRunnable(context: Context,
|
||||
|
||||
@@ -46,7 +46,7 @@ import com.kunzisoft.keepass.database.exception.InvalidCredentialsDatabaseExcept
|
||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||
import com.kunzisoft.keepass.hardware.HardwareKeyResponseHelper
|
||||
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK
|
||||
@@ -394,7 +394,8 @@ class DatabaseTaskProvider {
|
||||
*/
|
||||
|
||||
fun startDatabaseCreate(databaseUri: Uri,
|
||||
mainCredential: MainCredential) {
|
||||
mainCredential: MainCredential
|
||||
) {
|
||||
start(Bundle().apply {
|
||||
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
|
||||
putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential)
|
||||
@@ -447,7 +448,8 @@ class DatabaseTaskProvider {
|
||||
}
|
||||
|
||||
fun startDatabaseAssignPassword(databaseUri: Uri,
|
||||
mainCredential: MainCredential) {
|
||||
mainCredential: MainCredential
|
||||
) {
|
||||
|
||||
start(Bundle().apply {
|
||||
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
|
||||
|
||||
@@ -28,7 +28,7 @@ import com.kunzisoft.keepass.database.element.binary.BinaryData
|
||||
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
|
||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||
|
||||
@@ -25,7 +25,7 @@ import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.binary.BinaryData
|
||||
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
|
||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||
|
||||
@@ -24,7 +24,7 @@ import android.net.Uri
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.exception.DatabaseException
|
||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
|
||||
open class SaveDatabaseRunnable(protected var context: Context,
|
||||
|
||||
@@ -1,28 +1,10 @@
|
||||
package com.kunzisoft.keepass.database.element
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.net.Uri
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import com.kunzisoft.encrypt.HashManager
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
||||
import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
|
||||
import com.kunzisoft.keepass.utils.StringUtil.toHexString
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import org.apache.commons.codec.binary.Hex
|
||||
import org.w3c.dom.Node
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.nio.charset.Charset
|
||||
import javax.xml.XMLConstants
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.parsers.ParserConfigurationException
|
||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||
|
||||
data class CompositeKey(var passwordData: ByteArray? = null,
|
||||
var keyFileData: ByteArray? = null,
|
||||
var hardwareKeyData: ByteArray? = null) {
|
||||
var hardwareKey: HardwareKey? = null) {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
@@ -38,10 +20,7 @@ data class CompositeKey(var passwordData: ByteArray? = null,
|
||||
if (other.keyFileData == null) return false
|
||||
if (!keyFileData.contentEquals(other.keyFileData)) return false
|
||||
} else if (other.keyFileData != null) return false
|
||||
if (hardwareKeyData != null) {
|
||||
if (other.hardwareKeyData == null) return false
|
||||
if (!hardwareKeyData.contentEquals(other.hardwareKeyData)) return false
|
||||
} else if (other.hardwareKeyData != null) return false
|
||||
if (hardwareKey != other.hardwareKey) return false
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -49,189 +28,7 @@ data class CompositeKey(var passwordData: ByteArray? = null,
|
||||
override fun hashCode(): Int {
|
||||
var result = passwordData?.contentHashCode() ?: 0
|
||||
result = 31 * result + (keyFileData?.contentHashCode() ?: 0)
|
||||
result = 31 * result + (hardwareKeyData?.contentHashCode() ?: 0)
|
||||
result = 31 * result + (hardwareKey?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = CompositeKey::class.java.simpleName
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun retrievePasswordKey(key: String,
|
||||
encoding: Charset): ByteArray {
|
||||
val bKey: ByteArray = try {
|
||||
key.toByteArray(encoding)
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
key.toByteArray()
|
||||
}
|
||||
return HashManager.hashSha256(bKey)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun retrieveFileKey(contentResolver: ContentResolver,
|
||||
keyFileUri: Uri?,
|
||||
allowXML: Boolean): ByteArray {
|
||||
if (keyFileUri == null)
|
||||
throw IOException("Keyfile URI is null")
|
||||
val keyData = getKeyFileData(contentResolver, keyFileUri)
|
||||
?: throw IOException("No data retrieved")
|
||||
try {
|
||||
// Check XML key file
|
||||
val xmlKeyByteArray = if (allowXML)
|
||||
loadXmlKeyFile(ByteArrayInputStream(keyData))
|
||||
else
|
||||
null
|
||||
if (xmlKeyByteArray != null) {
|
||||
return xmlKeyByteArray
|
||||
}
|
||||
|
||||
// Check 32 bytes key file
|
||||
when (keyData.size) {
|
||||
32 -> return keyData
|
||||
64 -> try {
|
||||
return Hex.decodeHex(String(keyData).toCharArray())
|
||||
} catch (ignoredException: Exception) {
|
||||
// Key is not base 64, treat it as binary data
|
||||
}
|
||||
}
|
||||
// Hash file as binary data
|
||||
return HashManager.hashSha256(keyData)
|
||||
} catch (e: Exception) {
|
||||
throw IOException("Unable to load the keyfile.", e)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun retrieveHardwareKey(keyData: ByteArray): ByteArray {
|
||||
return HashManager.hashSha256(keyData)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun getKeyFileData(contentResolver: ContentResolver,
|
||||
keyFileUri: Uri): ByteArray? {
|
||||
UriUtil.getUriInputStream(contentResolver, keyFileUri)?.use { keyFileInputStream ->
|
||||
return keyFileInputStream.readBytes()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? {
|
||||
try {
|
||||
val documentBuilderFactory = DocumentBuilderFactory.newInstance()
|
||||
|
||||
// Disable certain unsecure XML-Parsing DocumentBuilderFactory features
|
||||
try {
|
||||
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
|
||||
} catch (e : ParserConfigurationException) {
|
||||
Log.w(TAG, "Unable to add FEATURE_SECURE_PROCESSING to prevent XML eXternal Entity injection (XXE)")
|
||||
}
|
||||
|
||||
val documentBuilder = documentBuilderFactory.newDocumentBuilder()
|
||||
val doc = documentBuilder.parse(keyInputStream)
|
||||
|
||||
var xmlKeyFileVersion = 1F
|
||||
|
||||
val docElement = doc.documentElement
|
||||
val keyFileChildNodes = docElement.childNodes
|
||||
// <KeyFile> Root node
|
||||
if (docElement == null
|
||||
|| !docElement.nodeName.equals(XML_NODE_ROOT_NAME, ignoreCase = true)) {
|
||||
return null
|
||||
}
|
||||
if (keyFileChildNodes.length < 2)
|
||||
return null
|
||||
for (keyFileChildPosition in 0 until keyFileChildNodes.length) {
|
||||
val keyFileChildNode = keyFileChildNodes.item(keyFileChildPosition)
|
||||
// <Meta>
|
||||
if (keyFileChildNode.nodeName.equals(XML_NODE_META_NAME, ignoreCase = true)) {
|
||||
val metaChildNodes = keyFileChildNode.childNodes
|
||||
for (metaChildPosition in 0 until metaChildNodes.length) {
|
||||
val metaChildNode = metaChildNodes.item(metaChildPosition)
|
||||
// <Version>
|
||||
if (metaChildNode.nodeName.equals(XML_NODE_VERSION_NAME, ignoreCase = true)) {
|
||||
val versionChildNodes = metaChildNode.childNodes
|
||||
for (versionChildPosition in 0 until versionChildNodes.length) {
|
||||
val versionChildNode = versionChildNodes.item(versionChildPosition)
|
||||
if (versionChildNode.nodeType == Node.TEXT_NODE) {
|
||||
val versionText = versionChildNode.textContent.removeSpaceChars()
|
||||
try {
|
||||
xmlKeyFileVersion = versionText.toFloat()
|
||||
Log.i(TAG, "Reading XML KeyFile version : $xmlKeyFileVersion")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "XML Keyfile version cannot be read : $versionText")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// <Key>
|
||||
if (keyFileChildNode.nodeName.equals(XML_NODE_KEY_NAME, ignoreCase = true)) {
|
||||
val keyChildNodes = keyFileChildNode.childNodes
|
||||
for (keyChildPosition in 0 until keyChildNodes.length) {
|
||||
val keyChildNode = keyChildNodes.item(keyChildPosition)
|
||||
// <Data>
|
||||
if (keyChildNode.nodeName.equals(XML_NODE_DATA_NAME, ignoreCase = true)) {
|
||||
var hashString : String? = null
|
||||
if (keyChildNode.hasAttributes()) {
|
||||
val dataNodeAttributes = keyChildNode.attributes
|
||||
hashString = dataNodeAttributes
|
||||
.getNamedItem(XML_ATTRIBUTE_DATA_HASH).nodeValue
|
||||
}
|
||||
val dataChildNodes = keyChildNode.childNodes
|
||||
for (dataChildPosition in 0 until dataChildNodes.length) {
|
||||
val dataChildNode = dataChildNodes.item(dataChildPosition)
|
||||
if (dataChildNode.nodeType == Node.TEXT_NODE) {
|
||||
val dataString = dataChildNode.textContent.removeSpaceChars()
|
||||
when (xmlKeyFileVersion) {
|
||||
1F -> {
|
||||
// No hash in KeyFile XML version 1
|
||||
return Base64.decode(dataString,
|
||||
DatabaseKDBX.BASE_64_FLAG
|
||||
)
|
||||
}
|
||||
2F -> {
|
||||
return if (hashString != null
|
||||
&& checkKeyFileHash(dataString, hashString)) {
|
||||
Log.i(TAG, "Successful key file hash check.")
|
||||
Hex.decodeHex(dataString.toCharArray())
|
||||
} else {
|
||||
Log.e(TAG, "Unable to check the hash of the key file.")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun checkKeyFileHash(data: String, hash: String): Boolean {
|
||||
var success = false
|
||||
try {
|
||||
// hexadecimal encoding of the first 4 bytes of the SHA-256 hash of the key.
|
||||
val dataDigest = HashManager.hashSha256(Hex.decodeHex(data.toCharArray()))
|
||||
.copyOfRange(0, 4).toHexString()
|
||||
success = dataDigest == hash
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
private const val XML_NODE_ROOT_NAME = "KeyFile"
|
||||
private const val XML_NODE_META_NAME = "Meta"
|
||||
private const val XML_NODE_VERSION_NAME = "Version"
|
||||
private const val XML_NODE_KEY_NAME = "Key"
|
||||
private const val XML_NODE_DATA_NAME = "Data"
|
||||
private const val XML_ATTRIBUTE_DATA_HASH = "Hash"
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,6 @@ import com.kunzisoft.keepass.database.search.SearchHelper
|
||||
import com.kunzisoft.keepass.database.search.SearchParameters
|
||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||
import com.kunzisoft.keepass.icons.IconDrawableFactory
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||
import com.kunzisoft.keepass.utils.SingletonHolder
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* Copyright 2022 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.element
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.net.Uri
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import com.kunzisoft.encrypt.HashManager
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||
import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
|
||||
import com.kunzisoft.keepass.utils.StringUtil.toHexString
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.utils.readEnum
|
||||
import com.kunzisoft.keepass.utils.writeEnum
|
||||
import org.apache.commons.codec.binary.Hex
|
||||
import org.w3c.dom.Node
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.nio.charset.Charset
|
||||
import javax.xml.XMLConstants
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.parsers.ParserConfigurationException
|
||||
|
||||
data class MainCredential(var password: String? = null,
|
||||
var keyFileUri: Uri? = null,
|
||||
var hardwareKey: HardwareKey? = null): Parcelable {
|
||||
|
||||
constructor(parcel: Parcel) : this() {
|
||||
password = parcel.readString()
|
||||
keyFileUri = parcel.readParcelable(Uri::class.java.classLoader)
|
||||
hardwareKey = parcel.readEnum<HardwareKey>()
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeString(password)
|
||||
parcel.writeParcelable(keyFileUri, flags)
|
||||
parcel.writeEnum(hardwareKey)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as MainCredential
|
||||
|
||||
if (password != other.password) return false
|
||||
if (keyFileUri != other.keyFileUri) return false
|
||||
if (hardwareKey != other.hardwareKey) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = password?.hashCode() ?: 0
|
||||
result = 31 * result + (keyFileUri?.hashCode() ?: 0)
|
||||
result = 31 * result + (hardwareKey?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<MainCredential> {
|
||||
override fun createFromParcel(parcel: Parcel): MainCredential {
|
||||
return MainCredential(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<MainCredential?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
|
||||
private val TAG = MainCredential::class.java.simpleName
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun retrievePasswordKey(key: String,
|
||||
encoding: Charset
|
||||
): ByteArray {
|
||||
val bKey: ByteArray = try {
|
||||
key.toByteArray(encoding)
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
key.toByteArray()
|
||||
}
|
||||
return HashManager.hashSha256(bKey)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun retrieveFileKey(contentResolver: ContentResolver,
|
||||
keyFileUri: Uri?,
|
||||
allowXML: Boolean): ByteArray {
|
||||
if (keyFileUri == null)
|
||||
throw IOException("Keyfile URI is null")
|
||||
val keyData = getKeyFileData(contentResolver, keyFileUri)
|
||||
?: throw IOException("No data retrieved")
|
||||
try {
|
||||
// Check XML key file
|
||||
val xmlKeyByteArray = if (allowXML)
|
||||
loadXmlKeyFile(ByteArrayInputStream(keyData))
|
||||
else
|
||||
null
|
||||
if (xmlKeyByteArray != null) {
|
||||
return xmlKeyByteArray
|
||||
}
|
||||
|
||||
// Check 32 bytes key file
|
||||
when (keyData.size) {
|
||||
32 -> return keyData
|
||||
64 -> try {
|
||||
return Hex.decodeHex(String(keyData).toCharArray())
|
||||
} catch (ignoredException: Exception) {
|
||||
// Key is not base 64, treat it as binary data
|
||||
}
|
||||
}
|
||||
// Hash file as binary data
|
||||
return HashManager.hashSha256(keyData)
|
||||
} catch (e: Exception) {
|
||||
throw IOException("Unable to load the keyfile.", e)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun retrieveHardwareKey(keyData: ByteArray): ByteArray {
|
||||
return HashManager.hashSha256(keyData)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun getKeyFileData(contentResolver: ContentResolver,
|
||||
keyFileUri: Uri): ByteArray? {
|
||||
UriUtil.getUriInputStream(contentResolver, keyFileUri)?.use { keyFileInputStream ->
|
||||
return keyFileInputStream.readBytes()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? {
|
||||
try {
|
||||
val documentBuilderFactory = DocumentBuilderFactory.newInstance()
|
||||
|
||||
// Disable certain unsecure XML-Parsing DocumentBuilderFactory features
|
||||
try {
|
||||
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
|
||||
} catch (e : ParserConfigurationException) {
|
||||
Log.w(TAG, "Unable to add FEATURE_SECURE_PROCESSING to prevent XML eXternal Entity injection (XXE)")
|
||||
}
|
||||
|
||||
val documentBuilder = documentBuilderFactory.newDocumentBuilder()
|
||||
val doc = documentBuilder.parse(keyInputStream)
|
||||
|
||||
var xmlKeyFileVersion = 1F
|
||||
|
||||
val docElement = doc.documentElement
|
||||
val keyFileChildNodes = docElement.childNodes
|
||||
// <KeyFile> Root node
|
||||
if (docElement == null
|
||||
|| !docElement.nodeName.equals(XML_NODE_ROOT_NAME, ignoreCase = true)) {
|
||||
return null
|
||||
}
|
||||
if (keyFileChildNodes.length < 2)
|
||||
return null
|
||||
for (keyFileChildPosition in 0 until keyFileChildNodes.length) {
|
||||
val keyFileChildNode = keyFileChildNodes.item(keyFileChildPosition)
|
||||
// <Meta>
|
||||
if (keyFileChildNode.nodeName.equals(XML_NODE_META_NAME, ignoreCase = true)) {
|
||||
val metaChildNodes = keyFileChildNode.childNodes
|
||||
for (metaChildPosition in 0 until metaChildNodes.length) {
|
||||
val metaChildNode = metaChildNodes.item(metaChildPosition)
|
||||
// <Version>
|
||||
if (metaChildNode.nodeName.equals(XML_NODE_VERSION_NAME, ignoreCase = true)) {
|
||||
val versionChildNodes = metaChildNode.childNodes
|
||||
for (versionChildPosition in 0 until versionChildNodes.length) {
|
||||
val versionChildNode = versionChildNodes.item(versionChildPosition)
|
||||
if (versionChildNode.nodeType == Node.TEXT_NODE) {
|
||||
val versionText = versionChildNode.textContent.removeSpaceChars()
|
||||
try {
|
||||
xmlKeyFileVersion = versionText.toFloat()
|
||||
Log.i(TAG, "Reading XML KeyFile version : $xmlKeyFileVersion")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "XML Keyfile version cannot be read : $versionText")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// <Key>
|
||||
if (keyFileChildNode.nodeName.equals(XML_NODE_KEY_NAME, ignoreCase = true)) {
|
||||
val keyChildNodes = keyFileChildNode.childNodes
|
||||
for (keyChildPosition in 0 until keyChildNodes.length) {
|
||||
val keyChildNode = keyChildNodes.item(keyChildPosition)
|
||||
// <Data>
|
||||
if (keyChildNode.nodeName.equals(XML_NODE_DATA_NAME, ignoreCase = true)) {
|
||||
var hashString : String? = null
|
||||
if (keyChildNode.hasAttributes()) {
|
||||
val dataNodeAttributes = keyChildNode.attributes
|
||||
hashString = dataNodeAttributes
|
||||
.getNamedItem(XML_ATTRIBUTE_DATA_HASH).nodeValue
|
||||
}
|
||||
val dataChildNodes = keyChildNode.childNodes
|
||||
for (dataChildPosition in 0 until dataChildNodes.length) {
|
||||
val dataChildNode = dataChildNodes.item(dataChildPosition)
|
||||
if (dataChildNode.nodeType == Node.TEXT_NODE) {
|
||||
val dataString = dataChildNode.textContent.removeSpaceChars()
|
||||
when (xmlKeyFileVersion) {
|
||||
1F -> {
|
||||
// No hash in KeyFile XML version 1
|
||||
return Base64.decode(dataString,
|
||||
DatabaseKDBX.BASE_64_FLAG
|
||||
)
|
||||
}
|
||||
2F -> {
|
||||
return if (hashString != null
|
||||
&& checkKeyFileHash(dataString, hashString)
|
||||
) {
|
||||
Log.i(TAG, "Successful key file hash check.")
|
||||
Hex.decodeHex(dataString.toCharArray())
|
||||
} else {
|
||||
Log.e(TAG, "Unable to check the hash of the key file.")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun checkKeyFileHash(data: String, hash: String): Boolean {
|
||||
var success = false
|
||||
try {
|
||||
// hexadecimal encoding of the first 4 bytes of the SHA-256 hash of the key.
|
||||
val dataDigest = HashManager.hashSha256(Hex.decodeHex(data.toCharArray()))
|
||||
.copyOfRange(0, 4).toHexString()
|
||||
success = dataDigest == hash
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
private const val XML_NODE_ROOT_NAME = "KeyFile"
|
||||
private const val XML_NODE_META_NAME = "Meta"
|
||||
private const val XML_NODE_VERSION_NAME = "Version"
|
||||
private const val XML_NODE_KEY_NAME = "Key"
|
||||
private const val XML_NODE_DATA_NAME = "Data"
|
||||
private const val XML_ATTRIBUTE_DATA_HASH = "Hash"
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import com.kunzisoft.encrypt.aes.AESTransformer
|
||||
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
|
||||
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
|
||||
import com.kunzisoft.keepass.database.element.CompositeKey
|
||||
import com.kunzisoft.keepass.database.element.binary.BinaryData
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDB
|
||||
import com.kunzisoft.keepass.database.element.group.GroupKDB
|
||||
@@ -33,7 +32,7 @@ import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
import com.kunzisoft.keepass.database.element.node.NodeIdInt
|
||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||
import com.kunzisoft.keepass.database.element.node.NodeVersioned
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
import java.io.IOException
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
@@ -131,37 +130,30 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
contentResolver: ContentResolver,
|
||||
mainCredential: MainCredential
|
||||
) {
|
||||
// Exception when no password
|
||||
if (mainCredential.password == null && mainCredential.keyFileUri == null)
|
||||
throw IllegalArgumentException("Key cannot be empty.")
|
||||
if (mainCredential.hardwareKey != null)
|
||||
throw IllegalArgumentException("Hardware key is not supported.")
|
||||
this.masterKey = compositeKeyToMasterKey(retrieveCompositeKey(
|
||||
contentResolver,
|
||||
mainCredential
|
||||
))
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun retrieveCompositeKey(contentResolver: ContentResolver,
|
||||
mainCredential: MainCredential
|
||||
): CompositeKey {
|
||||
// Save to rebuild master password with new seed later
|
||||
mMainCredential = mainCredential
|
||||
// Retrieve plain data
|
||||
val password = mainCredential.password
|
||||
val keyFileUri = mainCredential.keyFileUri
|
||||
val passwordBytes = if (password != null) CompositeKey.retrievePasswordKey(
|
||||
val passwordBytes = if (password != null) MainCredential.retrievePasswordKey(
|
||||
password,
|
||||
passwordEncoding
|
||||
) else null
|
||||
val keyFileBytes = if (keyFileUri != null) CompositeKey.retrieveFileKey(
|
||||
val keyFileBytes = if (keyFileUri != null) MainCredential.retrieveFileKey(
|
||||
contentResolver,
|
||||
keyFileUri,
|
||||
false
|
||||
) else null
|
||||
val compositeKey = CompositeKey(passwordBytes, keyFileBytes)
|
||||
// Save to rebuild master password with new seed later
|
||||
mCompositeKey = compositeKey
|
||||
return compositeKey
|
||||
|
||||
// Build master key
|
||||
this.masterKey = HashManager.hashSha256(
|
||||
passwordBytes,
|
||||
keyFileBytes
|
||||
)
|
||||
}
|
||||
|
||||
override fun createGroup(): GroupKDB {
|
||||
|
||||
@@ -52,7 +52,7 @@ import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VER
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_40
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_41
|
||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||
import com.kunzisoft.keepass.utils.longTo8Bytes
|
||||
import java.io.IOException
|
||||
@@ -66,6 +66,9 @@ import kotlin.math.min
|
||||
|
||||
class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
|
||||
// To resave the database with same credential when already loaded
|
||||
private var mCompositeKey = CompositeKey()
|
||||
|
||||
var hmacKey: ByteArray? = null
|
||||
private set
|
||||
|
||||
@@ -231,56 +234,72 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
mainCredential: MainCredential,
|
||||
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray
|
||||
) {
|
||||
this.masterKey = compositeKeyToMasterKey(retrieveCompositeKey(
|
||||
// Retrieve each plain credential
|
||||
val password = mainCredential.password
|
||||
val keyFileUri = mainCredential.keyFileUri
|
||||
val hardwareKey = mainCredential.hardwareKey
|
||||
val passwordBytes = if (password != null) MainCredential.retrievePasswordKey(
|
||||
password,
|
||||
passwordEncoding
|
||||
) else null
|
||||
val keyFileBytes = if (keyFileUri != null) MainCredential.retrieveFileKey(
|
||||
contentResolver,
|
||||
mainCredential,
|
||||
transformSeed,
|
||||
challengeResponseRetriever
|
||||
))
|
||||
keyFileUri,
|
||||
true
|
||||
) else null
|
||||
val hardwareKeyBytes = if (hardwareKey != null) MainCredential.retrieveHardwareKey(
|
||||
challengeResponseRetriever.invoke(hardwareKey, transformSeed)
|
||||
) else null
|
||||
|
||||
// Save to rebuild master password with new seed later
|
||||
mCompositeKey = CompositeKey(passwordBytes, keyFileBytes, hardwareKey)
|
||||
|
||||
// Build the master key
|
||||
this.masterKey = composedKeyToMasterKey(
|
||||
passwordBytes,
|
||||
keyFileBytes,
|
||||
hardwareKeyBytes
|
||||
)
|
||||
}
|
||||
|
||||
@Throws(DatabaseOutputException::class)
|
||||
fun deriveCompositeKey(
|
||||
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray
|
||||
) {
|
||||
if (mCompositeKey.hardwareKeyData == null)
|
||||
this.masterKey = compositeKeyToMasterKey(mCompositeKey)
|
||||
val hardwareKey = mMainCredential.hardwareKey
|
||||
val hardwareKeyBytes = if (hardwareKey != null) CompositeKey.retrieveHardwareKey(
|
||||
challengeResponseRetriever.invoke(hardwareKey, transformSeed)
|
||||
) else null
|
||||
this.masterKey = compositeKeyToMasterKey(mCompositeKey.apply {
|
||||
this.hardwareKeyData = hardwareKeyBytes
|
||||
})
|
||||
val passwordBytes = mCompositeKey.passwordData
|
||||
val keyFileBytes = mCompositeKey.keyFileData
|
||||
val hardwareKey = mCompositeKey.hardwareKey
|
||||
if (hardwareKey == null) {
|
||||
// If no hardware key, simply rebuild from composed keys
|
||||
this.masterKey = composedKeyToMasterKey(
|
||||
passwordBytes,
|
||||
keyFileBytes
|
||||
)
|
||||
} else {
|
||||
val hardwareKeyBytes = MainCredential.retrieveHardwareKey(
|
||||
challengeResponseRetriever.invoke(hardwareKey, transformSeed)
|
||||
)
|
||||
this.masterKey = composedKeyToMasterKey(
|
||||
passwordBytes,
|
||||
keyFileBytes,
|
||||
hardwareKeyBytes
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun retrieveCompositeKey(contentResolver: ContentResolver,
|
||||
mainCredential: MainCredential,
|
||||
transformSeed: ByteArray?,
|
||||
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray,
|
||||
): CompositeKey {
|
||||
// Save to rebuild master password with new seed later
|
||||
mMainCredential = mainCredential
|
||||
val password = mainCredential.password
|
||||
val keyFileUri = mainCredential.keyFileUri
|
||||
val hardwareKey = mainCredential.hardwareKey
|
||||
val passwordBytes = if (password != null) CompositeKey.retrievePasswordKey(
|
||||
password,
|
||||
passwordEncoding
|
||||
) else null
|
||||
val keyFileBytes = if (keyFileUri != null) CompositeKey.retrieveFileKey(
|
||||
contentResolver,
|
||||
keyFileUri,
|
||||
true
|
||||
) else null
|
||||
val hardwareKeyBytes = if (hardwareKey != null) CompositeKey.retrieveHardwareKey(
|
||||
challengeResponseRetriever.invoke(hardwareKey, transformSeed)
|
||||
) else null
|
||||
val compositeKey = CompositeKey(passwordBytes, keyFileBytes, hardwareKeyBytes)
|
||||
// Save to rebuild master password with new seed later
|
||||
mCompositeKey = compositeKey
|
||||
return compositeKey
|
||||
private fun composedKeyToMasterKey(passwordData: ByteArray?,
|
||||
keyFileData: ByteArray?,
|
||||
hardwareKeyData: ByteArray? = null): ByteArray {
|
||||
return HashManager.hashSha256(
|
||||
passwordData,
|
||||
keyFileData,
|
||||
hardwareKeyData
|
||||
)
|
||||
}
|
||||
|
||||
fun copyMasterKeyFrom(databaseVersioned: DatabaseKDBX) {
|
||||
super.copyMasterKeyFrom(databaseVersioned)
|
||||
this.mCompositeKey = databaseVersioned.mCompositeKey
|
||||
}
|
||||
|
||||
fun getMinKdbxVersion(): UnsignedInt {
|
||||
|
||||
@@ -20,10 +20,8 @@
|
||||
package com.kunzisoft.keepass.database.element.database
|
||||
|
||||
import android.util.Log
|
||||
import com.kunzisoft.encrypt.HashManager
|
||||
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
|
||||
import com.kunzisoft.keepass.database.element.CompositeKey
|
||||
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
|
||||
import com.kunzisoft.keepass.database.element.binary.BinaryCache
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
|
||||
@@ -33,7 +31,6 @@ import com.kunzisoft.keepass.database.element.icon.IconsManager
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.node.Type
|
||||
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import java.io.InputStream
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.nio.charset.Charset
|
||||
@@ -59,10 +56,6 @@ abstract class DatabaseVersioned<
|
||||
var masterKey = ByteArray(32)
|
||||
var finalKey: ByteArray? = null
|
||||
protected set
|
||||
|
||||
// To resave the database with same credential when already loaded
|
||||
protected var mMainCredential = MainCredential()
|
||||
protected var mCompositeKey = CompositeKey()
|
||||
var transformSeed: ByteArray? = null
|
||||
|
||||
abstract val version: String
|
||||
@@ -126,19 +119,9 @@ abstract class DatabaseVersioned<
|
||||
|
||||
fun copyMasterKeyFrom(databaseVersioned: DatabaseVersioned<GroupId, EntryId, Group, Entry>) {
|
||||
this.masterKey = databaseVersioned.masterKey
|
||||
this.mMainCredential = databaseVersioned.mMainCredential
|
||||
this.mCompositeKey = databaseVersioned.mCompositeKey
|
||||
this.transformSeed = databaseVersioned.transformSeed
|
||||
}
|
||||
|
||||
protected fun compositeKeyToMasterKey(compositeKey: CompositeKey): ByteArray {
|
||||
return HashManager.hashSha256(
|
||||
compositeKey.passwordData,
|
||||
compositeKey.keyFileData,
|
||||
compositeKey.hardwareKeyData
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
* -------------------------------------
|
||||
* Node Creation
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.kunzisoft.keepass.model
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||
import com.kunzisoft.keepass.utils.readEnum
|
||||
import com.kunzisoft.keepass.utils.writeEnum
|
||||
|
||||
data class MainCredential(var password: String? = null,
|
||||
var keyFileUri: Uri? = null,
|
||||
var hardwareKey: HardwareKey? = null): Parcelable {
|
||||
|
||||
constructor(parcel: Parcel) : this() {
|
||||
password = parcel.readString()
|
||||
keyFileUri = parcel.readParcelable(Uri::class.java.classLoader)
|
||||
hardwareKey = parcel.readEnum<HardwareKey>()
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeString(password)
|
||||
parcel.writeParcelable(keyFileUri, flags)
|
||||
parcel.writeEnum(hardwareKey)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as MainCredential
|
||||
|
||||
if (password != other.password) return false
|
||||
if (keyFileUri != other.keyFileUri) return false
|
||||
if (hardwareKey != other.hardwareKey) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = password?.hashCode() ?: 0
|
||||
result = 31 * result + (keyFileUri?.hashCode() ?: 0)
|
||||
result = 31 * result + (hardwareKey?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<MainCredential> {
|
||||
override fun createFromParcel(parcel: Parcel): MainCredential {
|
||||
return MainCredential(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<MainCredential?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.node.Type
|
||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||
|
||||
@@ -38,7 +38,7 @@ import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
|
||||
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.hardware.HardwareKeyResponseHelper
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
|
||||
|
||||
@@ -39,7 +39,7 @@ import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
|
||||
import com.kunzisoft.keepass.model.CredentialStorage
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.database.element.MainCredential
|
||||
|
||||
class MainCredentialView @JvmOverloads constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
|
||||
Reference in New Issue
Block a user