diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/AssignMainCredentialInDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/AssignMainCredentialInDatabaseRunnable.kt index f405faeb6..f1be79ea1 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/AssignMainCredentialInDatabaseRunnable.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/AssignMainCredentialInDatabaseRunnable.kt @@ -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) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt index 004906cdf..a31f4d45c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt @@ -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() } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt index f10901014..cc4f3a3f5 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt @@ -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() { @@ -117,14 +116,15 @@ class DatabaseKDB : DatabaseVersioned() { } @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.") } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt index adc05d675..03d0a41c1 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt @@ -529,19 +529,11 @@ class DatabaseKDBX : DatabaseVersioned { } @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) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseVersioned.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseVersioned.kt index 3ffae91b2..fc30bec30 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseVersioned.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseVersioned.kt @@ -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) { diff --git a/app/src/main/java/com/kunzisoft/keepass/model/CipherEncryptDatabase.kt b/app/src/main/java/com/kunzisoft/keepass/model/CipherEncryptDatabase.kt index f3ed3dade..321facd97 100644 --- a/app/src/main/java/com/kunzisoft/keepass/model/CipherEncryptDatabase.kt +++ b/app/src/main/java/com/kunzisoft/keepass/model/CipherEncryptDatabase.kt @@ -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) diff --git a/app/src/main/java/com/kunzisoft/keepass/model/MainCredential.kt b/app/src/main/java/com/kunzisoft/keepass/model/MainCredential.kt index e5ee12c72..fc2f31eee 100644 --- a/app/src/main/java/com/kunzisoft/keepass/model/MainCredential.kt +++ b/app/src/main/java/com/kunzisoft/keepass/model/MainCredential.kt @@ -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 . + */ 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 { override fun createFromParcel(parcel: Parcel): MainCredential { return MainCredential(parcel) diff --git a/crypto/src/main/java/com/kunzisoft/encrypt/HashManager.kt b/crypto/src/main/java/com/kunzisoft/encrypt/HashManager.kt index cde50b24e..1c7e1e573 100644 --- a/crypto/src/main/java/com/kunzisoft/encrypt/HashManager.kt +++ b/crypto/src/main/java/com/kunzisoft/encrypt/HashManager.kt @@ -39,10 +39,11 @@ object HashManager { return messageDigest } - fun hashSha256(vararg data: ByteArray): ByteArray { + fun hashSha256(vararg data: ByteArray?): ByteArray { val hash: MessageDigest = getHash256() for (byteArray in data) { - hash.update(byteArray) + if (byteArray != null) + hash.update(byteArray) } return hash.digest() } @@ -57,10 +58,11 @@ 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) { - hash.update(byteArray) + if (byteArray != null) + hash.update(byteArray) } return hash.digest() }