mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Add BinaryByte and BinaryFile
This commit is contained in:
@@ -3,7 +3,7 @@ package com.kunzisoft.keepass.tests.stream
|
||||
import android.content.Context
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.database.BinaryData
|
||||
import com.kunzisoft.keepass.database.element.database.BinaryFile
|
||||
import com.kunzisoft.keepass.stream.readAllBytes
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import junit.framework.TestCase.assertEquals
|
||||
@@ -25,7 +25,7 @@ class BinaryDataTest {
|
||||
|
||||
private val loadedKey = Database.LoadedKey.generateNewCipherKey()
|
||||
|
||||
private fun saveBinary(asset: String, binaryData: BinaryData) {
|
||||
private fun saveBinary(asset: String, binaryData: BinaryFile) {
|
||||
context.assets.open(asset).use { assetInputStream ->
|
||||
binaryData.getOutputDataStream(loadedKey).use { binaryOutputStream ->
|
||||
assetInputStream.readAllBytes(DEFAULT_BUFFER_SIZE) { buffer ->
|
||||
@@ -37,8 +37,8 @@ class BinaryDataTest {
|
||||
|
||||
@Test
|
||||
fun testSaveTextInCache() {
|
||||
val binaryA = BinaryData(fileA)
|
||||
val binaryB = BinaryData(fileB)
|
||||
val binaryA = BinaryFile(fileA)
|
||||
val binaryB = BinaryFile(fileB)
|
||||
saveBinary(TEST_TEXT_ASSET, binaryA)
|
||||
saveBinary(TEST_TEXT_ASSET, binaryB)
|
||||
assertEquals("Save text binary length failed.", binaryA.getSize(), binaryB.getSize())
|
||||
@@ -47,8 +47,8 @@ class BinaryDataTest {
|
||||
|
||||
@Test
|
||||
fun testSaveImageInCache() {
|
||||
val binaryA = BinaryData(fileA)
|
||||
val binaryB = BinaryData(fileB)
|
||||
val binaryA = BinaryFile(fileA)
|
||||
val binaryB = BinaryFile(fileB)
|
||||
saveBinary(TEST_IMAGE_ASSET, binaryA)
|
||||
saveBinary(TEST_IMAGE_ASSET, binaryB)
|
||||
assertEquals("Save image binary length failed.", binaryA.getSize(), binaryB.getSize())
|
||||
@@ -57,9 +57,9 @@ class BinaryDataTest {
|
||||
|
||||
@Test
|
||||
fun testCompressText() {
|
||||
val binaryA = BinaryData(fileA)
|
||||
val binaryB = BinaryData(fileB)
|
||||
val binaryC = BinaryData(fileC)
|
||||
val binaryA = BinaryFile(fileA)
|
||||
val binaryB = BinaryFile(fileB)
|
||||
val binaryC = BinaryFile(fileC)
|
||||
saveBinary(TEST_TEXT_ASSET, binaryA)
|
||||
saveBinary(TEST_TEXT_ASSET, binaryB)
|
||||
saveBinary(TEST_TEXT_ASSET, binaryC)
|
||||
@@ -74,9 +74,9 @@ class BinaryDataTest {
|
||||
|
||||
@Test
|
||||
fun testCompressImage() {
|
||||
val binaryA = BinaryData(fileA)
|
||||
var binaryB = BinaryData(fileB)
|
||||
val binaryC = BinaryData(fileC)
|
||||
val binaryA = BinaryFile(fileA)
|
||||
var binaryB = BinaryFile(fileB)
|
||||
val binaryC = BinaryFile(fileC)
|
||||
saveBinary(TEST_IMAGE_ASSET, binaryA)
|
||||
saveBinary(TEST_IMAGE_ASSET, binaryB)
|
||||
saveBinary(TEST_IMAGE_ASSET, binaryC)
|
||||
@@ -84,7 +84,7 @@ class BinaryDataTest {
|
||||
binaryB.compress(loadedKey)
|
||||
assertEquals("Compress image length failed.", binaryA.getSize(), binaryA.getSize())
|
||||
assertEquals("Compress image failed.", binaryA.binaryHash(), binaryA.binaryHash())
|
||||
binaryB = BinaryData(fileB, true)
|
||||
binaryB = BinaryFile(fileB, true)
|
||||
binaryB.decompress(loadedKey)
|
||||
assertEquals("Decompress image length failed.", binaryB.getSize(), binaryC.getSize())
|
||||
assertEquals("Decompress image failed.", binaryB.binaryHash(), binaryC.binaryHash())
|
||||
@@ -92,7 +92,7 @@ class BinaryDataTest {
|
||||
|
||||
@Test
|
||||
fun testReadText() {
|
||||
val binaryA = BinaryData(fileA)
|
||||
val binaryA = BinaryFile(fileA)
|
||||
saveBinary(TEST_TEXT_ASSET, binaryA)
|
||||
assert(streamAreEquals(context.assets.open(TEST_TEXT_ASSET),
|
||||
binaryA.getInputDataStream(loadedKey)))
|
||||
@@ -100,7 +100,7 @@ class BinaryDataTest {
|
||||
|
||||
@Test
|
||||
fun testReadImage() {
|
||||
val binaryA = BinaryData(fileA)
|
||||
val binaryA = BinaryFile(fileA)
|
||||
saveBinary(TEST_IMAGE_ASSET, binaryA)
|
||||
assert(streamAreEquals(context.assets.open(TEST_IMAGE_ASSET),
|
||||
binaryA.getInputDataStream(loadedKey)))
|
||||
|
||||
@@ -21,6 +21,7 @@ package com.kunzisoft.keepass.database.element
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.database.element.database.BinaryByte
|
||||
import com.kunzisoft.keepass.database.element.database.BinaryData
|
||||
|
||||
|
||||
@@ -29,7 +30,7 @@ data class Attachment(var name: String,
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
parcel.readString() ?: "",
|
||||
parcel.readParcelable(BinaryData::class.java.classLoader) ?: BinaryData()
|
||||
parcel.readParcelable(BinaryData::class.java.classLoader) ?: BinaryByte()
|
||||
)
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.element.database
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import java.io.*
|
||||
|
||||
class BinaryByte : BinaryData {
|
||||
|
||||
private var mDataByte: ByteArray = ByteArray(0)
|
||||
|
||||
/**
|
||||
* Empty protected binary
|
||||
*/
|
||||
constructor() : super()
|
||||
|
||||
constructor(byteArray: ByteArray,
|
||||
compressed: Boolean = false,
|
||||
protected: Boolean = false) : super(compressed, protected) {
|
||||
this.mDataByte = byteArray
|
||||
}
|
||||
|
||||
constructor(parcel: Parcel) : super(parcel) {
|
||||
val byteArray = ByteArray(parcel.readInt())
|
||||
parcel.readByteArray(byteArray)
|
||||
mDataByte = byteArray
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream {
|
||||
return ByteArrayInputStream(mDataByte)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream {
|
||||
return ByteOutputStream()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun getUnGzipInputDataStream(cipherKey: Database.LoadedKey): InputStream {
|
||||
return getInputDataStream(cipherKey)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun getGzipOutputDataStream(cipherKey: Database.LoadedKey): OutputStream {
|
||||
return getOutputDataStream(cipherKey)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun compress(cipherKey: Database.LoadedKey) {
|
||||
// TODO compress
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun decompress(cipherKey: Database.LoadedKey) {
|
||||
// TODO decompress
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun clear() {
|
||||
mDataByte = ByteArray(0)
|
||||
}
|
||||
|
||||
override fun dataExists(): Boolean {
|
||||
return mDataByte.isNotEmpty()
|
||||
}
|
||||
|
||||
override fun getSize(): Long {
|
||||
return mDataByte.size.toLong()
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash of the raw encrypted file in temp folder, only to compare binary data
|
||||
*/
|
||||
override fun binaryHash(): Int {
|
||||
return if (dataExists())
|
||||
mDataByte.contentHashCode()
|
||||
else
|
||||
0
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return mDataByte.toString()
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
super.writeToParcel(dest, flags)
|
||||
dest.writeInt(mDataByte.size)
|
||||
dest.writeByteArray(mDataByte)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is BinaryByte) return false
|
||||
if (!super.equals(other)) return false
|
||||
|
||||
if (!mDataByte.contentEquals(other.mDataByte)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = super.hashCode()
|
||||
result = 31 * result + mDataByte.contentHashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom OutputStream to calculate the size and hash of binary file
|
||||
*/
|
||||
private inner class ByteOutputStream : ByteArrayOutputStream() {
|
||||
override fun close() {
|
||||
mDataByte = this.toByteArray()
|
||||
super.close()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = BinaryByte::class.java.name
|
||||
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<BinaryByte> = object : Parcelable.Creator<BinaryByte> {
|
||||
override fun createFromParcel(parcel: Parcel): BinaryByte {
|
||||
return BinaryByte(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<BinaryByte?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,278 +21,92 @@ package com.kunzisoft.keepass.database.element.database
|
||||
|
||||
import android.os.Parcel
|
||||
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.GZIPInputStream
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.CipherInputStream
|
||||
import javax.crypto.CipherOutputStream
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
class BinaryData : Parcelable {
|
||||
abstract class BinaryData : Parcelable {
|
||||
|
||||
private var mDataFile: File? = null
|
||||
private var mLength: Long = 0
|
||||
private var mBinaryHash = 0
|
||||
var isCompressed: Boolean = false
|
||||
private set
|
||||
protected set
|
||||
var isProtected: Boolean = false
|
||||
private set
|
||||
protected set
|
||||
var isCorrupted: Boolean = false
|
||||
// Cipher to encrypt temp file
|
||||
private var cipherEncryption: Cipher = Cipher.getInstance(Database.LoadedKey.BINARY_CIPHER)
|
||||
private var cipherDecryption: Cipher = Cipher.getInstance(Database.LoadedKey.BINARY_CIPHER)
|
||||
|
||||
/**
|
||||
* Empty protected binary
|
||||
*/
|
||||
constructor()
|
||||
protected constructor()
|
||||
|
||||
constructor(dataFile: File, compressed: Boolean = false, protected: Boolean = false) {
|
||||
this.mDataFile = dataFile
|
||||
this.mLength = 0
|
||||
protected constructor(compressed: Boolean = false, protected: Boolean = false) {
|
||||
this.isCompressed = compressed
|
||||
this.isProtected = protected
|
||||
}
|
||||
|
||||
private constructor(parcel: Parcel) {
|
||||
parcel.readString()?.let {
|
||||
mDataFile = File(it)
|
||||
}
|
||||
mLength = parcel.readLong()
|
||||
protected constructor(parcel: Parcel) {
|
||||
isCompressed = parcel.readByte().toInt() != 0
|
||||
isProtected = parcel.readByte().toInt() != 0
|
||||
isCorrupted = parcel.readByte().toInt() != 0
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream {
|
||||
return buildInputStream(mDataFile, cipherKey)
|
||||
}
|
||||
abstract fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream {
|
||||
return buildOutputStream(mDataFile, cipherKey)
|
||||
}
|
||||
abstract fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun getUnGzipInputDataStream(cipherKey: Database.LoadedKey): InputStream {
|
||||
return if (isCompressed) {
|
||||
GZIPInputStream(getInputDataStream(cipherKey))
|
||||
} else {
|
||||
getInputDataStream(cipherKey)
|
||||
}
|
||||
}
|
||||
abstract fun getUnGzipInputDataStream(cipherKey: Database.LoadedKey): InputStream
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun getGzipOutputDataStream(cipherKey: Database.LoadedKey): OutputStream {
|
||||
return if (isCompressed) {
|
||||
GZIPOutputStream(getOutputDataStream(cipherKey))
|
||||
} else {
|
||||
getOutputDataStream(cipherKey)
|
||||
}
|
||||
}
|
||||
abstract fun getGzipOutputDataStream(cipherKey: Database.LoadedKey): OutputStream
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun buildInputStream(file: File?, cipherKey: Database.LoadedKey): InputStream {
|
||||
return when {
|
||||
file != null && file.length() > 0 -> {
|
||||
cipherDecryption.init(Cipher.DECRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
|
||||
Base64InputStream(CipherInputStream(FileInputStream(file), cipherDecryption), Base64.NO_WRAP)
|
||||
}
|
||||
else -> ByteArrayInputStream(ByteArray(0))
|
||||
}
|
||||
}
|
||||
abstract fun compress(cipherKey: Database.LoadedKey)
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun buildOutputStream(file: File?, cipherKey: Database.LoadedKey): OutputStream {
|
||||
return when {
|
||||
file != null -> {
|
||||
cipherEncryption.init(Cipher.ENCRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
|
||||
BinaryCountingOutputStream(Base64OutputStream(CipherOutputStream(FileOutputStream(file), cipherEncryption), Base64.NO_WRAP))
|
||||
}
|
||||
else -> throw IOException("Unable to write in an unknown file")
|
||||
}
|
||||
}
|
||||
abstract fun decompress(cipherKey: Database.LoadedKey)
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun compress(cipherKey: Database.LoadedKey) {
|
||||
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 ->
|
||||
inputStream.readAllBytes { buffer ->
|
||||
outputStream.write(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove ungzip file
|
||||
if (concreteDataFile.delete()) {
|
||||
if (fileBinaryCompress.renameTo(concreteDataFile)) {
|
||||
// Harmonize with database compression
|
||||
isCompressed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
abstract fun clear()
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun decompress(cipherKey: Database.LoadedKey) {
|
||||
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 ->
|
||||
inputStream.readAllBytes { buffer ->
|
||||
outputStream.write(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove gzip file
|
||||
if (concreteDataFile.delete()) {
|
||||
if (fileBinaryDecompress.renameTo(concreteDataFile)) {
|
||||
// Harmonize with database compression
|
||||
isCompressed = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
abstract fun dataExists(): Boolean
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun clear() {
|
||||
if (mDataFile != null && !mDataFile!!.delete())
|
||||
throw IOException("Unable to delete temp file " + mDataFile!!.absolutePath)
|
||||
}
|
||||
abstract fun getSize(): Long
|
||||
|
||||
fun dataExists(): Boolean {
|
||||
return mDataFile != null && mLength > 0
|
||||
}
|
||||
|
||||
fun getSize(): Long {
|
||||
return mLength
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash of the raw encrypted file in temp folder, only to compare binary data
|
||||
*/
|
||||
@Throws(FileNotFoundException::class)
|
||||
fun binaryHash(): Int {
|
||||
return mBinaryHash
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other)
|
||||
return true
|
||||
if (other == null || javaClass != other.javaClass)
|
||||
return false
|
||||
if (other !is BinaryData)
|
||||
return false
|
||||
|
||||
var sameData = false
|
||||
if (mDataFile != null && mDataFile == other.mDataFile)
|
||||
sameData = true
|
||||
|
||||
return isCompressed == other.isCompressed
|
||||
&& isProtected == other.isProtected
|
||||
&& isCorrupted == other.isCorrupted
|
||||
&& sameData
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
|
||||
var result = 0
|
||||
result = 31 * result + if (isCompressed) 1 else 0
|
||||
result = 31 * result + if (isProtected) 1 else 0
|
||||
result = 31 * result + if (isCorrupted) 1 else 0
|
||||
result = 31 * result + mDataFile!!.hashCode()
|
||||
result = 31 * result + mLength.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return mDataFile.toString()
|
||||
}
|
||||
abstract fun binaryHash(): Int
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeString(mDataFile?.absolutePath)
|
||||
dest.writeLong(mLength)
|
||||
dest.writeByte((if (isCompressed) 1 else 0).toByte())
|
||||
dest.writeByte((if (isProtected) 1 else 0).toByte())
|
||||
dest.writeByte((if (isCorrupted) 1 else 0).toByte())
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom OutputStream to calculate the size and hash of binary file
|
||||
*/
|
||||
private inner class BinaryCountingOutputStream(out: OutputStream): CountingOutputStream(out) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is BinaryData) return false
|
||||
|
||||
private val mMessageDigest: MessageDigest
|
||||
init {
|
||||
mLength = 0
|
||||
mMessageDigest = MessageDigest.getInstance("MD5")
|
||||
mBinaryHash = 0
|
||||
if (isCompressed != other.isCompressed) return false
|
||||
if (isProtected != other.isProtected) return false
|
||||
if (isCorrupted != other.isCorrupted) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun beforeWrite(n: Int) {
|
||||
super.beforeWrite(n)
|
||||
mLength = byteCount
|
||||
}
|
||||
|
||||
override fun write(idx: Int) {
|
||||
super.write(idx)
|
||||
mMessageDigest.update(idx.toByte())
|
||||
}
|
||||
|
||||
override fun write(bts: ByteArray) {
|
||||
super.write(bts)
|
||||
mMessageDigest.update(bts)
|
||||
}
|
||||
|
||||
override fun write(bts: ByteArray, st: Int, end: Int) {
|
||||
super.write(bts, st, end)
|
||||
mMessageDigest.update(bts, st, end)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
super.close()
|
||||
mLength = byteCount
|
||||
val bytes = mMessageDigest.digest()
|
||||
mBinaryHash = ByteBuffer.wrap(bytes).int
|
||||
}
|
||||
override fun hashCode(): Int {
|
||||
var result = isCompressed.hashCode()
|
||||
result = 31 * result + isProtected.hashCode()
|
||||
result = 31 * result + isCorrupted.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = BinaryData::class.java.name
|
||||
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<BinaryData> = object : Parcelable.Creator<BinaryData> {
|
||||
override fun createFromParcel(parcel: Parcel): BinaryData {
|
||||
return BinaryData(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<BinaryData?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.element.database
|
||||
|
||||
import android.os.Parcel
|
||||
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.GZIPInputStream
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.CipherInputStream
|
||||
import javax.crypto.CipherOutputStream
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
|
||||
class BinaryFile : BinaryData {
|
||||
|
||||
private var mDataFile: File? = null
|
||||
private var mLength: Long = 0
|
||||
private var mBinaryHash = 0
|
||||
// Cipher to encrypt temp file
|
||||
@Transient
|
||||
private var cipherEncryption: Cipher = Cipher.getInstance(Database.LoadedKey.BINARY_CIPHER)
|
||||
@Transient
|
||||
private var cipherDecryption: Cipher = Cipher.getInstance(Database.LoadedKey.BINARY_CIPHER)
|
||||
|
||||
constructor() : super()
|
||||
|
||||
constructor(dataFile: File,
|
||||
compressed: Boolean = false,
|
||||
protected: Boolean = false) : super(compressed, protected) {
|
||||
this.mDataFile = dataFile
|
||||
this.mLength = 0
|
||||
this.mBinaryHash = 0
|
||||
}
|
||||
|
||||
constructor(parcel: Parcel) : super(parcel) {
|
||||
parcel.readString()?.let {
|
||||
mDataFile = File(it)
|
||||
}
|
||||
mLength = parcel.readLong()
|
||||
mBinaryHash = parcel.readInt()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream {
|
||||
return buildInputStream(mDataFile, cipherKey)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream {
|
||||
return buildOutputStream(mDataFile, cipherKey)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun getUnGzipInputDataStream(cipherKey: Database.LoadedKey): InputStream {
|
||||
return if (isCompressed) {
|
||||
GZIPInputStream(getInputDataStream(cipherKey))
|
||||
} else {
|
||||
getInputDataStream(cipherKey)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun getGzipOutputDataStream(cipherKey: Database.LoadedKey): OutputStream {
|
||||
return if (isCompressed) {
|
||||
GZIPOutputStream(getOutputDataStream(cipherKey))
|
||||
} else {
|
||||
getOutputDataStream(cipherKey)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun buildInputStream(file: File?, cipherKey: Database.LoadedKey): InputStream {
|
||||
return when {
|
||||
file != null && file.length() > 0 -> {
|
||||
cipherDecryption.init(Cipher.DECRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
|
||||
Base64InputStream(CipherInputStream(FileInputStream(file), cipherDecryption), Base64.NO_WRAP)
|
||||
}
|
||||
else -> ByteArrayInputStream(ByteArray(0))
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun buildOutputStream(file: File?, cipherKey: Database.LoadedKey): OutputStream {
|
||||
return when {
|
||||
file != null -> {
|
||||
cipherEncryption.init(Cipher.ENCRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
|
||||
BinaryCountingOutputStream(Base64OutputStream(CipherOutputStream(FileOutputStream(file), cipherEncryption), Base64.NO_WRAP))
|
||||
}
|
||||
else -> throw IOException("Unable to write in an unknown file")
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun compress(cipherKey: Database.LoadedKey) {
|
||||
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 ->
|
||||
inputStream.readAllBytes { buffer ->
|
||||
outputStream.write(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove ungzip file
|
||||
if (concreteDataFile.delete()) {
|
||||
if (fileBinaryCompress.renameTo(concreteDataFile)) {
|
||||
// Harmonize with database compression
|
||||
isCompressed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun decompress(cipherKey: Database.LoadedKey) {
|
||||
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 ->
|
||||
inputStream.readAllBytes { buffer ->
|
||||
outputStream.write(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove gzip file
|
||||
if (concreteDataFile.delete()) {
|
||||
if (fileBinaryDecompress.renameTo(concreteDataFile)) {
|
||||
// Harmonize with database compression
|
||||
isCompressed = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun clear() {
|
||||
if (mDataFile != null && !mDataFile!!.delete())
|
||||
throw IOException("Unable to delete temp file " + mDataFile!!.absolutePath)
|
||||
}
|
||||
|
||||
override fun dataExists(): Boolean {
|
||||
return mDataFile != null && mLength > 0
|
||||
}
|
||||
|
||||
override fun getSize(): Long {
|
||||
return mLength
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash of the raw encrypted file in temp folder, only to compare binary data
|
||||
*/
|
||||
@Throws(FileNotFoundException::class)
|
||||
override fun binaryHash(): Int {
|
||||
return mBinaryHash
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return mDataFile.toString()
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
super.writeToParcel(dest, flags)
|
||||
dest.writeString(mDataFile?.absolutePath)
|
||||
dest.writeLong(mLength)
|
||||
dest.writeInt(mBinaryHash)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is BinaryFile) return false
|
||||
if (!super.equals(other)) return false
|
||||
|
||||
return mDataFile != null && mDataFile == other.mDataFile
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = super.hashCode()
|
||||
result = 31 * result + (mDataFile?.hashCode() ?: 0)
|
||||
result = 31 * result + mLength.hashCode()
|
||||
result = 31 * result + mBinaryHash
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom OutputStream to calculate the size and hash of binary file
|
||||
*/
|
||||
private inner class BinaryCountingOutputStream(out: OutputStream): CountingOutputStream(out) {
|
||||
|
||||
private val mMessageDigest: MessageDigest
|
||||
init {
|
||||
mLength = 0
|
||||
mMessageDigest = MessageDigest.getInstance("MD5")
|
||||
mBinaryHash = 0
|
||||
}
|
||||
|
||||
override fun beforeWrite(n: Int) {
|
||||
super.beforeWrite(n)
|
||||
mLength = byteCount
|
||||
}
|
||||
|
||||
override fun write(idx: Int) {
|
||||
super.write(idx)
|
||||
mMessageDigest.update(idx.toByte())
|
||||
}
|
||||
|
||||
override fun write(bts: ByteArray) {
|
||||
super.write(bts)
|
||||
mMessageDigest.update(bts)
|
||||
}
|
||||
|
||||
override fun write(bts: ByteArray, st: Int, end: Int) {
|
||||
super.write(bts, st, end)
|
||||
mMessageDigest.update(bts, st, end)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
super.close()
|
||||
mLength = byteCount
|
||||
val bytes = mMessageDigest.digest()
|
||||
mBinaryHash = ByteBuffer.wrap(bytes).int
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = BinaryFile::class.java.name
|
||||
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<BinaryFile> = object : Parcelable.Creator<BinaryFile> {
|
||||
override fun createFromParcel(parcel: Parcel): BinaryFile {
|
||||
return BinaryFile(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<BinaryFile?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -49,7 +49,7 @@ abstract class BinaryPool<T> {
|
||||
protection: Boolean = false): KeyBinary<T> {
|
||||
val fileInCache = File(cacheDirectory, "$poolId$creationId$binaryFileIncrement")
|
||||
binaryFileIncrement++
|
||||
val newBinaryFile = BinaryData(fileInCache, compression, protection)
|
||||
val newBinaryFile = BinaryFile(fileInCache, compression, protection)
|
||||
val newKey = put(key, newBinaryFile)
|
||||
return KeyBinary(newBinaryFile, newKey)
|
||||
}
|
||||
|
||||
@@ -278,7 +278,7 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
// Generate an unique new file
|
||||
val fileInCache = File(cacheDirectory, binaryIncrement.toString())
|
||||
binaryIncrement++
|
||||
return BinaryData(fileInCache)
|
||||
return BinaryFile(fileInCache)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.model
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.database.BinaryData
|
||||
import com.kunzisoft.keepass.database.element.database.BinaryByte
|
||||
import com.kunzisoft.keepass.utils.readEnum
|
||||
import com.kunzisoft.keepass.utils.writeEnum
|
||||
|
||||
@@ -33,7 +33,7 @@ data class EntryAttachmentState(var attachment: Attachment,
|
||||
var previewState: AttachmentState = AttachmentState.NULL) : Parcelable {
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
parcel.readParcelable(Attachment::class.java.classLoader) ?: Attachment("", BinaryData()),
|
||||
parcel.readParcelable(Attachment::class.java.classLoader) ?: Attachment("", BinaryByte()),
|
||||
parcel.readEnum<StreamDirection>() ?: StreamDirection.DOWNLOAD,
|
||||
parcel.readEnum<AttachmentState>() ?: AttachmentState.NULL,
|
||||
parcel.readInt(),
|
||||
|
||||
Reference in New Issue
Block a user