mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Prepare hardware key in main credential
This commit is contained in:
@@ -42,8 +42,7 @@ open class AssignMainCredentialInDatabaseRunnable (
|
|||||||
mBackupKey = ByteArray(database.masterKey.size)
|
mBackupKey = ByteArray(database.masterKey.size)
|
||||||
System.arraycopy(database.masterKey, 0, mBackupKey!!, 0, mBackupKey!!.size)
|
System.arraycopy(database.masterKey, 0, mBackupKey!!, 0, mBackupKey!!.size)
|
||||||
|
|
||||||
val uriInputStream = UriUtil.getUriInputStream(context.contentResolver, mMainCredential.keyFileUri)
|
database.assignMasterKey(context.contentResolver, mMainCredential)
|
||||||
database.assignMasterKey(mMainCredential.masterPassword, uriInputStream)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
erase(mBackupKey)
|
erase(mBackupKey)
|
||||||
setError(e)
|
setError(e)
|
||||||
|
|||||||
@@ -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)
|
@Throws(LoadDatabaseException::class)
|
||||||
fun loadData(uri: Uri,
|
fun loadData(uri: Uri,
|
||||||
mainCredential: MainCredential,
|
mainCredential: MainCredential,
|
||||||
@@ -620,14 +634,7 @@ class Database {
|
|||||||
// Check if the file is writable
|
// Check if the file is writable
|
||||||
this.isReadOnly = readOnly
|
this.isReadOnly = readOnly
|
||||||
|
|
||||||
// Pass KeyFile Uri as InputStreams
|
|
||||||
var keyFileInputStream: InputStream? = null
|
|
||||||
try {
|
try {
|
||||||
// Get keyFile inputStream
|
|
||||||
mainCredential.keyFileUri?.let { keyFile ->
|
|
||||||
keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read database stream for the first time
|
// Read database stream for the first time
|
||||||
readDatabaseStream(contentResolver, uri,
|
readDatabaseStream(contentResolver, uri,
|
||||||
{ databaseInputStream ->
|
{ databaseInputStream ->
|
||||||
@@ -641,7 +648,8 @@ class Database {
|
|||||||
) {
|
) {
|
||||||
databaseKDB.retrieveMasterKey(
|
databaseKDB.retrieveMasterKey(
|
||||||
mainCredential.masterPassword,
|
mainCredential.masterPassword,
|
||||||
keyFileInputStream
|
getKeyFileData(contentResolver, mainCredential.keyFileUri),
|
||||||
|
mainCredential.hardwareKeyData
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
databaseKDB
|
databaseKDB
|
||||||
@@ -657,7 +665,8 @@ class Database {
|
|||||||
progressTaskUpdater) {
|
progressTaskUpdater) {
|
||||||
databaseKDBX.retrieveMasterKey(
|
databaseKDBX.retrieveMasterKey(
|
||||||
mainCredential.masterPassword,
|
mainCredential.masterPassword,
|
||||||
keyFileInputStream,
|
getKeyFileData(contentResolver, mainCredential.keyFileUri),
|
||||||
|
mainCredential.hardwareKeyData
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -671,7 +680,6 @@ class Database {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw LoadDatabaseException(e)
|
throw LoadDatabaseException(e)
|
||||||
} finally {
|
} finally {
|
||||||
keyFileInputStream?.close()
|
|
||||||
dataModifiedSinceLastLoading = false
|
dataModifiedSinceLastLoading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -695,18 +703,9 @@ class Database {
|
|||||||
val databaseToMerge = Database()
|
val databaseToMerge = Database()
|
||||||
databaseToMerge.fileUri = databaseToMergeUri ?: this.fileUri
|
databaseToMerge.fileUri = databaseToMergeUri ?: this.fileUri
|
||||||
|
|
||||||
// Pass KeyFile Uri as InputStreams
|
|
||||||
var keyFileInputStream: InputStream? = null
|
|
||||||
try {
|
try {
|
||||||
val databaseUri = databaseToMerge.fileUri
|
val databaseUri = databaseToMerge.fileUri
|
||||||
if (databaseUri != null) {
|
if (databaseUri != null) {
|
||||||
if (databaseToMergeMainCredential != null) {
|
|
||||||
// Get keyFile inputStream
|
|
||||||
databaseToMergeMainCredential.keyFileUri?.let { keyFile ->
|
|
||||||
keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
databaseToMerge.readDatabaseStream(contentResolver, databaseUri,
|
databaseToMerge.readDatabaseStream(contentResolver, databaseUri,
|
||||||
{ databaseInputStream ->
|
{ databaseInputStream ->
|
||||||
val databaseToMergeKDB = DatabaseKDB()
|
val databaseToMergeKDB = DatabaseKDB()
|
||||||
@@ -715,7 +714,11 @@ class Database {
|
|||||||
if (databaseToMergeMainCredential != null) {
|
if (databaseToMergeMainCredential != null) {
|
||||||
databaseToMergeKDB.retrieveMasterKey(
|
databaseToMergeKDB.retrieveMasterKey(
|
||||||
databaseToMergeMainCredential.masterPassword,
|
databaseToMergeMainCredential.masterPassword,
|
||||||
keyFileInputStream,
|
getKeyFileData(
|
||||||
|
contentResolver,
|
||||||
|
databaseToMergeMainCredential.keyFileUri,
|
||||||
|
),
|
||||||
|
databaseToMergeMainCredential.hardwareKeyData
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
databaseToMergeKDB.masterKey = masterKey
|
databaseToMergeKDB.masterKey = masterKey
|
||||||
@@ -731,7 +734,11 @@ class Database {
|
|||||||
if (databaseToMergeMainCredential != null) {
|
if (databaseToMergeMainCredential != null) {
|
||||||
databaseToMergeKDBX.retrieveMasterKey(
|
databaseToMergeKDBX.retrieveMasterKey(
|
||||||
databaseToMergeMainCredential.masterPassword,
|
databaseToMergeMainCredential.masterPassword,
|
||||||
keyFileInputStream,
|
getKeyFileData(
|
||||||
|
contentResolver,
|
||||||
|
databaseToMergeMainCredential.keyFileUri
|
||||||
|
),
|
||||||
|
databaseToMergeMainCredential.hardwareKeyData
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
databaseToMergeKDBX.masterKey = masterKey
|
databaseToMergeKDBX.masterKey = masterKey
|
||||||
@@ -769,7 +776,6 @@ class Database {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw LoadDatabaseException(e)
|
throw LoadDatabaseException(e)
|
||||||
} finally {
|
} finally {
|
||||||
keyFileInputStream?.close()
|
|
||||||
databaseToMerge.clearAndClose()
|
databaseToMerge.clearAndClose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1024,9 +1030,19 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun assignMasterKey(key: String?, keyInputStream: InputStream?) {
|
fun assignMasterKey(contentResolver: ContentResolver,
|
||||||
mDatabaseKDB?.retrieveMasterKey(key, keyInputStream)
|
mainCredential: MainCredential) {
|
||||||
mDatabaseKDBX?.retrieveMasterKey(key, keyInputStream)
|
val keyFileData = getKeyFileData(contentResolver, mainCredential.keyFileUri)
|
||||||
|
mDatabaseKDB?.retrieveMasterKey(
|
||||||
|
mainCredential.masterPassword,
|
||||||
|
keyFileData,
|
||||||
|
mainCredential.hardwareKeyData
|
||||||
|
)
|
||||||
|
mDatabaseKDBX?.retrieveMasterKey(
|
||||||
|
mainCredential.masterPassword,
|
||||||
|
keyFileData,
|
||||||
|
mainCredential.hardwareKeyData
|
||||||
|
)
|
||||||
mDatabaseKDBX?.keyLastChanged = DateInstant()
|
mDatabaseKDBX?.keyLastChanged = DateInstant()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.NodeIdUUID
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeVersioned
|
import com.kunzisoft.keepass.database.element.node.NodeVersioned
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||||
@@ -117,14 +116,15 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun getMasterKey(key: String?, keyInputStream: InputStream?): ByteArray {
|
override fun getMasterKey(passwordKey: String?,
|
||||||
|
keyFileData: ByteArray?,
|
||||||
return if (key != null && keyInputStream != null) {
|
hardwareKey: ByteArray?): ByteArray {
|
||||||
getCompositeKey(key, keyInputStream)
|
return if (passwordKey != null && keyFileData != null) {
|
||||||
} else if (key != null) { // key.length() >= 0
|
getCompositeKey(passwordKey, keyFileData, null) ?: byteArrayOf()
|
||||||
getPasswordKey(key)
|
} else if (passwordKey != null) { // key.length() >= 0
|
||||||
} else if (keyInputStream != null) { // key == null
|
getPasswordKey(passwordKey)
|
||||||
getFileKey(keyInputStream)
|
} else if (keyFileData != null) { // key == null
|
||||||
|
getFileKey(keyFileData)
|
||||||
} else {
|
} else {
|
||||||
throw IllegalArgumentException("Key cannot be empty.")
|
throw IllegalArgumentException("Key cannot be empty.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -529,19 +529,11 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
public override fun getMasterKey(key: String?, keyInputStream: InputStream?): ByteArray {
|
public override fun getMasterKey(passwordKey: String?,
|
||||||
|
keyFileData: ByteArray?,
|
||||||
var masterKey = byteArrayOf()
|
hardwareKey: ByteArray?): ByteArray {
|
||||||
|
return getCompositeKey(passwordKey, keyFileData, hardwareKey)
|
||||||
if (key != null && keyInputStream != null) {
|
?: HashManager.hashSha256(byteArrayOf())
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
|
|||||||
@@ -92,18 +92,30 @@ abstract class DatabaseVersioned<
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@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)
|
@Throws(IOException::class)
|
||||||
fun retrieveMasterKey(key: String?, keyfileInputStream: InputStream?) {
|
fun retrieveMasterKey(key: String?,
|
||||||
masterKey = getMasterKey(key, keyfileInputStream)
|
keyFileData: ByteArray?,
|
||||||
|
hardwareKeyData: ByteArray?) {
|
||||||
|
masterKey = getMasterKey(key, keyFileData, hardwareKeyData)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
protected fun getCompositeKey(key: String, keyfileInputStream: InputStream): ByteArray {
|
protected fun getCompositeKey(passwordKey: String?,
|
||||||
val fileKey = getFileKey(keyfileInputStream)
|
keyFileData: ByteArray?,
|
||||||
val passwordKey = getPasswordKey(key)
|
hardwareKeyData: ByteArray?): ByteArray? {
|
||||||
return HashManager.hashSha256(passwordKey, fileKey)
|
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)
|
@Throws(IOException::class)
|
||||||
@@ -117,10 +129,8 @@ abstract class DatabaseVersioned<
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
protected fun getFileKey(keyInputStream: InputStream): ByteArray {
|
protected fun getFileKey(keyData: ByteArray): ByteArray {
|
||||||
try {
|
try {
|
||||||
val keyData = keyInputStream.readBytes()
|
|
||||||
|
|
||||||
// Check XML key file
|
// Check XML key file
|
||||||
val xmlKeyByteArray = loadXmlKeyFile(ByteArrayInputStream(keyData))
|
val xmlKeyByteArray = loadXmlKeyFile(ByteArrayInputStream(keyData))
|
||||||
if (xmlKeyByteArray != null) {
|
if (xmlKeyByteArray != null) {
|
||||||
|
|||||||
@@ -41,11 +41,6 @@ class CipherEncryptDatabase(): Parcelable {
|
|||||||
parcel.readByteArray(specParameters)
|
parcel.readByteArray(specParameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun replaceContent(copy: CipherEncryptDatabase) {
|
|
||||||
this.encryptedValue = copy.encryptedValue
|
|
||||||
this.specParameters = copy.specParameters
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
parcel.writeParcelable(databaseUri, flags)
|
parcel.writeParcelable(databaseUri, flags)
|
||||||
parcel.writeEnum(credentialStorage)
|
parcel.writeEnum(credentialStorage)
|
||||||
|
|||||||
@@ -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
|
package com.kunzisoft.keepass.model
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
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(
|
constructor(parcel: Parcel) : this() {
|
||||||
parcel.readString(),
|
masterPassword = parcel.readString()
|
||||||
parcel.readParcelable(Uri::class.java.classLoader)) {
|
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) {
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
parcel.writeString(masterPassword)
|
parcel.writeString(masterPassword)
|
||||||
parcel.writeParcelable(keyFileUri, flags)
|
parcel.writeParcelable(keyFileUri, flags)
|
||||||
|
if (hardwareKeyData != null) {
|
||||||
|
parcel.writeInt(hardwareKeyData!!.size)
|
||||||
|
parcel.writeByteArray(hardwareKeyData)
|
||||||
|
} else {
|
||||||
|
parcel.writeInt(-1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun describeContents(): Int {
|
override fun describeContents(): Int {
|
||||||
return 0
|
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> {
|
companion object CREATOR : Parcelable.Creator<MainCredential> {
|
||||||
override fun createFromParcel(parcel: Parcel): MainCredential {
|
override fun createFromParcel(parcel: Parcel): MainCredential {
|
||||||
return MainCredential(parcel)
|
return MainCredential(parcel)
|
||||||
|
|||||||
@@ -39,10 +39,11 @@ object HashManager {
|
|||||||
return messageDigest
|
return messageDigest
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hashSha256(vararg data: ByteArray): ByteArray {
|
fun hashSha256(vararg data: ByteArray?): ByteArray {
|
||||||
val hash: MessageDigest = getHash256()
|
val hash: MessageDigest = getHash256()
|
||||||
for (byteArray in data) {
|
for (byteArray in data) {
|
||||||
hash.update(byteArray)
|
if (byteArray != null)
|
||||||
|
hash.update(byteArray)
|
||||||
}
|
}
|
||||||
return hash.digest()
|
return hash.digest()
|
||||||
}
|
}
|
||||||
@@ -57,10 +58,11 @@ object HashManager {
|
|||||||
return messageDigest
|
return messageDigest
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hashSha512(vararg data: ByteArray): ByteArray {
|
private fun hashSha512(vararg data: ByteArray?): ByteArray {
|
||||||
val hash: MessageDigest = getHash512()
|
val hash: MessageDigest = getHash512()
|
||||||
for (byteArray in data) {
|
for (byteArray in data) {
|
||||||
hash.update(byteArray)
|
if (byteArray != null)
|
||||||
|
hash.update(byteArray)
|
||||||
}
|
}
|
||||||
return hash.digest()
|
return hash.digest()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user