Prepare hardware key in main credential

This commit is contained in:
J-Jamet
2022-02-26 12:51:00 +01:00
parent f79d32b22b
commit 2e0081b66c
8 changed files with 142 additions and 72 deletions

View File

@@ -42,8 +42,7 @@ open class AssignMainCredentialInDatabaseRunnable (
mBackupKey = ByteArray(database.masterKey.size)
System.arraycopy(database.masterKey, 0, mBackupKey!!, 0, mBackupKey!!.size)
val uriInputStream = UriUtil.getUriInputStream(context.contentResolver, mMainCredential.keyFileUri)
database.assignMasterKey(mMainCredential.masterPassword, uriInputStream)
database.assignMasterKey(context.contentResolver, mMainCredential)
} catch (e: Exception) {
erase(mBackupKey)
setError(e)

View File

@@ -604,6 +604,20 @@ class Database {
}
}
@Throws(Exception::class)
private fun getKeyFileData(contentResolver: ContentResolver, keyFileUri: Uri?): ByteArray? {
try {
keyFileUri?.let { uri ->
UriUtil.getUriInputStream(contentResolver, uri)?.use { keyFileInputStream ->
return keyFileInputStream.readBytes()
}
}
} catch (e: OutOfMemoryError) {
throw LoadDatabaseException("Keyfile too large")
}
return null
}
@Throws(LoadDatabaseException::class)
fun loadData(uri: Uri,
mainCredential: MainCredential,
@@ -620,14 +634,7 @@ class Database {
// Check if the file is writable
this.isReadOnly = readOnly
// Pass KeyFile Uri as InputStreams
var keyFileInputStream: InputStream? = null
try {
// Get keyFile inputStream
mainCredential.keyFileUri?.let { keyFile ->
keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyFile)
}
// Read database stream for the first time
readDatabaseStream(contentResolver, uri,
{ databaseInputStream ->
@@ -641,7 +648,8 @@ class Database {
) {
databaseKDB.retrieveMasterKey(
mainCredential.masterPassword,
keyFileInputStream
getKeyFileData(contentResolver, mainCredential.keyFileUri),
mainCredential.hardwareKeyData
)
}
databaseKDB
@@ -657,7 +665,8 @@ class Database {
progressTaskUpdater) {
databaseKDBX.retrieveMasterKey(
mainCredential.masterPassword,
keyFileInputStream,
getKeyFileData(contentResolver, mainCredential.keyFileUri),
mainCredential.hardwareKeyData
)
}
}
@@ -671,7 +680,6 @@ class Database {
} catch (e: Exception) {
throw LoadDatabaseException(e)
} finally {
keyFileInputStream?.close()
dataModifiedSinceLastLoading = false
}
}
@@ -695,18 +703,9 @@ class Database {
val databaseToMerge = Database()
databaseToMerge.fileUri = databaseToMergeUri ?: this.fileUri
// Pass KeyFile Uri as InputStreams
var keyFileInputStream: InputStream? = null
try {
val databaseUri = databaseToMerge.fileUri
if (databaseUri != null) {
if (databaseToMergeMainCredential != null) {
// Get keyFile inputStream
databaseToMergeMainCredential.keyFileUri?.let { keyFile ->
keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyFile)
}
}
databaseToMerge.readDatabaseStream(contentResolver, databaseUri,
{ databaseInputStream ->
val databaseToMergeKDB = DatabaseKDB()
@@ -715,7 +714,11 @@ class Database {
if (databaseToMergeMainCredential != null) {
databaseToMergeKDB.retrieveMasterKey(
databaseToMergeMainCredential.masterPassword,
keyFileInputStream,
getKeyFileData(
contentResolver,
databaseToMergeMainCredential.keyFileUri,
),
databaseToMergeMainCredential.hardwareKeyData
)
} else {
databaseToMergeKDB.masterKey = masterKey
@@ -731,7 +734,11 @@ class Database {
if (databaseToMergeMainCredential != null) {
databaseToMergeKDBX.retrieveMasterKey(
databaseToMergeMainCredential.masterPassword,
keyFileInputStream,
getKeyFileData(
contentResolver,
databaseToMergeMainCredential.keyFileUri
),
databaseToMergeMainCredential.hardwareKeyData
)
} else {
databaseToMergeKDBX.masterKey = masterKey
@@ -769,7 +776,6 @@ class Database {
} catch (e: Exception) {
throw LoadDatabaseException(e)
} finally {
keyFileInputStream?.close()
databaseToMerge.clearAndClose()
}
}
@@ -1024,9 +1030,19 @@ class Database {
}
@Throws(IOException::class)
fun assignMasterKey(key: String?, keyInputStream: InputStream?) {
mDatabaseKDB?.retrieveMasterKey(key, keyInputStream)
mDatabaseKDBX?.retrieveMasterKey(key, keyInputStream)
fun assignMasterKey(contentResolver: ContentResolver,
mainCredential: MainCredential) {
val keyFileData = getKeyFileData(contentResolver, mainCredential.keyFileUri)
mDatabaseKDB?.retrieveMasterKey(
mainCredential.masterPassword,
keyFileData,
mainCredential.hardwareKeyData
)
mDatabaseKDBX?.retrieveMasterKey(
mainCredential.masterPassword,
keyFileData,
mainCredential.hardwareKeyData
)
mDatabaseKDBX?.keyLastChanged = DateInstant()
}

View File

@@ -32,7 +32,6 @@ 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 java.io.IOException
import java.io.InputStream
import java.util.*
class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
@@ -117,14 +116,15 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
}
@Throws(IOException::class)
override fun getMasterKey(key: String?, keyInputStream: InputStream?): ByteArray {
return if (key != null && keyInputStream != null) {
getCompositeKey(key, keyInputStream)
} else if (key != null) { // key.length() >= 0
getPasswordKey(key)
} else if (keyInputStream != null) { // key == null
getFileKey(keyInputStream)
override fun getMasterKey(passwordKey: String?,
keyFileData: ByteArray?,
hardwareKey: ByteArray?): ByteArray {
return if (passwordKey != null && keyFileData != null) {
getCompositeKey(passwordKey, keyFileData, null) ?: byteArrayOf()
} else if (passwordKey != null) { // key.length() >= 0
getPasswordKey(passwordKey)
} else if (keyFileData != null) { // key == null
getFileKey(keyFileData)
} else {
throw IllegalArgumentException("Key cannot be empty.")
}

View File

@@ -529,19 +529,11 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
@Throws(IOException::class)
public override fun getMasterKey(key: String?, keyInputStream: InputStream?): ByteArray {
var masterKey = byteArrayOf()
if (key != null && keyInputStream != null) {
return getCompositeKey(key, keyInputStream)
} else if (key != null) { // key.length() >= 0
masterKey = getPasswordKey(key)
} else if (keyInputStream != null) { // key == null
masterKey = getFileKey(keyInputStream)
}
return HashManager.hashSha256(masterKey)
public override fun getMasterKey(passwordKey: String?,
keyFileData: ByteArray?,
hardwareKey: ByteArray?): ByteArray {
return getCompositeKey(passwordKey, keyFileData, hardwareKey)
?: HashManager.hashSha256(byteArrayOf())
}
@Throws(IOException::class)

View File

@@ -92,18 +92,30 @@ abstract class DatabaseVersioned<
}
@Throws(IOException::class)
protected abstract fun getMasterKey(key: String?, keyInputStream: InputStream?): ByteArray
protected abstract fun getMasterKey(passwordKey: String?,
keyFileData: ByteArray?,
hardwareKey: ByteArray?): ByteArray
@Throws(IOException::class)
fun retrieveMasterKey(key: String?, keyfileInputStream: InputStream?) {
masterKey = getMasterKey(key, keyfileInputStream)
fun retrieveMasterKey(key: String?,
keyFileData: ByteArray?,
hardwareKeyData: ByteArray?) {
masterKey = getMasterKey(key, keyFileData, hardwareKeyData)
}
@Throws(IOException::class)
protected fun getCompositeKey(key: String, keyfileInputStream: InputStream): ByteArray {
val fileKey = getFileKey(keyfileInputStream)
val passwordKey = getPasswordKey(key)
return HashManager.hashSha256(passwordKey, fileKey)
protected fun getCompositeKey(passwordKey: String?,
keyFileData: ByteArray?,
hardwareKeyData: ByteArray?): ByteArray? {
if (passwordKey == null && keyFileData == null && hardwareKeyData == null)
return null
val passwordBytes = if (passwordKey != null) getPasswordKey(passwordKey) else null
val keyFileBytes = if (keyFileData != null) getFileKey(keyFileData) else null
return HashManager.hashSha256(
passwordBytes,
keyFileBytes,
hardwareKeyData
)
}
@Throws(IOException::class)
@@ -117,10 +129,8 @@ abstract class DatabaseVersioned<
}
@Throws(IOException::class)
protected fun getFileKey(keyInputStream: InputStream): ByteArray {
protected fun getFileKey(keyData: ByteArray): ByteArray {
try {
val keyData = keyInputStream.readBytes()
// Check XML key file
val xmlKeyByteArray = loadXmlKeyFile(ByteArrayInputStream(keyData))
if (xmlKeyByteArray != null) {

View File

@@ -41,11 +41,6 @@ class CipherEncryptDatabase(): Parcelable {
parcel.readByteArray(specParameters)
}
fun replaceContent(copy: CipherEncryptDatabase) {
this.encryptedValue = copy.encryptedValue
this.specParameters = copy.specParameters
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeParcelable(databaseUri, flags)
parcel.writeEnum(credentialStorage)

View File

@@ -1,25 +1,81 @@
/*
* 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
data class MainCredential(var masterPassword: String? = null, var keyFileUri: Uri? = null): Parcelable {
data class MainCredential(var masterPassword: String? = null,
var keyFileUri: Uri? = null,
var hardwareKeyData: ByteArray? = null): Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readParcelable(Uri::class.java.classLoader)) {
constructor(parcel: Parcel) : this() {
masterPassword = parcel.readString()
keyFileUri = parcel.readParcelable(Uri::class.java.classLoader)
val hardwareKeyDataLength = parcel.readInt()
if (hardwareKeyDataLength >= 0) {
hardwareKeyData = ByteArray(hardwareKeyDataLength)
parcel.readByteArray(hardwareKeyData!!)
} else {
hardwareKeyData = null
}
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(masterPassword)
parcel.writeParcelable(keyFileUri, flags)
if (hardwareKeyData != null) {
parcel.writeInt(hardwareKeyData!!.size)
parcel.writeByteArray(hardwareKeyData)
} else {
parcel.writeInt(-1)
}
}
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 (masterPassword != other.masterPassword) return false
if (keyFileUri != other.keyFileUri) 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
return true
}
override fun hashCode(): Int {
var result = masterPassword?.hashCode() ?: 0
result = 31 * result + (keyFileUri?.hashCode() ?: 0)
result = 31 * result + (hardwareKeyData?.contentHashCode() ?: 0)
return result
}
companion object CREATOR : Parcelable.Creator<MainCredential> {
override fun createFromParcel(parcel: Parcel): MainCredential {
return MainCredential(parcel)

View File

@@ -39,9 +39,10 @@ object HashManager {
return messageDigest
}
fun hashSha256(vararg data: ByteArray): ByteArray {
fun hashSha256(vararg data: ByteArray?): ByteArray {
val hash: MessageDigest = getHash256()
for (byteArray in data) {
if (byteArray != null)
hash.update(byteArray)
}
return hash.digest()
@@ -57,9 +58,10 @@ object HashManager {
return messageDigest
}
private fun hashSha512(vararg data: ByteArray): ByteArray {
private fun hashSha512(vararg data: ByteArray?): ByteArray {
val hash: MessageDigest = getHash512()
for (byteArray in data) {
if (byteArray != null)
hash.update(byteArray)
}
return hash.digest()