Fix OOM by stream implementation and add KDBX version for DatabaseV2

This commit is contained in:
J-Jamet
2019-11-26 17:21:33 +01:00
parent 32343dc937
commit cc5b96f539
10 changed files with 224 additions and 215 deletions

View File

@@ -20,6 +20,7 @@
android:allowBackup="true" android:allowBackup="true"
android:fullBackupContent="@xml/backup" android:fullBackupContent="@xml/backup"
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent" android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
android:largeHeap="true"
android:theme="@style/KeepassDXStyle.Night"> android:theme="@style/KeepassDXStyle.Night">
<!-- TODO backup API Key --> <!-- TODO backup API Key -->
<meta-data <meta-data

View File

@@ -31,6 +31,8 @@ import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
import com.kunzisoft.keepass.database.element.PwDatabaseV3.Companion.BACKUP_FOLDER_TITLE import com.kunzisoft.keepass.database.element.PwDatabaseV3.Companion.BACKUP_FOLDER_TITLE
import com.kunzisoft.keepass.database.exception.UnknownKDF import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.file.PwDbHeaderV4.Companion.FILE_VERSION_32_3
import com.kunzisoft.keepass.database.file.PwDbHeaderV4.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.utils.VariantDictionary import com.kunzisoft.keepass.utils.VariantDictionary
import org.w3c.dom.Node import org.w3c.dom.Node
import org.w3c.dom.Text import org.w3c.dom.Text
@@ -56,6 +58,7 @@ class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
private var numKeyEncRounds: Long = 0 private var numKeyEncRounds: Long = 0
var publicCustomData = VariantDictionary() var publicCustomData = VariantDictionary()
var kdbxVersion: Long = 0
var name = "" var name = ""
var nameChanged = PwDate() var nameChanged = PwDate()
// TODO change setting date // TODO change setting date
@@ -116,7 +119,14 @@ class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
} }
override val version: String override val version: String
get() = "KeePass 2" get() {
val kdbxStringVersion = when(kdbxVersion) {
FILE_VERSION_32_3 -> "3.1"
FILE_VERSION_32_4 -> "4.0"
else -> "UNKNOWN"
}
return "KeePass 2 - KDBX$kdbxStringVersion"
}
override val kdfEngine: KdfEngine? override val kdfEngine: KdfEngine?
get() = try { get() = try {

View File

@@ -49,7 +49,7 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
var iconCustom = PwIconCustom.UNKNOWN_ICON var iconCustom = PwIconCustom.UNKNOWN_ICON
private var customData = HashMap<String, String>() private var customData = HashMap<String, String>()
var fields = HashMap<String, ProtectedString>() var fields = HashMap<String, ProtectedString>()
val binaries = HashMap<String, ProtectedBinary>() var binaries = HashMap<String, ProtectedBinary>()
var foregroundColor = "" var foregroundColor = ""
var backgroundColor = "" var backgroundColor = ""
var overrideURL = "" var overrideURL = ""
@@ -98,7 +98,7 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
locationChanged = parcel.readParcelable(PwDate::class.java.classLoader) ?: locationChanged locationChanged = parcel.readParcelable(PwDate::class.java.classLoader) ?: locationChanged
customData = ParcelableUtil.readStringParcelableMap(parcel) customData = ParcelableUtil.readStringParcelableMap(parcel)
fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java) fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java)
// TODO binaries = ParcelableUtil.readStringParcelableMap(parcel, ProtectedBinary.class); binaries = ParcelableUtil.readStringParcelableMap(parcel, ProtectedBinary::class.java)
foregroundColor = parcel.readString() ?: foregroundColor foregroundColor = parcel.readString() ?: foregroundColor
backgroundColor = parcel.readString() ?: backgroundColor backgroundColor = parcel.readString() ?: backgroundColor
overrideURL = parcel.readString() ?: overrideURL overrideURL = parcel.readString() ?: overrideURL
@@ -116,7 +116,7 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
dest.writeParcelable(locationChanged, flags) dest.writeParcelable(locationChanged, flags)
ParcelableUtil.writeStringParcelableMap(dest, customData) ParcelableUtil.writeStringParcelableMap(dest, customData)
ParcelableUtil.writeStringParcelableMap(dest, flags, fields) ParcelableUtil.writeStringParcelableMap(dest, flags, fields)
// TODO ParcelableUtil.writeStringParcelableMap(dest, flags, binaries); ParcelableUtil.writeStringParcelableMap(dest, flags, binaries)
dest.writeString(foregroundColor) dest.writeString(foregroundColor)
dest.writeString(backgroundColor) dest.writeString(backgroundColor)
dest.writeString(overrideURL) dest.writeString(overrideURL)

View File

