Better code encapsulation

This commit is contained in:
J-Jamet
2022-05-11 14:19:32 +02:00
parent 20e35f1a69
commit 20b352cabe
24 changed files with 378 additions and 386 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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() {

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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"
}
}

View File

@@ -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

View File

@@ -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"
}
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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)
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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,