diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt index 955a547d3..87b9de595 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt @@ -125,7 +125,7 @@ class EntryActivity : LockingActivity() { historyView = findViewById(R.id.history_container) entryContentsView = findViewById(R.id.entry_contents) entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this)) - entryContentsView?.setAttachmentCipherKey(mDatabase?.loadedCipherKey) + entryContentsView?.setAttachmentCipherKey(mDatabase) entryProgress = findViewById(R.id.entry_progress) lockView = findViewById(R.id.lock_button) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt index 167b0581c..32362d47f 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt @@ -202,7 +202,7 @@ class EntryEditActivity : LockingActivity(), // Build fragment to manage entry modification entryEditFragment = supportFragmentManager.findFragmentByTag(ENTRY_EDIT_FRAGMENT_TAG) as? EntryEditFragment? if (entryEditFragment == null) { - entryEditFragment = EntryEditFragment.getInstance(tempEntryInfo, mDatabase?.loadedCipherKey) + entryEditFragment = EntryEditFragment.getInstance(tempEntryInfo) } supportFragmentManager.beginTransaction() .replace(R.id.entry_edit_contents, entryEditFragment!!, ENTRY_EDIT_FRAGMENT_TAG) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/IconPickerActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/IconPickerActivity.kt index bd5ef9a21..9a7bd8980 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/IconPickerActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/IconPickerActivity.kt @@ -232,8 +232,13 @@ class IconPickerActivity : LockingActivity() { mDatabase?.buildNewCustomIcon(UriUtil.getBinaryDir(this@IconPickerActivity)) { customIcon, binary -> if (customIcon != null) { iconCustomState.iconCustom = customIcon - BinaryDatabaseManager.resizeBitmapAndStoreDataInBinaryFile(contentResolver, - iconToUploadUri, binary) + mDatabase?.let { database -> + BinaryDatabaseManager.resizeBitmapAndStoreDataInBinaryFile( + contentResolver, + database, + iconToUploadUri, + binary) + } when { binary == null -> { } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/ImageViewerActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/ImageViewerActivity.kt index 143fd5763..eac37462c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/ImageViewerActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/ImageViewerActivity.kt @@ -39,6 +39,8 @@ import kotlin.math.max class ImageViewerActivity : LockingActivity() { + private var mDatabase: Database? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -59,6 +61,8 @@ class ImageViewerActivity : LockingActivity() { resources.displayMetrics.heightPixels * 2 ) + mDatabase = Database.getInstance() + try { progressView.visibility = View.VISIBLE intent.getParcelableExtra(IMAGE_ATTACHMENT_TAG)?.let { attachment -> @@ -66,16 +70,18 @@ class ImageViewerActivity : LockingActivity() { supportActionBar?.title = attachment.name supportActionBar?.subtitle = Formatter.formatFileSize(this, attachment.binaryData.getSize()) - BinaryDatabaseManager.loadBitmap( - attachment.binaryData, - Database.getInstance().loadedCipherKey, - mImagePreviewMaxWidth - ) { bitmapLoaded -> - if (bitmapLoaded == null) { - finish() - } else { - progressView.visibility = View.GONE - imageView.setImageBitmap(bitmapLoaded) + mDatabase?.let { database -> + BinaryDatabaseManager.loadBitmap( + database, + attachment.binaryData, + mImagePreviewMaxWidth + ) { bitmapLoaded -> + if (bitmapLoaded == null) { + finish() + } else { + progressView.visibility = View.GONE + imageView.setImageBitmap(bitmapLoaded) + } } } } ?: finish() diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryEditFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryEditFragment.kt index ae48ccd03..2c8f1a4f8 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryEditFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryEditFragment.kt @@ -84,7 +84,6 @@ class EntryEditFragment: StylishFragment() { // Elements to modify the current entry private var mEntryInfo = EntryInfo() - private var mBinaryCipherKey: Database.LoadedKey? = null private var mLastFocusedEditField: FocusedEditField? = null private var mExtraViewToRequestFocus: EditText? = null @@ -122,7 +121,9 @@ class EntryEditFragment: StylishFragment() { attachmentsContainerView = rootView.findViewById(R.id.entry_attachments_container) attachmentsListView = rootView.findViewById(R.id.entry_attachments_list) attachmentsAdapter = EntryAttachmentsItemsAdapter(requireContext()) - attachmentsAdapter.binaryCipherKey = arguments?.getSerializable(KEY_BINARY_CIPHER_KEY) as? Database.LoadedKey? + // TODO retrieve current database with its unique key + attachmentsAdapter.database = Database.getInstance() + //attachmentsAdapter.database = arguments?.getInt(KEY_DATABASE) attachmentsAdapter.onListSizeChangedListener = { previousSize, newSize -> if (previousSize > 0 && newSize == 0) { attachmentsContainerView.collapse(true) @@ -502,7 +503,6 @@ class EntryEditFragment: StylishFragment() { override fun onSaveInstanceState(outState: Bundle) { populateEntryWithViews() outState.putParcelable(KEY_TEMP_ENTRY_INFO, mEntryInfo) - outState.putSerializable(KEY_BINARY_CIPHER_KEY, mBinaryCipherKey) outState.putParcelable(KEY_LAST_FOCUSED_FIELD, mLastFocusedEditField) super.onSaveInstanceState(outState) @@ -510,15 +510,16 @@ class EntryEditFragment: StylishFragment() { companion object { const val KEY_TEMP_ENTRY_INFO = "KEY_TEMP_ENTRY_INFO" - const val KEY_BINARY_CIPHER_KEY = "KEY_BINARY_CIPHER_KEY" + const val KEY_DATABASE = "KEY_DATABASE" const val KEY_LAST_FOCUSED_FIELD = "KEY_LAST_FOCUSED_FIELD" - fun getInstance(entryInfo: EntryInfo?, - loadedKey: Database.LoadedKey?): EntryEditFragment { + fun getInstance(entryInfo: EntryInfo?): EntryEditFragment { + //database: Database?): EntryEditFragment { return EntryEditFragment().apply { arguments = Bundle().apply { putParcelable(KEY_TEMP_ENTRY_INFO, entryInfo) - putSerializable(KEY_BINARY_CIPHER_KEY, loadedKey) + // TODO Unique database key database.key + putInt(KEY_DATABASE, 0) } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/adapters/EntryAttachmentsItemsAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/adapters/EntryAttachmentsItemsAdapter.kt index 31d03ec02..3bc2824d9 100644 --- a/app/src/main/java/com/kunzisoft/keepass/adapters/EntryAttachmentsItemsAdapter.kt +++ b/app/src/main/java/com/kunzisoft/keepass/adapters/EntryAttachmentsItemsAdapter.kt @@ -45,7 +45,7 @@ import kotlin.math.max class EntryAttachmentsItemsAdapter(context: Context) : AnimatedItemsAdapter(context) { - var binaryCipherKey: Database.LoadedKey? = null + var database: Database? = null var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null var onBinaryPreviewLoaded: ((item: EntryAttachmentState) -> Unit)? = null @@ -82,21 +82,23 @@ class EntryAttachmentsItemsAdapter(context: Context) if (entryAttachmentState.previewState == AttachmentState.NULL) { entryAttachmentState.previewState = AttachmentState.IN_PROGRESS // Load the bitmap image - BinaryDatabaseManager.loadBitmap( - entryAttachmentState.attachment.binaryData, - binaryCipherKey, - mImagePreviewMaxWidth - ) { imageLoaded -> - if (imageLoaded == null) { - entryAttachmentState.previewState = AttachmentState.ERROR - visibility = View.GONE - onBinaryPreviewLoaded?.invoke(entryAttachmentState) - } else { - entryAttachmentState.previewState = AttachmentState.COMPLETE - setImageBitmap(imageLoaded) - if (visibility != View.VISIBLE) { - expand(true, resources.getDimensionPixelSize(R.dimen.item_file_info_height)) { - onBinaryPreviewLoaded?.invoke(entryAttachmentState) + database?.let { database -> + BinaryDatabaseManager.loadBitmap( + database, + entryAttachmentState.attachment.binaryData, + mImagePreviewMaxWidth + ) { imageLoaded -> + if (imageLoaded == null) { + entryAttachmentState.previewState = AttachmentState.ERROR + visibility = View.GONE + onBinaryPreviewLoaded?.invoke(entryAttachmentState) + } else { + entryAttachmentState.previewState = AttachmentState.COMPLETE + setImageBitmap(imageLoaded) + if (visibility != View.VISIBLE) { + expand(true, resources.getDimensionPixelSize(R.dimen.item_file_info_height)) { + onBinaryPreviewLoaded?.invoke(entryAttachmentState) + } } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/ReloadDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/ReloadDatabaseRunnable.kt index d5d39bb45..874fae526 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/ReloadDatabaseRunnable.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/ReloadDatabaseRunnable.kt @@ -37,7 +37,7 @@ class ReloadDatabaseRunnable(private val context: Context, private var tempCipherKey: Database.LoadedKey? = null override fun onStartRun() { - tempCipherKey = mDatabase.loadedCipherKey + tempCipherKey = mDatabase.binaryCache.loadedCipherKey // Clear before we load mDatabase.clear(UriUtil.getBinaryDir(context)) mDatabase.wasReloaded = true 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 20c7e658a..553d31b6c 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 @@ -70,7 +70,7 @@ class Database { var isReadOnly = false val iconDrawableFactory = IconDrawableFactory( - { loadedCipherKey }, + { binaryCache }, { iconId -> iconsManager.getBinaryForCustomIcon(iconId) } ) @@ -92,13 +92,13 @@ class Database { * Cipher key regenerated when the database is loaded and closed * Can be used to temporarily store database elements */ - var loadedCipherKey: LoadedKey? + var binaryCache: BinaryCache private set(value) { - mDatabaseKDB?.loadedCipherKey = value - mDatabaseKDBX?.loadedCipherKey = value + mDatabaseKDB?.binaryCache = value + mDatabaseKDBX?.binaryCache = value } get() { - return mDatabaseKDB?.loadedCipherKey ?: mDatabaseKDBX?.loadedCipherKey + return mDatabaseKDB?.binaryCache ?: mDatabaseKDBX?.binaryCache ?: BinaryCache() } private val iconsManager: IconsManager @@ -136,7 +136,7 @@ class Database { fun removeCustomIcon(customIcon: IconImageCustom) { iconDrawableFactory.clearFromCache(customIcon) - iconsManager.removeCustomIcon(customIcon.uuid) + iconsManager.removeCustomIcon(binaryCache, customIcon.uuid) } val allowName: Boolean @@ -381,7 +381,7 @@ class Database { fun createData(databaseUri: Uri, databaseName: String, rootName: String) { val newDatabase = DatabaseKDBX(databaseName, rootName) - newDatabase.loadedCipherKey = LoadedKey.generateNewCipherKey() + newDatabase.binaryCache.loadedCipherKey = LoadedKey.generateNewCipherKey() setDatabaseKDBX(newDatabase) this.fileUri = databaseUri // Set Database state @@ -669,6 +669,7 @@ class Database { } fun clear(filesDirectory: File? = null) { + binaryCache.clear() iconsManager.clearCache() iconDrawableFactory.clearCache() // Delete the cache of the database if present diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryByte.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryByte.kt index fa735ae63..f9602ce37 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryByte.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryByte.kt @@ -24,21 +24,16 @@ import android.os.Parcelable import android.util.Base64 import android.util.Base64InputStream import android.util.Base64OutputStream -import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.stream.readAllBytes import java.io.* import java.util.zip.GZIPOutputStream -import javax.crypto.Cipher -import javax.crypto.CipherInputStream -import javax.crypto.CipherOutputStream -import javax.crypto.spec.IvParameterSpec class BinaryByte : BinaryData { private var mDataByteId: Int? = null - private fun getByteArray(): ByteArray { - val keyData = KeyByteArray.getByteArray(mDataByteId) + private fun getByteArray(binaryCache: BinaryCache): ByteArray { + val keyData = binaryCache.getByteArray(mDataByteId) mDataByteId = keyData.key return keyData.data } @@ -46,14 +41,10 @@ class BinaryByte : BinaryData { /** * Empty protected binary */ - constructor() : super() { - getByteArray() - } + constructor() : super() constructor(compressed: Boolean = false, - protected: Boolean = false) : super(compressed, protected) { - getByteArray() - } + protected: Boolean = false) : super(compressed, protected) constructor(mDataByteId: Int, compressed: Boolean = false, @@ -73,27 +64,25 @@ class BinaryByte : BinaryData { } @Throws(IOException::class) - override fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream { + override fun getInputDataStream(binaryCache: BinaryCache): InputStream { return when { getSize() > 0 -> { - cipherDecryption.init(Cipher.DECRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv)) - Base64InputStream(CipherInputStream(ByteArrayInputStream(getByteArray()), cipherDecryption), Base64.NO_WRAP) + Base64InputStream(ByteArrayInputStream(getByteArray(binaryCache)), Base64.NO_WRAP) } else -> ByteArrayInputStream(ByteArray(0)) } } @Throws(IOException::class) - override fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream { - cipherEncryption.init(Cipher.ENCRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv)) - return BinaryCountingOutputStream(Base64OutputStream(CipherOutputStream(ByteOutputStream(), cipherEncryption), Base64.NO_WRAP)) + override fun getOutputDataStream(binaryCache: BinaryCache): OutputStream { + return BinaryCountingOutputStream(Base64OutputStream(ByteOutputStream(binaryCache), Base64.NO_WRAP)) } @Throws(IOException::class) - override fun compress(cipherKey: Database.LoadedKey) { + override fun compress(binaryCache: BinaryCache) { if (!isCompressed) { - GZIPOutputStream(getOutputDataStream(cipherKey)).use { outputStream -> - getInputDataStream(cipherKey).use { inputStream -> + GZIPOutputStream(getOutputDataStream(binaryCache)).use { outputStream -> + getInputDataStream(binaryCache).use { inputStream -> inputStream.readAllBytes { buffer -> outputStream.write(buffer) } @@ -104,10 +93,10 @@ class BinaryByte : BinaryData { } @Throws(IOException::class) - override fun decompress(cipherKey: Database.LoadedKey) { + override fun decompress(binaryCache: BinaryCache) { if (isCompressed) { - getUnGzipInputDataStream(cipherKey).use { inputStream -> - getOutputDataStream(cipherKey).use { outputStream -> + getUnGzipInputDataStream(binaryCache).use { inputStream -> + getOutputDataStream(binaryCache).use { outputStream -> inputStream.readAllBytes { buffer -> outputStream.write(buffer) } @@ -118,12 +107,14 @@ class BinaryByte : BinaryData { } @Throws(IOException::class) - override fun clear() { - KeyByteArray.removeByteArray(mDataByteId) + override fun delete() { + mDataByteId = null } - override fun toString(): String { - return getByteArray().toString() + @Throws(IOException::class) + override fun clear(binaryCache: BinaryCache) { + binaryCache.removeByteArray(mDataByteId) + mDataByteId = null } override fun equals(other: Any?): Boolean { @@ -145,9 +136,9 @@ class BinaryByte : BinaryData { /** * Custom OutputStream to calculate the size and hash of binary file */ - private inner class ByteOutputStream : ByteArrayOutputStream() { + private inner class ByteOutputStream(private val binaryCache: BinaryCache) : ByteArrayOutputStream() { override fun close() { - KeyByteArray.setByteArray(mDataByteId, this.toByteArray()) + binaryCache.setByteArray(mDataByteId, this.toByteArray()) super.close() } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryCache.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryCache.kt new file mode 100644 index 000000000..34be783d9 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryCache.kt @@ -0,0 +1,66 @@ +package com.kunzisoft.keepass.database.element.database + +import com.kunzisoft.keepass.database.element.Database +import java.util.* + +class BinaryCache { + + /** + * Cipher key generated when the database is loaded, and destroyed when the database is closed + * Can be used to temporarily store database elements + */ + var loadedCipherKey: Database.LoadedKey? = null + + // Similar to file storage but much faster + private val byteArrayList = HashMap() + + fun getByteArray(key: Int?): KeyByteArray { + if (key == null) { + val newItem = KeyByteArray(byteArrayList.size, ByteArray(0)) + byteArrayList[newItem.key] = newItem.data + return newItem + } + if (!byteArrayList.containsKey(key)) { + val newItem = KeyByteArray(key, ByteArray(0)) + byteArrayList[newItem.key] = newItem.data + return newItem + } + return KeyByteArray(key, byteArrayList[key]!!) + } + + fun setByteArray(key: Int?, data: ByteArray): KeyByteArray { + return if (key == null) { + val keyByteArray = KeyByteArray(byteArrayList.size, data) + byteArrayList[keyByteArray.key] = keyByteArray.data + keyByteArray + } else { + byteArrayList[key] = data + KeyByteArray(key, data) + } + } + + fun removeByteArray(key: Int?) { + key?.let { + byteArrayList.remove(it) + } + } + + fun clear() { + byteArrayList.clear() + } + + data class KeyByteArray(val key: Int, val data: ByteArray) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is KeyByteArray) return false + + if (key != other.key) return false + + return true + } + + override fun hashCode(): Int { + return key + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryData.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryData.kt index f8fd1c8f0..1aed84386 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryData.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryData.kt @@ -71,37 +71,40 @@ abstract class BinaryData : Parcelable { } @Throws(IOException::class) - abstract fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream + abstract fun getInputDataStream(binaryCache: BinaryCache): InputStream @Throws(IOException::class) - abstract fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream + abstract fun getOutputDataStream(binaryCache: BinaryCache): OutputStream @Throws(IOException::class) - fun getUnGzipInputDataStream(cipherKey: Database.LoadedKey): InputStream { + fun getUnGzipInputDataStream(binaryCache: BinaryCache): InputStream { return if (isCompressed) { - GZIPInputStream(getInputDataStream(cipherKey)) + GZIPInputStream(getInputDataStream(binaryCache)) } else { - getInputDataStream(cipherKey) + getInputDataStream(binaryCache) } } @Throws(IOException::class) - fun getGzipOutputDataStream(cipherKey: Database.LoadedKey): OutputStream { + fun getGzipOutputDataStream(binaryCache: BinaryCache): OutputStream { return if (isCompressed) { - GZIPOutputStream(getOutputDataStream(cipherKey)) + GZIPOutputStream(getOutputDataStream(binaryCache)) } else { - getOutputDataStream(cipherKey) + getOutputDataStream(binaryCache) } } @Throws(IOException::class) - abstract fun compress(cipherKey: Database.LoadedKey) + abstract fun compress(binaryCache: BinaryCache) @Throws(IOException::class) - abstract fun decompress(cipherKey: Database.LoadedKey) + abstract fun decompress(binaryCache: BinaryCache) @Throws(IOException::class) - abstract fun clear() + abstract fun delete() + + @Throws(IOException::class) + abstract fun clear(binaryCache: BinaryCache) open fun dataExists(): Boolean { return mLength > 0 diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryFile.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryFile.kt index c6dd6836d..a634a115a 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryFile.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryFile.kt @@ -24,12 +24,8 @@ import android.os.Parcelable import android.util.Base64 import android.util.Base64InputStream import android.util.Base64OutputStream -import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.stream.readAllBytes -import org.apache.commons.io.output.CountingOutputStream import java.io.* -import java.nio.ByteBuffer -import java.security.MessageDigest import java.util.zip.GZIPOutputStream import javax.crypto.Cipher import javax.crypto.CipherInputStream @@ -55,19 +51,20 @@ class BinaryFile : BinaryData { } @Throws(IOException::class) - override fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream { - return buildInputStream(mDataFile, cipherKey) + override fun getInputDataStream(binaryCache: BinaryCache): InputStream { + return buildInputStream(mDataFile, binaryCache) } @Throws(IOException::class) - override fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream { - return buildOutputStream(mDataFile, cipherKey) + override fun getOutputDataStream(binaryCache: BinaryCache): OutputStream { + return buildOutputStream(mDataFile, binaryCache) } @Throws(IOException::class) - private fun buildInputStream(file: File?, cipherKey: Database.LoadedKey): InputStream { + private fun buildInputStream(file: File?, binaryCache: BinaryCache): InputStream { + val cipherKey = binaryCache.loadedCipherKey return when { - file != null && file.length() > 0 -> { + cipherKey != null && file != null && file.length() > 0 -> { cipherDecryption.init(Cipher.DECRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv)) Base64InputStream(CipherInputStream(FileInputStream(file), cipherDecryption), Base64.NO_WRAP) } @@ -76,9 +73,10 @@ class BinaryFile : BinaryData { } @Throws(IOException::class) - private fun buildOutputStream(file: File?, cipherKey: Database.LoadedKey): OutputStream { + private fun buildOutputStream(file: File?, binaryCache: BinaryCache): OutputStream { + val cipherKey = binaryCache.loadedCipherKey return when { - file != null -> { + cipherKey != null && file != null -> { cipherEncryption.init(Cipher.ENCRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv)) BinaryCountingOutputStream(Base64OutputStream(CipherOutputStream(FileOutputStream(file), cipherEncryption), Base64.NO_WRAP)) } @@ -87,14 +85,14 @@ class BinaryFile : BinaryData { } @Throws(IOException::class) - override fun compress(cipherKey: Database.LoadedKey) { + override fun compress(binaryCache: BinaryCache) { mDataFile?.let { concreteDataFile -> // To compress, create a new binary with file if (!isCompressed) { // Encrypt the new gzipped temp file val fileBinaryCompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp") - getInputDataStream(cipherKey).use { inputStream -> - GZIPOutputStream(buildOutputStream(fileBinaryCompress, cipherKey)).use { outputStream -> + getInputDataStream(binaryCache).use { inputStream -> + GZIPOutputStream(buildOutputStream(fileBinaryCompress, binaryCache)).use { outputStream -> inputStream.readAllBytes { buffer -> outputStream.write(buffer) } @@ -112,13 +110,13 @@ class BinaryFile : BinaryData { } @Throws(IOException::class) - override fun decompress(cipherKey: Database.LoadedKey) { + override fun decompress(binaryCache: BinaryCache) { mDataFile?.let { concreteDataFile -> if (isCompressed) { // Encrypt the new ungzipped temp file val fileBinaryDecompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp") - getUnGzipInputDataStream(cipherKey).use { inputStream -> - buildOutputStream(fileBinaryDecompress, cipherKey).use { outputStream -> + getUnGzipInputDataStream(binaryCache).use { inputStream -> + buildOutputStream(fileBinaryDecompress, binaryCache).use { outputStream -> inputStream.readAllBytes { buffer -> outputStream.write(buffer) } @@ -135,12 +133,16 @@ class BinaryFile : BinaryData { } } - @Throws(IOException::class) - override fun clear() { + override fun delete() { if (mDataFile != null && !mDataFile!!.delete()) throw IOException("Unable to delete temp file " + mDataFile!!.absolutePath) } + @Throws(IOException::class) + override fun clear(binaryCache: BinaryCache) { + delete() + } + override fun dataExists(): Boolean { return mDataFile != null && super.dataExists() } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryPool.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryPool.kt index 9aaea8e86..7a97ecca5 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryPool.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryPool.kt @@ -20,7 +20,6 @@ package com.kunzisoft.keepass.database.element.database import android.util.Log -import java.io.File import java.io.IOException import kotlin.math.abs @@ -227,7 +226,7 @@ abstract class BinaryPool { @Throws(IOException::class) fun clear() { doForEachBinary { _, binary -> - binary.clear() + binary.delete() } pool.clear() } 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 ca43b3ae9..6e3e7474b 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 @@ -212,10 +212,8 @@ class DatabaseKDBX : DatabaseVersioned { private fun compressAllBinaries() { binaryPool.doForEachBinary { _, binary -> try { - val cipherKey = loadedCipherKey - ?: throw IOException("Unable to retrieve cipher key to compress binaries") // To compress, create a new binary with file - binary.compress(cipherKey) + binary.compress(binaryCache) } catch (e: Exception) { Log.e(TAG, "Unable to compress $binary", e) } @@ -225,9 +223,7 @@ class DatabaseKDBX : DatabaseVersioned { private fun decompressAllBinaries() { binaryPool.doForEachBinary { _, binary -> try { - val cipherKey = loadedCipherKey - ?: throw IOException("Unable to retrieve cipher key to decompress binaries") - binary.decompress(cipherKey) + binary.decompress(binaryCache) } catch (e: Exception) { Log.e(TAG, "Unable to decompress $binary", e) } @@ -690,7 +686,7 @@ class DatabaseKDBX : DatabaseVersioned { try { binaryPool.remove(it) if (clear) - it.clear() + it.clear(binaryCache) } catch (e: Exception) { Log.w(TAG, "Unable to clean binaries", e) } 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 019126dc4..81874418e 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 @@ -57,10 +57,11 @@ abstract class DatabaseVersioned< protected set /** + * To manage binaries in faster way * Cipher key generated when the database is loaded, and destroyed when the database is closed * Can be used to temporarily store database elements */ - var loadedCipherKey: Database.LoadedKey? = null + var binaryCache = BinaryCache() val iconsManager = IconsManager() diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/KeyByteArray.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/KeyByteArray.kt deleted file mode 100644 index f82c4ac09..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/database/KeyByteArray.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.kunzisoft.keepass.database.element.database - -import java.util.HashMap - -class KeyByteArray { - - data class KeyByteArray(val key: Int, val data: ByteArray) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is KeyByteArray) return false - - if (key != other.key) return false - - return true - } - - override fun hashCode(): Int { - return key - } - } - - companion object { - - // Similar to file storage but much faster - private val byteArrayList = HashMap() - - fun getByteArray(key: Int?): KeyByteArray { - if (key == null) { - val newItem = KeyByteArray(byteArrayList.size, ByteArray(0)) - byteArrayList[newItem.key] = newItem.data - return newItem - } - if (!byteArrayList.containsKey(key)) { - val newItem = KeyByteArray(key, ByteArray(0)) - byteArrayList[newItem.key] = newItem.data - return newItem - } - return KeyByteArray(key, byteArrayList[key]!!) - } - - fun setByteArray(key: Int?, data: ByteArray): KeyByteArray { - return if (key == null) { - val keyByteArray = KeyByteArray(byteArrayList.size, data) - byteArrayList[keyByteArray.key] = keyByteArray.data - keyByteArray - } else { - byteArrayList[key] = data - KeyByteArray(key, data) - } - } - - fun removeByteArray(key: Int?) { - key?.let { - byteArrayList.remove(it) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/icon/IconsManager.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/icon/IconsManager.kt index 1d34a854c..3b230cd83 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/icon/IconsManager.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/icon/IconsManager.kt @@ -20,10 +20,7 @@ package com.kunzisoft.keepass.database.element.icon import android.util.Log -import com.kunzisoft.keepass.database.element.database.BinaryByte -import com.kunzisoft.keepass.database.element.database.BinaryData -import com.kunzisoft.keepass.database.element.database.BinaryFile -import com.kunzisoft.keepass.database.element.database.CustomIconPool +import com.kunzisoft.keepass.database.element.database.* import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.KEY_ID import com.kunzisoft.keepass.icons.IconPack.Companion.NB_ICONS import java.io.File @@ -82,11 +79,11 @@ class IconsManager { return customCache.isBinaryDuplicate(binaryData) } - fun removeCustomIcon(iconUuid: UUID) { + fun removeCustomIcon(binaryCache: BinaryCache, iconUuid: UUID) { val binary = customCache[iconUuid] customCache.remove(iconUuid) try { - binary?.clear() + binary?.clear(binaryCache) } catch (e: Exception) { Log.w(TAG, "Unable to remove custom icon binary", e) } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt index b6602089a..548b7f3b3 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt @@ -60,7 +60,7 @@ class DatabaseInputKDB(cacheDirectory: File, progressTaskUpdater: ProgressTaskUpdater?, fixDuplicateUUID: Boolean): DatabaseKDB { return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) { - mDatabase.loadedCipherKey = loadedCipherKey + mDatabase.binaryCache.loadedCipherKey = loadedCipherKey mDatabase.retrieveMasterKey(password, keyfileInputStream) } } @@ -72,7 +72,7 @@ class DatabaseInputKDB(cacheDirectory: File, progressTaskUpdater: ProgressTaskUpdater?, fixDuplicateUUID: Boolean): DatabaseKDB { return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) { - mDatabase.loadedCipherKey = loadedCipherKey + mDatabase.binaryCache.loadedCipherKey = loadedCipherKey mDatabase.masterKey = masterKey } } @@ -309,9 +309,7 @@ class DatabaseInputKDB(cacheDirectory: File, if (fieldSize > 0) { val binaryData = mDatabase.buildNewAttachment(cacheDirectory) entry.putBinary(binaryData, mDatabase.binaryPool) - val cipherKey = mDatabase.loadedCipherKey - ?: throw IOException("Unable to retrieve cipher key to load binaries") - BufferedOutputStream(binaryData.getOutputDataStream(cipherKey)).use { outputStream -> + BufferedOutputStream(binaryData.getOutputDataStream(mDatabase.binaryCache)).use { outputStream -> cipherInputStream.readBytes(fieldSize) { buffer -> outputStream.write(buffer) } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt index a18caae07..a75c7a543 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt @@ -102,7 +102,7 @@ class DatabaseInputKDBX(cacheDirectory: File, progressTaskUpdater: ProgressTaskUpdater?, fixDuplicateUUID: Boolean): DatabaseKDBX { return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) { - mDatabase.loadedCipherKey = loadedCipherKey + mDatabase.binaryCache.loadedCipherKey = loadedCipherKey mDatabase.retrieveMasterKey(password, keyfileInputStream) } } @@ -114,7 +114,7 @@ class DatabaseInputKDBX(cacheDirectory: File, progressTaskUpdater: ProgressTaskUpdater?, fixDuplicateUUID: Boolean): DatabaseKDBX { return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) { - mDatabase.loadedCipherKey = loadedCipherKey + mDatabase.binaryCache.loadedCipherKey = loadedCipherKey mDatabase.masterKey = masterKey } } @@ -279,9 +279,7 @@ class DatabaseInputKDBX(cacheDirectory: File, // No compression at this level val protectedBinary = mDatabase.buildNewAttachment(cacheDirectory, isRAMSufficient.invoke(byteLength.toLong()), false, protectedFlag) - val cipherKey = mDatabase.loadedCipherKey - ?: throw IOException("Unable to retrieve cipher key to load binaries") - protectedBinary.getOutputDataStream(cipherKey).use { outputStream -> + protectedBinary.getOutputDataStream(mDatabase.binaryCache).use { outputStream -> dataInputStream.readBytes(byteLength) { buffer -> outputStream.write(buffer) } @@ -708,10 +706,8 @@ class DatabaseInputKDBX(cacheDirectory: File, val iconData = customIconData if (customIconID != DatabaseVersioned.UUID_ZERO && iconData != null) { mDatabase.addCustomIcon(cacheDirectory, customIconID, isRAMSufficient.invoke(iconData.size.toLong())) { _, binary -> - mDatabase.loadedCipherKey?.let { cipherKey -> - binary?.getOutputDataStream(cipherKey)?.use { outputStream -> - outputStream.write(iconData) - } + binary?.getOutputDataStream(mDatabase.binaryCache)?.use { outputStream -> + outputStream.write(iconData) } } } @@ -1023,16 +1019,14 @@ class DatabaseInputKDBX(cacheDirectory: File, // Build the new binary and compress val binaryAttachment = mDatabase.buildNewAttachment(cacheDirectory, isRAMSufficient.invoke(base64.length.toLong()), compressed, protected, binaryId) - val binaryCipherKey = mDatabase.loadedCipherKey - ?: throw IOException("Unable to retrieve cipher key to load binaries") try { - binaryAttachment.getOutputDataStream(binaryCipherKey).use { outputStream -> + binaryAttachment.getOutputDataStream(mDatabase.binaryCache).use { outputStream -> outputStream.write(Base64.decode(base64, BASE_64_FLAG)) } } catch (e: Exception) { Log.e(TAG, "Unable to read base 64 attachment", e) binaryAttachment.isCorrupted = true - binaryAttachment.getOutputDataStream(binaryCipherKey).use { outputStream -> + binaryAttachment.getOutputDataStream(mDatabase.binaryCache).use { outputStream -> outputStream.write(base64.toByteArray()) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDB.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDB.kt index 16d704560..5faf83293 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDB.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDB.kt @@ -217,7 +217,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB, } // Entries mDatabaseKDB.doForEachEntryInIndex { entry -> - EntryOutputKDB(entry, outputStream, mDatabaseKDB.loadedCipherKey).output(mDatabaseKDB) + EntryOutputKDB(mDatabaseKDB, entry, outputStream).output() } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt index 77e5b9ccf..d3756d0d0 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt @@ -136,28 +136,26 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX, dataOutputStream.writeInt(streamKeySize) dataOutputStream.write(header.innerRandomStreamKey) - database.loadedCipherKey?.let { binaryCipherKey -> - database.binaryPool.doForEachOrderedBinaryWithoutDuplication { _, binary -> - // Force decompression to add binary in header - binary.decompress(binaryCipherKey) - // Write type binary - dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary) - // Write size - dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(binary.getSize() + 1)) - // Write protected flag - var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None - if (binary.isProtected) { - flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected - } - dataOutputStream.writeByte(flag) + database.binaryPool.doForEachOrderedBinaryWithoutDuplication { _, binary -> + // Force decompression to add binary in header + binary.decompress(database.binaryCache) + // Write type binary + dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary) + // Write size + dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(binary.getSize() + 1)) + // Write protected flag + var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None + if (binary.isProtected) { + flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected + } + dataOutputStream.writeByte(flag) - binary.getInputDataStream(binaryCipherKey).use { inputStream -> - inputStream.readAllBytes { buffer -> - dataOutputStream.write(buffer) - } + binary.getInputDataStream(database.binaryCache).use { inputStream -> + inputStream.readAllBytes { buffer -> + dataOutputStream.write(buffer) } } - } ?: Log.e(TAG, "Unable to retrieve cipher key to write head binaries") + } dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader) dataOutputStream.writeInt(0) @@ -494,31 +492,29 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX, // With kdbx4, don't use this method because binaries are in header file @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) private fun writeMetaBinaries() { - mDatabaseKDBX.loadedCipherKey?.let { binaryCipherKey -> - xml.startTag(null, DatabaseKDBXXML.ElemBinaries) - // Use indexes because necessarily (binary header ref is the order) - mDatabaseKDBX.binaryPool.doForEachOrderedBinaryWithoutDuplication { index, binary -> - xml.startTag(null, DatabaseKDBXXML.ElemBinary) - xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString()) - if (binary.getSize() > 0) { - if (binary.isCompressed) { - xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue) - } - try { - // Write the XML - binary.getInputDataStream(binaryCipherKey).use { inputStream -> - inputStream.readAllBytes { buffer -> - xml.text(String(Base64.encode(buffer, BASE_64_FLAG))) - } - } - } catch (e: Exception) { - Log.e(TAG, "Unable to write binary", e) - } + xml.startTag(null, DatabaseKDBXXML.ElemBinaries) + // Use indexes because necessarily (binary header ref is the order) + mDatabaseKDBX.binaryPool.doForEachOrderedBinaryWithoutDuplication { index, binary -> + xml.startTag(null, DatabaseKDBXXML.ElemBinary) + xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString()) + if (binary.getSize() > 0) { + if (binary.isCompressed) { + xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue) + } + try { + // Write the XML + binary.getInputDataStream(mDatabaseKDBX.binaryCache).use { inputStream -> + inputStream.readAllBytes { buffer -> + xml.text(String(Base64.encode(buffer, BASE_64_FLAG))) + } + } + } catch (e: Exception) { + Log.e(TAG, "Unable to write binary", e) } - xml.endTag(null, DatabaseKDBXXML.ElemBinary) } - xml.endTag(null, DatabaseKDBXXML.ElemBinaries) - } ?: Log.e(TAG, "Unable to retrieve cipher key to write binaries") + xml.endTag(null, DatabaseKDBXXML.ElemBinary) + } + xml.endTag(null, DatabaseKDBXXML.ElemBinaries) } @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @@ -699,39 +695,37 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX, @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) private fun writeCustomIconList() { - mDatabaseKDBX.loadedCipherKey?.let { cipherKey -> - var firstElement = true - mDatabaseKDBX.iconsManager.doForEachCustomIcon { iconCustom, binary -> - if (binary.dataExists()) { - // Write the parent tag - if (firstElement) { - xml.startTag(null, DatabaseKDBXXML.ElemCustomIcons) - firstElement = false - } - - xml.startTag(null, DatabaseKDBXXML.ElemCustomIconItem) - - writeUuid(DatabaseKDBXXML.ElemCustomIconItemID, iconCustom.uuid) - var customImageData = ByteArray(0) - try { - binary.getInputDataStream(cipherKey).use { inputStream -> - customImageData = inputStream.readBytes() - } - } catch (e: Exception) { - Log.e(TAG, "Unable to write custom icon", e) - } finally { - writeObject(DatabaseKDBXXML.ElemCustomIconItemData, - String(Base64.encode(customImageData, BASE_64_FLAG))) - } - - xml.endTag(null, DatabaseKDBXXML.ElemCustomIconItem) + var firstElement = true + mDatabaseKDBX.iconsManager.doForEachCustomIcon { iconCustom, binary -> + if (binary.dataExists()) { + // Write the parent tag + if (firstElement) { + xml.startTag(null, DatabaseKDBXXML.ElemCustomIcons) + firstElement = false } + + xml.startTag(null, DatabaseKDBXXML.ElemCustomIconItem) + + writeUuid(DatabaseKDBXXML.ElemCustomIconItemID, iconCustom.uuid) + var customImageData = ByteArray(0) + try { + binary.getInputDataStream(mDatabaseKDBX.binaryCache).use { inputStream -> + customImageData = inputStream.readBytes() + } + } catch (e: Exception) { + Log.e(TAG, "Unable to write custom icon", e) + } finally { + writeObject(DatabaseKDBXXML.ElemCustomIconItemData, + String(Base64.encode(customImageData, BASE_64_FLAG))) + } + + xml.endTag(null, DatabaseKDBXXML.ElemCustomIconItem) } - // Close the parent tag - if (!firstElement) { - xml.endTag(null, DatabaseKDBXXML.ElemCustomIcons) - } - } ?: Log.e(TAG, "Unable to retrieve cipher key to write custom icons") + } + // Close the parent tag + if (!firstElement) { + xml.endTag(null, DatabaseKDBXXML.ElemCustomIcons) + } } private fun safeXmlString(text: String): String { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/EntryOutputKDB.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/EntryOutputKDB.kt index e605c48fe..d82f7edb9 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/output/EntryOutputKDB.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/EntryOutputKDB.kt @@ -19,8 +19,6 @@ */ package com.kunzisoft.keepass.database.file.output -import android.util.Log -import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.database.DatabaseKDB import com.kunzisoft.keepass.database.element.entry.EntryKDB import com.kunzisoft.keepass.database.exception.DatabaseOutputException @@ -34,13 +32,13 @@ import java.nio.charset.Charset /** * Output the GroupKDB to the stream */ -class EntryOutputKDB(private val mEntry: EntryKDB, - private val mOutputStream: OutputStream, - private val mCipherKey: Database.LoadedKey?) { +class EntryOutputKDB(private val mDatabase: DatabaseKDB, + private val mEntry: EntryKDB, + private val mOutputStream: OutputStream) { //NOTE: Need be to careful about using ints. The actual type written to file is a unsigned int @Throws(DatabaseOutputException::class) - fun output(database: DatabaseKDB) { + fun output() { try { // UUID mOutputStream.write(UUID_FIELD_TYPE) @@ -95,22 +93,20 @@ class EntryOutputKDB(private val mEntry: EntryKDB, StringDatabaseKDBUtils.writeStringToStream(mOutputStream, mEntry.binaryDescription) // Binary - mCipherKey?.let { cipherKey -> - mOutputStream.write(BINARY_DATA_FIELD_TYPE) - val binaryData = mEntry.getBinary(database.binaryPool) - val binaryDataLength = binaryData?.getSize() ?: 0L - // Write data length - mOutputStream.write(uIntTo4Bytes(UnsignedInt.fromKotlinLong(binaryDataLength))) - // Write data - if (binaryDataLength > 0) { - binaryData?.getInputDataStream(cipherKey).use { inputStream -> - inputStream?.readAllBytes { buffer -> - mOutputStream.write(buffer) - } - inputStream?.close() + mOutputStream.write(BINARY_DATA_FIELD_TYPE) + val binaryData = mEntry.getBinary(mDatabase.binaryPool) + val binaryDataLength = binaryData?.getSize() ?: 0L + // Write data length + mOutputStream.write(uIntTo4Bytes(UnsignedInt.fromKotlinLong(binaryDataLength))) + // Write data + if (binaryDataLength > 0) { + binaryData?.getInputDataStream(mDatabase.binaryCache).use { inputStream -> + inputStream?.readAllBytes { buffer -> + mOutputStream.write(buffer) } + inputStream?.close() } - } ?: Log.e(TAG, "Unable to retrieve cipher key to write entry binary") + } // End mOutputStream.write(END_FIELD_TYPE) diff --git a/app/src/main/java/com/kunzisoft/keepass/icons/IconDrawableFactory.kt b/app/src/main/java/com/kunzisoft/keepass/icons/IconDrawableFactory.kt index 038bdfe5b..2e9a38046 100644 --- a/app/src/main/java/com/kunzisoft/keepass/icons/IconDrawableFactory.kt +++ b/app/src/main/java/com/kunzisoft/keepass/icons/IconDrawableFactory.kt @@ -33,8 +33,8 @@ import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.drawable.toBitmap import androidx.core.widget.ImageViewCompat import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.database.BinaryData +import com.kunzisoft.keepass.database.element.database.BinaryCache import com.kunzisoft.keepass.database.element.icon.IconImageCustom import com.kunzisoft.keepass.database.element.icon.IconImageDraw import kotlinx.coroutines.CoroutineScope @@ -48,7 +48,7 @@ import kotlin.collections.HashMap /** * Factory class who build database icons dynamically, can assign an icon of IconPack, or a custom icon to an ImageView with a tint */ -class IconDrawableFactory(private val retrieveCipherKey : () -> Database.LoadedKey?, +class IconDrawableFactory(private val retrieveBinaryCache : () -> BinaryCache?, private val retrieveCustomIconBinary : (iconId: UUID) -> BinaryData?) { /** customIconMap @@ -87,13 +87,13 @@ class IconDrawableFactory(private val retrieveCipherKey : () -> Database.LoadedK */ private fun getIconDrawable(resources: Resources, icon: IconImageCustom, iconCustomBinary: BinaryData?): Drawable? { val patternIcon = PatternIcon(resources) - val cipherKey = retrieveCipherKey() - if (cipherKey != null) { + val binaryManager = retrieveBinaryCache() + if (binaryManager != null) { val draw: Drawable? = customIconMap[icon.uuid]?.get() if (draw == null) { iconCustomBinary?.let { binaryFile -> try { - var bitmap: Bitmap? = BitmapFactory.decodeStream(binaryFile.getInputDataStream(cipherKey)) + var bitmap: Bitmap? = BitmapFactory.decodeStream(binaryFile.getInputDataStream(binaryManager)) bitmap?.let { bitmapIcon -> bitmap = resize(bitmapIcon, patternIcon) val createdDraw = BitmapDrawable(resources, bitmap) diff --git a/app/src/main/java/com/kunzisoft/keepass/services/AttachmentFileNotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/services/AttachmentFileNotificationService.kt index 3f9ac7802..9de745620 100644 --- a/app/src/main/java/com/kunzisoft/keepass/services/AttachmentFileNotificationService.kt +++ b/app/src/main/java/com/kunzisoft/keepass/services/AttachmentFileNotificationService.kt @@ -29,6 +29,7 @@ import android.util.Log import androidx.documentfile.provider.DocumentFile import com.kunzisoft.keepass.R import com.kunzisoft.keepass.database.element.Attachment +import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.model.AttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.StreamDirection @@ -48,6 +49,8 @@ class AttachmentFileNotificationService: LockNotificationService() { private val mainScope = CoroutineScope(Dispatchers.Main) + private var mDatabase: Database? = Database.getInstance() + override fun retrieveChannelId(): String { return CHANNEL_ATTACHMENT_ID } @@ -285,11 +288,14 @@ class AttachmentFileNotificationService: LockNotificationService() { // Add action to the list on start attachmentNotificationList.add(attachmentNotification) - mainScope.launch { - AttachmentFileAction(attachmentNotification, - contentResolver).apply { - listener = attachmentFileActionListener - }.executeAction() + mDatabase?.let { database -> + mainScope.launch { + AttachmentFileAction(attachmentNotification, + database, + contentResolver).apply { + listener = attachmentFileActionListener + }.executeAction() + } } } } catch (e: Exception) { @@ -313,6 +319,7 @@ class AttachmentFileNotificationService: LockNotificationService() { private class AttachmentFileAction( private val attachmentNotification: AttachmentNotification, + private val database: Database, private val contentResolver: ContentResolver) { private val updateMinFrequency = 1000 @@ -345,6 +352,7 @@ class AttachmentFileNotificationService: LockNotificationService() { when (streamDirection) { StreamDirection.UPLOAD -> { BinaryDatabaseManager.uploadToDatabase( + database, attachmentNotification.uri, attachment.binaryData, contentResolver, @@ -358,6 +366,7 @@ class AttachmentFileNotificationService: LockNotificationService() { } StreamDirection.DOWNLOAD -> { BinaryDatabaseManager.downloadFromDatabase( + database, attachmentNotification.uri, attachment.binaryData, contentResolver, diff --git a/app/src/main/java/com/kunzisoft/keepass/tasks/BinaryDatabaseManager.kt b/app/src/main/java/com/kunzisoft/keepass/tasks/BinaryDatabaseManager.kt index 8a6387e12..f9b0f0449 100644 --- a/app/src/main/java/com/kunzisoft/keepass/tasks/BinaryDatabaseManager.kt +++ b/app/src/main/java/com/kunzisoft/keepass/tasks/BinaryDatabaseManager.kt @@ -7,6 +7,7 @@ import android.net.Uri import android.util.Log import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.database.BinaryData +import com.kunzisoft.keepass.database.element.database.BinaryCache import com.kunzisoft.keepass.stream.readAllBytes import com.kunzisoft.keepass.utils.UriUtil import kotlinx.coroutines.* @@ -21,41 +22,42 @@ import kotlin.math.pow object BinaryDatabaseManager { - fun downloadFromDatabase(attachmentToUploadUri: Uri, + fun downloadFromDatabase(database: Database, + attachmentToUploadUri: Uri, binaryData: BinaryData, contentResolver: ContentResolver, update: ((percent: Int)->Unit)? = null, canceled: ()-> Boolean = { false }, bufferSize: Int = DEFAULT_BUFFER_SIZE) { UriUtil.getUriOutputStream(contentResolver, attachmentToUploadUri)?.use { outputStream -> - downloadFromDatabase(outputStream, binaryData, update, canceled, bufferSize) + downloadFromDatabase(database.binaryCache, outputStream, binaryData, update, canceled, bufferSize) } } - private fun downloadFromDatabase(outputStream: OutputStream, + private fun downloadFromDatabase(binaryCache: BinaryCache, + outputStream: OutputStream, binaryData: BinaryData, update: ((percent: Int)->Unit)? = null, canceled: ()-> Boolean = { false }, bufferSize: Int = DEFAULT_BUFFER_SIZE) { val fileSize = binaryData.getSize() var dataDownloaded = 0L - Database.getInstance().loadedCipherKey?.let { binaryCipherKey -> - binaryData.getUnGzipInputDataStream(binaryCipherKey).use { inputStream -> - inputStream.readAllBytes(bufferSize, canceled) { buffer -> - outputStream.write(buffer) - dataDownloaded += buffer.size - try { - val percentDownload = (100 * dataDownloaded / fileSize).toInt() - update?.invoke(percentDownload) - } catch (e: Exception) { - Log.w(TAG, "Unable to call update callback during download", e) - } + binaryData.getUnGzipInputDataStream(binaryCache).use { inputStream -> + inputStream.readAllBytes(bufferSize, canceled) { buffer -> + outputStream.write(buffer) + dataDownloaded += buffer.size + try { + val percentDownload = (100 * dataDownloaded / fileSize).toInt() + update?.invoke(percentDownload) + } catch (e: Exception) { + Log.w(TAG, "Unable to call update callback during download", e) } } } } - fun uploadToDatabase(attachmentFromDownloadUri: Uri, + fun uploadToDatabase(database: Database, + attachmentFromDownloadUri: Uri, binaryData: BinaryData, contentResolver: ContentResolver, update: ((percent: Int)->Unit)? = null, @@ -63,34 +65,34 @@ object BinaryDatabaseManager { bufferSize: Int = DEFAULT_BUFFER_SIZE) { val fileSize = contentResolver.openFileDescriptor(attachmentFromDownloadUri, "r")?.statSize ?: 0 UriUtil.getUriInputStream(contentResolver, attachmentFromDownloadUri)?.use { inputStream -> - uploadToDatabase(inputStream, fileSize, binaryData, update, canceled, bufferSize) + uploadToDatabase(database.binaryCache, inputStream, fileSize, binaryData, update, canceled, bufferSize) } } - private fun uploadToDatabase(inputStream: InputStream, + private fun uploadToDatabase(binaryCache: BinaryCache, + inputStream: InputStream, fileSize: Long, binaryData: BinaryData, update: ((percent: Int)->Unit)? = null, canceled: ()-> Boolean = { false }, bufferSize: Int = DEFAULT_BUFFER_SIZE) { var dataUploaded = 0L - Database.getInstance().loadedCipherKey?.let { binaryCipherKey -> - binaryData.getGzipOutputDataStream(binaryCipherKey).use { outputStream -> - inputStream.readAllBytes(bufferSize, canceled) { buffer -> - outputStream.write(buffer) - dataUploaded += buffer.size - try { - val percentDownload = (100 * dataUploaded / fileSize).toInt() - update?.invoke(percentDownload) - } catch (e: Exception) { - Log.w(TAG, "Unable to call update callback during upload", e) - } + binaryData.getGzipOutputDataStream(binaryCache).use { outputStream -> + inputStream.readAllBytes(bufferSize, canceled) { buffer -> + outputStream.write(buffer) + dataUploaded += buffer.size + try { + val percentDownload = (100 * dataUploaded / fileSize).toInt() + update?.invoke(percentDownload) + } catch (e: Exception) { + Log.w(TAG, "Unable to call update callback during upload", e) } } } } fun resizeBitmapAndStoreDataInBinaryFile(contentResolver: ContentResolver, + database: Database, bitmapUri: Uri?, binaryData: BinaryData?) { try { @@ -103,6 +105,7 @@ object BinaryDatabaseManager { val bitmapData: ByteArray = byteArrayOutputStream.toByteArray() val byteArrayInputStream = ByteArrayInputStream(bitmapData) uploadToDatabase( + database.binaryCache, byteArrayInputStream, bitmapData.size.toLong(), binaryData @@ -118,7 +121,6 @@ object BinaryDatabaseManager { /** * reduces the size of the image - * @param image * @param maxSize * @return */ @@ -136,20 +138,18 @@ object BinaryDatabaseManager { return Bitmap.createScaledBitmap(this, width, height, true) } - fun loadBitmap(binaryData: BinaryData, - binaryCipherKey: Database.LoadedKey?, + fun loadBitmap(database: Database, + binaryData: BinaryData, maxWidth: Int, actionOnFinish: (Bitmap?) -> Unit) { CoroutineScope(Dispatchers.Main).launch { withContext(Dispatchers.IO) { val asyncResult: Deferred = async { runCatching { - binaryCipherKey?.let { binaryKey -> - val bitmap: Bitmap? = decodeSampledBitmap(binaryData, - binaryKey, - maxWidth) - bitmap - } + val bitmap: Bitmap? = decodeSampledBitmap(binaryData, + database.binaryCache, + maxWidth) + bitmap }.getOrNull() } withContext(Dispatchers.Main) { @@ -160,13 +160,13 @@ object BinaryDatabaseManager { } private fun decodeSampledBitmap(binaryData: BinaryData, - binaryCipherKey: Database.LoadedKey, + binaryCache: BinaryCache, maxWidth: Int): Bitmap? { // First decode with inJustDecodeBounds=true to check dimensions return BitmapFactory.Options().run { try { inJustDecodeBounds = true - binaryData.getUnGzipInputDataStream(binaryCipherKey).use { + binaryData.getUnGzipInputDataStream(binaryCache).use { BitmapFactory.decodeStream(it, null, this) } // Calculate inSampleSize @@ -178,7 +178,7 @@ object BinaryDatabaseManager { // Decode bitmap with inSampleSize set inJustDecodeBounds = false - binaryData.getUnGzipInputDataStream(binaryCipherKey).use { + binaryData.getUnGzipInputDataStream(binaryCache).use { BitmapFactory.decodeStream(it, null, this) } } catch (e: Exception) { diff --git a/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt b/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt index e7114ef75..41d13788f 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt @@ -322,8 +322,8 @@ class EntryContentsView @JvmOverloads constructor(context: Context, * ------------- */ - fun setAttachmentCipherKey(cipherKey: Database.LoadedKey?) { - attachmentsAdapter.binaryCipherKey = cipherKey + fun setAttachmentCipherKey(database: Database?) { + attachmentsAdapter.database = database } private fun showAttachments(show: Boolean) {