Skip to content

Commit

Permalink
feat: Add internal xml writer (#183)
Browse files Browse the repository at this point in the history
  • Loading branch information
behzodhalil committed Feb 26, 2024
1 parent c7a0c4b commit ff00b26
Show file tree
Hide file tree
Showing 12 changed files with 211 additions and 59 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
package io.spherelabs.crypto.tinypass.database.core

import io.spherelabs.crypto.tinypass.database.buffer.KdbxBuffer
import io.spherelabs.crypto.tinypass.database.buffer.KdbxReader
import io.spherelabs.crypto.tinypass.database.getPlatformFileSystem
import io.spherelabs.crypto.tinypass.database.header.KdbxInnerHeader
import io.spherelabs.crypto.tinypass.database.header.KdbxOuterHeader
import io.spherelabs.crypto.tinypass.database.model.component.KdbxQuery
import io.spherelabs.crypto.tinypass.database.serializer.KdbxSerializer
import kotlin.coroutines.CoroutineContext
import okio.Buffer
import okio.Path.Companion.toPath

data class KdbxDatabase(
val configuration: KdbxConfiguration,
val outerHeader: KdbxOuterHeader,
val innerHeader: KdbxInnerHeader,
val query: KdbxQuery,
) {
// private val decoder = KdbxSerializer.decode(configuration)

companion object {
fun isEmpty(path: String): Boolean {
return getPlatformFileSystem().exists(path.toPath())
}

fun write(configuration: KdbxConfiguration): KdbxDatabase {
return if (!isEmpty(configuration.path)) KdbxSerializer.encode(configuration) else {
throw Exception("File is existed!")
}
}
}

fun read(): KdbxQuery {
TODO()
}

fun write() {
KdbxSerializer.encode(configuration)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,26 +87,7 @@ package io.spherelabs.crypto.tinypass.database.core
// return decryptedContent
// }
//
// private fun deserializeAsContent(
// cipherId: CipherId,
// key: ByteArray,
// iv: ByteArray,
// data: ByteArray,
// ): ByteArray {
// return when (cipherId) {
// CipherId.Aes -> {
// ChaCha7539Engine().apply { init(key, iv) }.processBytes(data)
// }
// CipherId.ChaCha20 -> {
// AES.decryptAesCbc(
// key = key,
// data = data,
// iv = iv,
// padding = CipherPadding.NoPadding,
// )
// }
// }
// }

//
// private fun serializeAsContent(
// cipherId: CipherId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import kotlin.io.encoding.ExperimentalEncodingApi

/**
* [SecureBytes] represents a secure byte array with optional encryption.
* This class provides methods for encryption, decryption, and hashing of byte arrays.
* It provides methods for encryption, decryption, and hashing of byte arrays.
*
* Password hashing and salting are two techniques that
* can strengthen the security of passwords stored in a database.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package io.spherelabs.crypto.tinypass.database.serializer

import io.spherelabs.crypto.tinypass.database.core.KdbxConfiguration
import io.spherelabs.crypto.tinypass.database.core.KdbxDatabase
import okio.BufferedSource

interface KdbxDecoder {
fun decode(config: KdbxConfiguration): KdbxDatabase
fun decode(source: BufferedSource, config: KdbxConfiguration): KdbxDatabase
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,31 @@ package io.spherelabs.crypto.tinypass.database.serializer

import com.benasher44.uuid.uuid4
import io.spherelabs.anycrypto.securerandom.buildSecureRandom
import io.spherelabs.crypto.cipher.AES
import io.spherelabs.crypto.cipher.ChaCha7539Engine
import io.spherelabs.crypto.cipher.CipherPadding
import io.spherelabs.crypto.tinypass.database.buffer.KdbxBuffer
import io.spherelabs.crypto.tinypass.database.compressor.toGzipSink
import io.spherelabs.crypto.tinypass.database.core.ContentBlocks
import io.spherelabs.crypto.tinypass.database.core.KdbxConfiguration
import io.spherelabs.crypto.tinypass.database.core.KdbxDatabase
import io.spherelabs.crypto.tinypass.database.core.masterKey
import io.spherelabs.crypto.tinypass.database.core.transformKey
import io.spherelabs.crypto.tinypass.database.getPlatformFileSystem
import io.spherelabs.crypto.tinypass.database.header.CipherId
import io.spherelabs.crypto.tinypass.database.header.CompressionFlags
import io.spherelabs.crypto.tinypass.database.header.KdbxInnerHeader
import io.spherelabs.crypto.tinypass.database.header.KdbxOuterHeader
import io.spherelabs.crypto.tinypass.database.header.KdfParameters
import io.spherelabs.crypto.tinypass.database.model.component.KdbxQuery
import io.spherelabs.crypto.tinypass.database.model.component.Group
import io.spherelabs.crypto.tinypass.database.model.component.Meta
import io.spherelabs.crypto.tinypass.database.serializer.internal.commonKdbxEncode
import okio.Buffer
import okio.BufferedSource
import okio.ByteString.Companion.toByteString
import okio.Path.Companion.toPath
import okio.buffer

object KdbxSerializer : KdbxEncoder, KdbxDecoder {

Expand All @@ -35,7 +50,9 @@ object KdbxSerializer : KdbxEncoder, KdbxDecoder {
val innerHeader = KdbxInnerHeader.of(
random.nextBytes(64).toByteString(),
)
val meta = Meta()
val meta = Meta(
name = config.path,
)

return commonKdbxEncode(
KdbxDatabase(
Expand All @@ -57,7 +74,29 @@ object KdbxSerializer : KdbxEncoder, KdbxDecoder {

}

override fun decode(config: KdbxConfiguration): KdbxDatabase {
TODO("Not yet implemented")
override fun decode(source: BufferedSource, configuration: KdbxConfiguration): KdbxDatabase {
TODO()
}

private fun deserializeAsContent(
cipherId: CipherId,
key: ByteArray,
iv: ByteArray,
data: ByteArray,
): ByteArray {
return when (cipherId) {
CipherId.Aes -> {
ChaCha7539Engine().apply { init(key, iv) }.processBytes(data)
}

CipherId.ChaCha20 -> {
AES.decryptAesCbc(
key = key,
data = data,
iv = iv,
padding = CipherPadding.NoPadding,
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import io.spherelabs.crypto.tinypass.database.buffer.KdbxBuffer
import io.spherelabs.crypto.tinypass.database.compressor.toGzipSink
import io.spherelabs.crypto.tinypass.database.compressor.toGzipSource
import io.spherelabs.crypto.tinypass.database.core.*
import io.spherelabs.crypto.tinypass.database.getPlatformFileSystem
import io.spherelabs.crypto.tinypass.database.header.CipherId
import io.spherelabs.crypto.tinypass.database.header.CompressionFlags
import io.spherelabs.crypto.tinypass.database.serializer.KdbxSerializer
import io.spherelabs.crypto.tinypass.database.xml.XmlOption
import io.spherelabs.crypto.tinypass.database.xml.XmlReader
import io.spherelabs.crypto.tinypass.database.xml.XmlWriter
import okio.Buffer
import okio.Path.Companion.toPath
import okio.buffer
import okio.use

Expand Down Expand Up @@ -43,9 +47,6 @@ fun commonKdbxEncode(database: KdbxDatabase) = database.apply {
query, option,
).text().encodeToByteArray()

println("Xml is ${XmlWriter.write(
query, option,
).text()}")
val contentBuffer = Buffer()
KdbxBuffer.writeInnerHeader(sink = contentBuffer, innerHeader)
contentBuffer.write(rawContent)
Expand All @@ -64,14 +65,20 @@ fun commonKdbxEncode(database: KdbxDatabase) = database.apply {
iv = outerHeader.encryptionIV.toByteArray(),
data = raw,
)
Buffer().buffer.use { sink ->
sink.write(outerHeaderBuffer.snapshot().toByteArray())
ContentBlocks.writeContentBlocksVer4x(
sink = sink,
contentData = encryptedContent,
masterSeed = seed,
transformedKey = transformKey(header = outerHeader, configuration),
)

getPlatformFileSystem().write(
configuration.path.toPath(),
mustCreate = true
) {
buffer().use { sink ->
sink.write(outerHeaderBuffer.snapshot().toByteArray())
ContentBlocks.writeContentBlocksVer4x(
sink = sink,
contentData = encryptedContent,
masterSeed = seed,
transformedKey = transformKey(header = outerHeader, configuration),
)
}
}
}

Expand Down Expand Up @@ -118,3 +125,40 @@ private fun serializeAsContent(
}


fun commonKdbxDecode(database: KdbxDatabase): KdbxDatabase = database.apply {
val headerBuffer = Buffer()

getPlatformFileSystem().read(configuration.path.toPath()) {
val header = KdbxBuffer.readOuterHeader()

val rawHeaderData = headerBuffer.snapshot()

val expectedSha256 = readByteString(32)
val expectedHmacSha256 = readByteString(32)
val transformedKey = transformKey(header, configuration)
val innerHeaderBuffer = Buffer()
val seed = header.seed.toByteArray()
val encryptedContent = ContentBlocks.readContentBlocksVer4x(
source = this,
masterSeed = seed,
transformedKey = transformedKey,
)
var decryptedContent =
deserializeAsContent(
header.option.cipherId, key = masterKey(seed, header, configuration),
iv = header.encryptionIV.toByteArray(), data = encryptedContent,
)

if (outerHeader.option.compressionFlags == CompressionFlags.GZip) {
decryptedContent = decryptedContent.toGzipSink().buffer().buffer.readByteArray()
}

val innerHeader = KdbxBuffer.readInnerHeader()
val saltGenerator = EncryptionSaltGenerator.create(
id = innerHeader.streamCipher,
key = innerHeader.streamKey
)

val content = XmlReader
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
package io.spherelabs.crypto.tinypass.database.xml

import com.fleeksoft.ksoup.nodes.Element
import io.spherelabs.crypto.tinypass.database.common.xmlParser
import io.spherelabs.crypto.tinypass.database.core.KdbxDatabase
import io.spherelabs.crypto.tinypass.database.core.KdbxQuery
import okio.BufferedSource

interface XmlReader<T : Any> {
fun read(element: Element): T

object XmlReader {
fun read(source: BufferedSource, option: XmlOption): KdbxQuery {
val documentation = xmlParser(source.readByteArray())
val root = documentation.root()
TODO()
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.spherelabs.crypto.tinypass.database.xml

import com.benasher44.uuid.Uuid
import com.fleeksoft.ksoup.nodes.Document
import com.fleeksoft.ksoup.nodes.Element
import io.spherelabs.crypto.tinypass.database.FormatXml
import io.spherelabs.crypto.tinypass.database.common.*
Expand All @@ -17,20 +18,20 @@ object XmlWriter {
val version = "2.0"
val encoding = "utf-8"

val element = xml(version, encoding) {
writeGroup(query.group)
writeMeta(query.meta, option)
val document = xml(version, encoding) {
writeMeta(query.meta, option).appendTo(this)
appendElement("root").appendTo(this).apply {
writeGroup(query.group).appendTo(this)
}

}
println(element)
return element
}

fun writeTime(timeDatta: TimeData): Element {
TODO()
println("Document is $document")
return document
}

fun writeGroup(group: Group): Element = with(group) {

private fun writeGroup(group: Group): Element = with(group) {
return element(XmlTags.GROUP_TAG_NAME) {
writeElement(XmlTags.UUID, uuid = id)
writeElement(XmlTags.GROUP_NAME, title)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package io.spherelabs.crypto.tinypass.database.xml.internal

import com.benasher44.uuid.Uuid
import com.fleeksoft.ksoup.nodes.Element
import io.spherelabs.crypto.tinypass.database.common.addBytes
import io.spherelabs.crypto.tinypass.database.common.addUuid
import io.spherelabs.crypto.tinypass.database.model.autotype.toXmlString
import io.spherelabs.crypto.tinypass.database.xml.XmlOption


internal fun element(tagName: String, builder: Element.() -> Unit): Element =
Element(tagName).apply(builder)

internal inline fun Element.writeElement(
tagName: String,
value: String,
option: XmlOption,
) {
appendElement(tagName).text(value)
}

internal inline fun Element.writeElement(
tagName: String,
value: String,
) {
appendElement(tagName).text(value)
}

internal inline fun Element.writeElement(
tagName: String,
raw: ByteArray,
) {
appendElement(tagName).addBytes(raw)
}

internal inline fun Element.writeElement(
tagName: String,
raw: Boolean,
) {
appendElement(tagName).text(raw.toXmlString())
}

internal inline fun Element.writeElement(
tagName: String,
uuid: Uuid,
) {
appendElement(tagName).addUuid(uuid)
}

internal inline fun Element.writeIfElement(
predicate: Boolean,
tagName: String,
raw: String,
) {
if (predicate) appendElement(tagName).text(raw)
}
Loading

0 comments on commit ff00b26

Please # to comment.