@@ -22,18 +22,14 @@ package com.kunzisoft.keepass.database.element.security
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import android.util.Log import android.util.Log
import java.io.*
import java.io.ByteArrayInputStream
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.io.InputStream
import java.util.Arrays import java.util.Arrays
class ProtectedBinary : Parcelable { class ProtectedBinary : Parcelable {
var isCompressed: Boolean? = null // Only for KDBX3.1-
var isProtected: Boolean = false var isProtected: Boolean = false
private set
private var data: ByteArray? = null private var data: ByteArray? = null
private var dataFile: File? = null private var dataFile: File? = null
@@ -49,30 +45,36 @@ class ProtectedBinary : Parcelable {
* Empty protected binary * Empty protected binary
*/ */
constructor() { constructor() {
this.isCompressed = null
this.isProtected = false this.isProtected = false
this.data = null this.data = null
this.dataFile = null this.dataFile = null
} }
constructor(protectedBinary: ProtectedBinary) { constructor(protectedBinary: ProtectedBinary) {
this.isCompressed = protectedBinary.isCompressed
this.isProtected = protectedBinary.isProtected this.isProtected = protectedBinary.isProtected
this.data = protectedBinary.data this.data = protectedBinary.data
this.dataFile = protectedBinary.dataFile this.dataFile = protectedBinary.dataFile
} }
constructor(enableProtection: Boolean, data: ByteArray?) { constructor(data: ByteArray?, enableProtection: Boolean = false, compressed: Boolean? = null) {
this.isCompressed = compressed
this.isProtected = enableProtection this.isProtected = enableProtection
this.data = data this.data = data
this.dataFile = null this.dataFile = null
} }
constructor(enableProtection: Boolean, dataFile: File) { constructor(dataFile: File, enableProtection: Boolean = false, compressed: Boolean? = null) {
this.isCompressed = compressed
this.isProtected = enableProtection this.isProtected = enableProtection
this.data = null this.data = null
this.dataFile = dataFile this.dataFile = dataFile
} }
private constructor(parcel: Parcel) { private constructor(parcel: Parcel) {
val compressedByte = parcel.readByte().toInt()
isCompressed = if (compressedByte == 2) null else compressedByte != 0
isProtected = parcel.readByte().toInt() != 0 isProtected = parcel.readByte().toInt() != 0
data = ByteArray(parcel.readInt()) data = ByteArray(parcel.readInt())
parcel.readByteArray(data) parcel.readByteArray(data)
@@ -80,11 +82,11 @@ class ProtectedBinary : Parcelable {
} }
@Throws(IOException::class) @Throws(IOException::class)
fun getData(): InputStream? { fun getInputDataStream(): InputStream {
return when { return when {
data != null -> ByteArrayInputStream(data) data != null -> ByteArrayInputStream(data)
dataFile != null -> FileInputStream(dataFile!!) dataFile != null -> FileInputStream(dataFile!!)
else -> null else -> throw IOException("Unable to get binary data")
} }
} }
@@ -111,12 +113,15 @@ class ProtectedBinary : Parcelable {
&& dataFile == null && other.dataFile == null) && dataFile == null && other.dataFile == null)
sameData = true sameData = true
return isProtected == other.isProtected && sameData return isCompressed == other.isCompressed
&& isProtected == other.isProtected
&& sameData
} }
override fun hashCode(): Int { override fun hashCode(): Int {
var result = 0 var result = 0
result = 31 * result + if (isCompressed == null) 2 else if (isCompressed!!) 1 else 0
result = 31 * result + if (isProtected) 1 else 0 result = 31 * result + if (isProtected) 1 else 0
result = 31 * result + dataFile!!.hashCode() result = 31 * result + dataFile!!.hashCode()
result = 31 * result + Arrays.hashCode(data) result = 31 * result + Arrays.hashCode(data)
@@ -128,6 +133,7 @@ class ProtectedBinary : Parcelable {
} }
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeByte((if (isCompressed == null) 2 else if (isCompressed!!) 1 else 0).toByte())
dest.writeByte((if (isProtected) 1 else 0).toByte()) dest.writeByte((if (isProtected) 1 else 0).toByte())
dest.writeInt(data?.size ?: 0) dest.writeInt(data?.size ?: 0)
dest.writeByteArray(data) dest.writeByteArray(data)

View File

@@ -32,13 +32,9 @@ import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.* import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.KDBX4DateUtil import com.kunzisoft.keepass.database.file.KDBX4DateUtil
import com.kunzisoft.keepass.database.file.PwDbHeaderV4 import com.kunzisoft.keepass.database.file.PwDbHeaderV4
import com.kunzisoft.keepass.stream.BetterCipherInputStream import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.stream.HashedBlockInputStream
import com.kunzisoft.keepass.stream.HmacBlockInputStream
import com.kunzisoft.keepass.stream.LEDataInputStream
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import org.apache.commons.io.IOUtils
import org.spongycastle.crypto.StreamCipher import org.spongycastle.crypto.StreamCipher
import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException import org.xmlpull.v1.XmlPullParserException
@@ -58,7 +54,6 @@ class ImporterV4(private val streamDir: File,
private lateinit var mDatabase: PwDatabaseV4 private lateinit var mDatabase: PwDatabaseV4
private var hashOfHeader: ByteArray? = null private var hashOfHeader: ByteArray? = null
private var version: Long = 0
private val unusedCacheFileName: String private val unusedCacheFileName: String
get() = mDatabase.binPool.findUnusedKey().toString() get() = mDatabase.binPool.findUnusedKey().toString()
@@ -102,7 +97,7 @@ class ImporterV4(private val streamDir: File,
val header = PwDbHeaderV4(mDatabase) val header = PwDbHeaderV4(mDatabase)
val headerAndHash = header.loadFromFile(databaseInputStream) val headerAndHash = header.loadFromFile(databaseInputStream)
version = header.version mDatabase.kdbxVersion = header.version
hashOfHeader = headerAndHash.hash hashOfHeader = headerAndHash.hash
val pbHeader = headerAndHash.header val pbHeader = headerAndHash.header
@@ -124,7 +119,7 @@ class ImporterV4(private val streamDir: File,
} }
val isPlain: InputStream val isPlain: InputStream
if (version < PwDbHeaderV4.FILE_VERSION_32_4) { if (mDatabase.kdbxVersion < PwDbHeaderV4.FILE_VERSION_32_4) {
val decrypted = attachCipherStream(databaseInputStream, cipher) val decrypted = attachCipherStream(databaseInputStream, cipher)
val dataDecrypted = LEDataInputStream(decrypted) val dataDecrypted = LEDataInputStream(decrypted)
@@ -172,7 +167,7 @@ class ImporterV4(private val streamDir: File,
else -> isPlain else -> isPlain
} }
if (version >= PwDbHeaderV4.FILE_VERSION_32_4) { if (mDatabase.kdbxVersion >= PwDbHeaderV4.FILE_VERSION_32_4) {
loadInnerHeader(inputStreamXml, header) loadInnerHeader(inputStreamXml, header)
} }
@@ -241,9 +236,13 @@ class ImporterV4(private val streamDir: File,
// Read in a file // Read in a file
val file = File(streamDir, unusedCacheFileName) val file = File(streamDir, unusedCacheFileName)
FileOutputStream(file).use { outputStream -> FileOutputStream(file).use { outputStream ->
dataInputStream.readBytes(byteLength) { outputStream.write(it) } dataInputStream.readBytes(byteLength, object : ReadBytes {
override fun read(buffer: ByteArray) {
outputStream.write(buffer)
}
})
} }
val protectedBinary = ProtectedBinary(protectedFlag, file) val protectedBinary = ProtectedBinary(file, protectedFlag)
mDatabase.binPool.add(protectedBinary) mDatabase.binPool.add(protectedBinary)
} }
else -> { else -> {
@@ -432,7 +431,7 @@ class ImporterV4(private val streamDir: File,
KdbContext.Binaries -> if (name.equals(PwDatabaseV4XML.ElemBinary, ignoreCase = true)) { KdbContext.Binaries -> if (name.equals(PwDatabaseV4XML.ElemBinary, ignoreCase = true)) {
val key = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrId) val key = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrId)
if (key != null) { if (key != null) {
val pbData = readProtectedBinary(xpp) val pbData = readBinary(xpp)
val id = Integer.parseInt(key) val id = Integer.parseInt(key)
mDatabase.binPool.put(id, pbData!!) mDatabase.binPool.put(id, pbData!!)
} else { } else {
@@ -606,7 +605,7 @@ class ImporterV4(private val streamDir: File,
KdbContext.EntryBinary -> if (name.equals(PwDatabaseV4XML.ElemKey, ignoreCase = true)) { KdbContext.EntryBinary -> if (name.equals(PwDatabaseV4XML.ElemKey, ignoreCase = true)) {
ctxBinaryName = readString(xpp) ctxBinaryName = readString(xpp)
} else if (name.equals(PwDatabaseV4XML.ElemValue, ignoreCase = true)) { } else if (name.equals(PwDatabaseV4XML.ElemValue, ignoreCase = true)) {
ctxBinaryValue = readProtectedBinary(xpp) ctxBinaryValue = readBinary(xpp)
} }
KdbContext.EntryAutoType -> if (name.equals(PwDatabaseV4XML.ElemAutoTypeEnabled, ignoreCase = true)) { KdbContext.EntryAutoType -> if (name.equals(PwDatabaseV4XML.ElemAutoTypeEnabled, ignoreCase = true)) {
@@ -806,7 +805,7 @@ class ImporterV4(private val streamDir: File,
val sDate = readString(xpp) val sDate = readString(xpp)
var utcDate: Date? = null var utcDate: Date? = null
if (version >= PwDbHeaderV4.FILE_VERSION_32_4) { if (mDatabase.kdbxVersion >= PwDbHeaderV4.FILE_VERSION_32_4) {
var buf = Base64.decode(sDate, BASE_64_FLAG) var buf = Base64.decode(sDate, BASE_64_FLAG)
if (buf.size != 8) { if (buf.size != 8) {
val buf8 = ByteArray(8) val buf8 = ByteArray(8)
@@ -939,7 +938,9 @@ class ImporterV4(private val streamDir: File,
} }
@Throws(XmlPullParserException::class, IOException::class) @Throws(XmlPullParserException::class, IOException::class)
private fun readProtectedBinary(xpp: XmlPullParser): ProtectedBinary? { private fun readBinary(xpp: XmlPullParser): ProtectedBinary? {
// Reference Id to a binary already present in binary pool
val ref = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrRef) val ref = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrRef)
if (ref != null) { if (ref != null) {
xpp.next() // Consume end tag xpp.next() // Consume end tag
@@ -948,41 +949,38 @@ class ImporterV4(private val streamDir: File,
return mDatabase.binPool[id] return mDatabase.binPool[id]
} }
var compressed = false // New binary to retrieve
var protected = false else {
var compressed: Boolean? = null
var protected = false
if (xpp.attributeCount > 0) { if (xpp.attributeCount > 0) {
val compress = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrCompressed) val compress = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrCompressed)
if (compress != null) { if (compress != null) {
compressed = compress.equals(PwDatabaseV4XML.ValTrue, ignoreCase = true) compressed = compress.equals(PwDatabaseV4XML.ValTrue, ignoreCase = true)
}
val protect = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrProtected)
if (protect != null) {
protected = protect.equals(PwDatabaseV4XML.ValTrue, ignoreCase = true)
}
}
val base64 = readString(xpp)
if (base64.isEmpty())
return ProtectedBinary()
val data = Base64.decode(base64, BASE_64_FLAG)
return if (!compressed && data.size <= BUFFER_SIZE_BYTES) {
// Small data, don't need a file
ProtectedBinary(protected, data)
} else {
val file = File(streamDir, unusedCacheFileName)
if (compressed) {
FileOutputStream(file).use { outputStream ->
IOUtils.copy(GZIPInputStream(ByteArrayInputStream(data)), outputStream)
} }
val protect = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrProtected)
if (protect != null) {
protected = protect.equals(PwDatabaseV4XML.ValTrue, ignoreCase = true)
}
}
val base64 = readString(xpp)
if (base64.isEmpty())
return ProtectedBinary()
val data = Base64.decode(base64, BASE_64_FLAG)
return if (data.size <= BUFFER_SIZE_BYTES) {
// Small data, don't need a file
ProtectedBinary(data, protected, compressed)
} else { } else {
val file = File(streamDir, unusedCacheFileName)
FileOutputStream(file).use { outputStream -> FileOutputStream(file).use { outputStream ->
outputStream.write(data) outputStream.write(data)
} }
ProtectedBinary(file, protected, compressed)
} }
ProtectedBinary(protected, file)
} }
} }

View File

@@ -22,10 +22,10 @@ package com.kunzisoft.keepass.database.file.save
import com.kunzisoft.keepass.database.element.PwDatabaseV4 import com.kunzisoft.keepass.database.element.PwDatabaseV4
import com.kunzisoft.keepass.database.element.PwDatabaseV4.Companion.BUFFER_SIZE_BYTES import com.kunzisoft.keepass.database.element.PwDatabaseV4.Companion.BUFFER_SIZE_BYTES
import com.kunzisoft.keepass.database.file.PwDbHeaderV4 import com.kunzisoft.keepass.database.file.PwDbHeaderV4
import com.kunzisoft.keepass.stream.ActionReadBytes import com.kunzisoft.keepass.stream.ReadBytes
import com.kunzisoft.keepass.stream.LEDataOutputStream import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.stream.readFromStream
import java.io.IOException import java.io.IOException
import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import kotlin.experimental.or import kotlin.experimental.or
@@ -58,32 +58,16 @@ class PwDbInnerHeaderOutputV4(private val database: PwDatabaseV4,
dataOutputStream.writeInt(protectedBinary.length().toInt() + 1) // TODO verify dataOutputStream.writeInt(protectedBinary.length().toInt() + 1) // TODO verify
dataOutputStream.write(flag.toInt()) dataOutputStream.write(flag.toInt())
protectedBinary.getData()?.let { readFromStream(protectedBinary.getInputDataStream(), BUFFER_SIZE_BYTES,
readBytes(it, ActionReadBytes { buffer -> object : ReadBytes {
dataOutputStream.write(buffer) override fun read(buffer: ByteArray) {
}) dataOutputStream.write(buffer)
} ?: throw IOException("Can't write protected binary") }
}
)
} }
dataOutputStream.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.EndOfHeader.toInt()) dataOutputStream.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.EndOfHeader.toInt())
dataOutputStream.writeInt(0) dataOutputStream.writeInt(0)
} }
@Throws(IOException::class)
fun readBytes(inputStream: InputStream, actionReadBytes: ActionReadBytes) {
val buffer = ByteArray(BUFFER_SIZE_BYTES)
var read = 0
while (read != -1) {
read = inputStream.read(buffer, 0, buffer.size)
if (read != -1) {
val optimizedBuffer: ByteArray = if (buffer.size == read) {
buffer
} else {
buffer.copyOf(read)
}
actionReadBytes.doAction(optimizedBuffer)
}
}
}
} }

View File

@@ -37,9 +37,7 @@ import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.exception.UnknownKDF import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.file.KDBX4DateUtil import com.kunzisoft.keepass.database.file.KDBX4DateUtil
import com.kunzisoft.keepass.database.file.PwDbHeaderV4 import com.kunzisoft.keepass.database.file.PwDbHeaderV4
import com.kunzisoft.keepass.stream.HashedBlockOutputStream import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.stream.HmacBlockOutputStream
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import org.joda.time.DateTime import org.joda.time.DateTime
import org.spongycastle.crypto.StreamCipher import org.spongycastle.crypto.StreamCipher
@@ -53,7 +51,8 @@ import javax.crypto.Cipher
import javax.crypto.CipherOutputStream import javax.crypto.CipherOutputStream
class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputStream) : PwDbOutput<PwDbHeaderV4>(outputStream) { class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4,
outputStream: OutputStream) : PwDbOutput<PwDbHeaderV4>(outputStream) {
private var randomStream: StreamCipher? = null private var randomStream: StreamCipher? = null
private lateinit var xml: XmlSerializer private lateinit var xml: XmlSerializer
@@ -75,17 +74,16 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
header = outputHeader(mOS) header = outputHeader(mOS)
val osPlain: OutputStream val osPlain: OutputStream
if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4) { osPlain = if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4) {
val cos = attachStreamEncryptor(header!!, mOS) val cos = attachStreamEncryptor(header!!, mOS)
cos.write(header!!.streamStartBytes) cos.write(header!!.streamStartBytes)
osPlain = HashedBlockOutputStream(cos) HashedBlockOutputStream(cos)
} else { } else {
mOS.write(hashOfHeader!!) mOS.write(hashOfHeader!!)
mOS.write(headerHmac!!) mOS.write(headerHmac!!)
val hbos = HmacBlockOutputStream(mOS, mDatabaseV4.hmacKey) attachStreamEncryptor(header!!, HmacBlockOutputStream(mOS, mDatabaseV4.hmacKey))
osPlain = attachStreamEncryptor(header!!, hbos)
} }
val osXml: OutputStream val osXml: OutputStream
@@ -114,11 +112,11 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun outputDatabase(os: OutputStream) { private fun outputDatabase(outputStream: OutputStream) {
xml = Xml.newSerializer() xml = Xml.newSerializer()
xml.setOutput(os, "UTF-8") xml.setOutput(outputStream, "UTF-8")
xml.startDocument("UTF-8", true) xml.startDocument("UTF-8", true)
xml.startTag(null, PwDatabaseV4XML.ElemDocNode) xml.startTag(null, PwDatabaseV4XML.ElemDocNode)
@@ -210,17 +208,17 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
writeCustomIconList() writeCustomIconList()
writeObject(PwDatabaseV4XML.ElemRecycleBinEnabled, mDatabaseV4.isRecycleBinEnabled) writeObject(PwDatabaseV4XML.ElemRecycleBinEnabled, mDatabaseV4.isRecycleBinEnabled)
writeObject(PwDatabaseV4XML.ElemRecycleBinUuid, mDatabaseV4.recycleBinUUID) writeUuid(PwDatabaseV4XML.ElemRecycleBinUuid, mDatabaseV4.recycleBinUUID)
writeObject(PwDatabaseV4XML.ElemRecycleBinChanged, mDatabaseV4.recycleBinChanged) writeObject(PwDatabaseV4XML.ElemRecycleBinChanged, mDatabaseV4.recycleBinChanged)
writeObject(PwDatabaseV4XML.ElemEntryTemplatesGroup, mDatabaseV4.entryTemplatesGroup) writeUuid(PwDatabaseV4XML.ElemEntryTemplatesGroup, mDatabaseV4.entryTemplatesGroup)
writeObject(PwDatabaseV4XML.ElemEntryTemplatesGroupChanged, mDatabaseV4.entryTemplatesGroupChanged.date) writeObject(PwDatabaseV4XML.ElemEntryTemplatesGroupChanged, mDatabaseV4.entryTemplatesGroupChanged.date)
writeObject(PwDatabaseV4XML.ElemHistoryMaxItems, mDatabaseV4.historyMaxItems.toLong()) writeObject(PwDatabaseV4XML.ElemHistoryMaxItems, mDatabaseV4.historyMaxItems.toLong())
writeObject(PwDatabaseV4XML.ElemHistoryMaxSize, mDatabaseV4.historyMaxSize) writeObject(PwDatabaseV4XML.ElemHistoryMaxSize, mDatabaseV4.historyMaxSize)
writeObject(PwDatabaseV4XML.ElemLastSelectedGroup, mDatabaseV4.lastSelectedGroupUUID) writeUuid(PwDatabaseV4XML.ElemLastSelectedGroup, mDatabaseV4.lastSelectedGroupUUID)
writeObject(PwDatabaseV4XML.ElemLastTopVisibleGroup, mDatabaseV4.lastTopVisibleGroupUUID) writeUuid(PwDatabaseV4XML.ElemLastTopVisibleGroup, mDatabaseV4.lastTopVisibleGroupUUID)
if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4) { if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4) {
writeBinPool() writeBinariesKDBX31()
} }
writeCustomData(mDatabaseV4.customData) writeCustomData(mDatabaseV4.customData)
@@ -306,13 +304,13 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun startGroup(group: PwGroupV4) { private fun startGroup(group: PwGroupV4) {
xml.startTag(null, PwDatabaseV4XML.ElemGroup) xml.startTag(null, PwDatabaseV4XML.ElemGroup)
writeObject(PwDatabaseV4XML.ElemUuid, group.id) writeUuid(PwDatabaseV4XML.ElemUuid, group.id)
writeObject(PwDatabaseV4XML.ElemName, group.title) writeObject(PwDatabaseV4XML.ElemName, group.title)
writeObject(PwDatabaseV4XML.ElemNotes, group.notes) writeObject(PwDatabaseV4XML.ElemNotes, group.notes)
writeObject(PwDatabaseV4XML.ElemIcon, group.icon.iconId.toLong()) writeObject(PwDatabaseV4XML.ElemIcon, group.icon.iconId.toLong())
if (group.iconCustom != PwIconCustom.UNKNOWN_ICON) { if (group.iconCustom != PwIconCustom.UNKNOWN_ICON) {
writeObject(PwDatabaseV4XML.ElemCustomIconID, group.iconCustom.uuid) writeUuid(PwDatabaseV4XML.ElemCustomIconID, group.iconCustom.uuid)
} }
writeTimes(group) writeTimes(group)
@@ -320,7 +318,7 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
writeObject(PwDatabaseV4XML.ElemGroupDefaultAutoTypeSeq, group.defaultAutoTypeSequence) writeObject(PwDatabaseV4XML.ElemGroupDefaultAutoTypeSeq, group.defaultAutoTypeSequence)
writeObject(PwDatabaseV4XML.ElemEnableAutoType, group.enableAutoType) writeObject(PwDatabaseV4XML.ElemEnableAutoType, group.enableAutoType)
writeObject(PwDatabaseV4XML.ElemEnableSearching, group.enableSearching) writeObject(PwDatabaseV4XML.ElemEnableSearching, group.enableSearching)
writeObject(PwDatabaseV4XML.ElemLastTopVisibleEntry, group.lastTopVisibleEntry) writeUuid(PwDatabaseV4XML.ElemLastTopVisibleEntry, group.lastTopVisibleEntry)
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
@@ -333,11 +331,11 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
xml.startTag(null, PwDatabaseV4XML.ElemEntry) xml.startTag(null, PwDatabaseV4XML.ElemEntry)
writeObject(PwDatabaseV4XML.ElemUuid, entry.id) writeUuid(PwDatabaseV4XML.ElemUuid, entry.id)
writeObject(PwDatabaseV4XML.ElemIcon, entry.icon.iconId.toLong()) writeObject(PwDatabaseV4XML.ElemIcon, entry.icon.iconId.toLong())
if (entry.iconCustom != PwIconCustom.UNKNOWN_ICON) { if (entry.iconCustom != PwIconCustom.UNKNOWN_ICON) {
writeObject(PwDatabaseV4XML.ElemCustomIconID, entry.iconCustom.uuid) writeUuid(PwDatabaseV4XML.ElemCustomIconID, entry.iconCustom.uuid)
} }
writeObject(PwDatabaseV4XML.ElemFgColor, entry.foregroundColor) writeObject(PwDatabaseV4XML.ElemFgColor, entry.foregroundColor)
@@ -348,7 +346,7 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
writeTimes(entry) writeTimes(entry)
writeFields(entry.fields) writeFields(entry.fields)
writeList(entry.binaries) writeEntryBinaries(entry.binaries)
writeAutoType(entry.autoType) writeAutoType(entry.autoType)
if (!isHistory) { if (!isHistory) {
@@ -358,81 +356,6 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
xml.endTag(null, PwDatabaseV4XML.ElemEntry) xml.endTag(null, PwDatabaseV4XML.ElemEntry)
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeObject(key: String, binary: ProtectedBinary) {
xml.startTag(null, PwDatabaseV4XML.ElemBinary)
xml.startTag(null, PwDatabaseV4XML.ElemKey)
xml.text(safeXmlString(key))
xml.endTag(null, PwDatabaseV4XML.ElemKey)
xml.startTag(null, PwDatabaseV4XML.ElemValue)
val ref = mDatabaseV4.binPool.findKey(binary)
if (ref != null) {
xml.attribute(null, PwDatabaseV4XML.AttrRef, ref.toString())
} else {
writeBinary(binary)
}
xml.endTag(null, PwDatabaseV4XML.ElemValue)
xml.endTag(null, PwDatabaseV4XML.ElemBinary)
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeBinary(value: ProtectedBinary) {
val valLength = value.length().toInt()
if (valLength > 0) {
val buffer = ByteArray(valLength)
if (valLength == value.getData()!!.read(buffer, 0, valLength)) {
if (value.isProtected) {
xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue)
val encoded = ByteArray(valLength)
randomStream!!.processBytes(buffer, 0, valLength, encoded, 0)
xml.text(String(Base64.encode(encoded, BASE_64_FLAG)))
} else {
if (mDatabaseV4.compressionAlgorithm === PwCompressionAlgorithm.GZip) {
xml.attribute(null, PwDatabaseV4XML.AttrCompressed, PwDatabaseV4XML.ValTrue)
val byteArrayOutputStream = ByteArrayOutputStream()
val gzipOutputStream = GZIPOutputStream(byteArrayOutputStream)
copyStream(ByteArrayInputStream(buffer), gzipOutputStream)
// IOUtils.copy(ByteArrayInputStream(ByteArrayInputStream(buffer)), gzipOutputStream)
gzipOutputStream.close()
xml.text(String(Base64.encode(byteArrayOutputStream.toByteArray(), BASE_64_FLAG)))
} else {
xml.text(String(Base64.encode(buffer, BASE_64_FLAG)))
}
}
} else {
Log.e(TAG, "Unable to read the stream of the protected binary")
}
}
}
@Throws(IOException::class)
fun copyStream(inputStream: InputStream, out: OutputStream) {
val buffer = ByteArray(BUFFER_SIZE_BYTES)
try {
var read = inputStream.read(buffer)
while (read != -1) {
out.write(buffer, 0, read)
read = inputStream.read(buffer)
if (Thread.interrupted()) {
throw InterruptedException()
}
}
} catch (error: OutOfMemoryError) {
throw IOException(error)
}
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeObject(name: String, value: String, filterXmlChars: Boolean = false) { private fun writeObject(name: String, value: String, filterXmlChars: Boolean = false) {
var xmlString = value var xmlString = value
@@ -477,11 +400,57 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeObject(name: String, uuid: UUID) { private fun writeUuid(name: String, uuid: UUID) {
val data = DatabaseInputOutputUtils.uuidToBytes(uuid) val data = DatabaseInputOutputUtils.uuidToBytes(uuid)
writeObject(name, String(Base64.encode(data, BASE_64_FLAG))) writeObject(name, String(Base64.encode(data, BASE_64_FLAG)))
} }
// Only for KDBX3.1-
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeBinariesKDBX31() {
xml.startTag(null, PwDatabaseV4XML.ElemBinaries)
mDatabaseV4.binPool.doForEachBinary { key, binary ->
xml.startTag(null, PwDatabaseV4XML.ElemBinary)
xml.attribute(null, PwDatabaseV4XML.AttrId, key.toString())
xml.attribute(null, PwDatabaseV4XML.AttrCompressed,
if (mDatabaseV4.compressionAlgorithm === PwCompressionAlgorithm.GZip) {
PwDatabaseV4XML.ValTrue
} else {
PwDatabaseV4XML.ValFalse
}
)
/*
// TODO compression needed here ?
// Binary need to be compressed before storage
val byteArrayOutputStream = ByteArrayOutputStream()
val gzipOutputStream = GZIPOutputStream(byteArrayOutputStream)
IOUtils.copy(binary.getInputDataStream(), gzipOutputStream)
gzipOutputStream.close()
readFromStream(ByteArrayInputStream(byteArrayOutputStream.toByteArray()), BUFFER_SIZE_BYTES,
object : ReadBytes {
override fun read(buffer: ByteArray) {
xml.text(String(Base64.encode(buffer, BASE_64_FLAG)))
}
}
)
}*/
readFromStream(binary.getInputDataStream(), BUFFER_SIZE_BYTES,
object : ReadBytes {
override fun read(buffer: ByteArray) {
val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray()
xml.text(charArray, 0, charArray.size)
}
}
)
xml.endTag(null, PwDatabaseV4XML.ElemBinary)
}
xml.endTag(null, PwDatabaseV4XML.ElemBinaries)
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeObject(name: String, keyName: String, keyValue: String, valueName: String, valueValue: String) { private fun writeObject(name: String, keyName: String, keyValue: String, valueName: String, valueValue: String) {
xml.startTag(null, name) xml.startTag(null, name)
@@ -565,20 +534,58 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
private fun writeDeletedObject(value: PwDeletedObject) { private fun writeDeletedObject(value: PwDeletedObject) {
xml.startTag(null, PwDatabaseV4XML.ElemDeletedObject) xml.startTag(null, PwDatabaseV4XML.ElemDeletedObject)
writeObject(PwDatabaseV4XML.ElemUuid, value.uuid) writeUuid(PwDatabaseV4XML.ElemUuid, value.uuid)
writeObject(PwDatabaseV4XML.ElemDeletionTime, value.deletionTime) writeObject(PwDatabaseV4XML.ElemDeletionTime, value.deletionTime)
xml.endTag(null, PwDatabaseV4XML.ElemDeletedObject) xml.endTag(null, PwDatabaseV4XML.ElemDeletedObject)
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeList(binaries: Map<String, ProtectedBinary>) { private fun writeEntryBinaries(binaries: Map<String, ProtectedBinary>) {
for ((key, value) in binaries) { for ((key, binary) in binaries) {
writeObject(key, value) xml.startTag(null, PwDatabaseV4XML.ElemBinary)
xml.startTag(null, PwDatabaseV4XML.ElemKey)
xml.text(safeXmlString(key))
xml.endTag(null, PwDatabaseV4XML.ElemKey)
xml.startTag(null, PwDatabaseV4XML.ElemValue)
val ref = mDatabaseV4.binPool.findKey(binary)
if (ref != null) {
xml.attribute(null, PwDatabaseV4XML.AttrRef, ref.toString())
} else {
val binaryLength = binary.length()
if (binaryLength > 0) {
if (binary.isProtected) {
xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue)
readFromStream(binary.getInputDataStream(), BUFFER_SIZE_BYTES,
object : ReadBytes {
override fun read(buffer: ByteArray) {
val encoded = ByteArray(buffer.size)
randomStream!!.processBytes(buffer, 0, encoded.size, encoded, 0)
val charArray = String(Base64.encode(encoded, BASE_64_FLAG)).toCharArray()
xml.text(charArray, 0, charArray.size)
}
})
} else {
readFromStream(binary.getInputDataStream(), BUFFER_SIZE_BYTES,
object : ReadBytes {
override fun read(buffer: ByteArray) {
val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray()
xml.text(charArray, 0, charArray.size)
}
}
)
}
}
}
xml.endTag(null, PwDatabaseV4XML.ElemValue)
xml.endTag(null, PwDatabaseV4XML.ElemBinary)
} }
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeDeletedObjects(value: List<PwDeletedObject>) { private fun writeDeletedObjects(value: List<PwDeletedObject>) {
xml.startTag(null, PwDatabaseV4XML.ElemDeletedObjects) xml.startTag(null, PwDatabaseV4XML.ElemDeletedObjects)
@@ -658,7 +665,7 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
for (icon in customIcons) { for (icon in customIcons) {
xml.startTag(null, PwDatabaseV4XML.ElemCustomIconItem) xml.startTag(null, PwDatabaseV4XML.ElemCustomIconItem)
writeObject(PwDatabaseV4XML.ElemCustomIconItemID, icon.uuid) writeUuid(PwDatabaseV4XML.ElemCustomIconItemID, icon.uuid)
writeObject(PwDatabaseV4XML.ElemCustomIconItemData, String(Base64.encode(icon.imageData, BASE_64_FLAG))) writeObject(PwDatabaseV4XML.ElemCustomIconItemData, String(Base64.encode(icon.imageData, BASE_64_FLAG)))
xml.endTag(null, PwDatabaseV4XML.ElemCustomIconItem) xml.endTag(null, PwDatabaseV4XML.ElemCustomIconItem)
@@ -667,22 +674,6 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
xml.endTag(null, PwDatabaseV4XML.ElemCustomIcons) xml.endTag(null, PwDatabaseV4XML.ElemCustomIcons)
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeBinPool() {
xml.startTag(null, PwDatabaseV4XML.ElemBinaries)
mDatabaseV4.binPool.doForEachBinary { key, binary ->
xml.startTag(null, PwDatabaseV4XML.ElemBinary)
xml.attribute(null, PwDatabaseV4XML.AttrId, key.toString())
writeBinary(binary)
xml.endTag(null, PwDatabaseV4XML.ElemBinary)
}
xml.endTag(null, PwDatabaseV4XML.ElemBinaries)
}
private fun safeXmlString(text: String): String { private fun safeXmlString(text: String): String {
if (text.isEmpty()) { if (text.isEmpty()) {
return text return text

View File

@@ -1,11 +0,0 @@
package com.kunzisoft.keepass.stream;
import java.io.IOException;
public interface ActionReadBytes {
/**
* Called after each buffer fill
* @param buffer filled
*/
void doAction(byte[] buffer) throws IOException;
}

View File

@@ -124,7 +124,7 @@ public class LEDataInputStream extends InputStream {
return buf; return buf;
} }
public void readBytes(int length, ActionReadBytes actionReadBytes) throws IOException { public void readBytes(int length, ReadBytes readBytes) throws IOException {
int bufferSize = 256 * 3; // TODO Buffer size int bufferSize = 256 * 3; // TODO Buffer size
byte[] buffer = new byte[bufferSize]; byte[] buffer = new byte[bufferSize];
@@ -146,7 +146,7 @@ public class LEDataInputStream extends InputStream {
} else { } else {
optimizedBuffer = buffer; optimizedBuffer = buffer;
} }
actionReadBytes.doAction(optimizedBuffer); readBytes.read(optimizedBuffer);
offset += read; offset += read;
} }
} }

View File

@@ -0,0 +1,30 @@
package com.kunzisoft.keepass.stream
import java.io.IOException
import java.io.InputStream
interface ReadBytes {
/**
* Called after each buffer fill
* @param buffer filled
*/
@Throws(IOException::class)
fun read(buffer: ByteArray)
}
@Throws(IOException::class)
fun readFromStream(inputStream: InputStream, bufferSize: Int, readBytes: ReadBytes) {
val buffer = ByteArray(bufferSize)
var read = 0
while (read != -1) {
read = inputStream.read(buffer, 0, buffer.size)
if (read != -1) {
val optimizedBuffer: ByteArray = if (buffer.size == read) {
buffer
} else {
buffer.copyOf(read)
}
readBytes.read(optimizedBuffer)
}
}
}