diff --git a/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/core/KdbxDatabase.kt b/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/core/KdbxDatabase.kt index b05b0da6..843158b2 100644 --- a/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/core/KdbxDatabase.kt +++ b/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/core/KdbxDatabase.kt @@ -1,9 +1,15 @@ 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, @@ -11,13 +17,21 @@ data class KdbxDatabase( 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) - } } diff --git a/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/core/TinypassDatabase.kt b/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/core/TinypassDatabase.kt index 2906d616..41c68cfb 100644 --- a/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/core/TinypassDatabase.kt +++ b/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/core/TinypassDatabase.kt @@ -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, diff --git a/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/model/component/InternalValue.kt b/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/model/component/InternalValue.kt index 2d5cf500..d4da3e3d 100644 --- a/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/model/component/InternalValue.kt +++ b/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/model/component/InternalValue.kt @@ -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. diff --git a/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/serializer/KdbxDecoder.kt b/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/serializer/KdbxDecoder.kt index 0a5c4fbc..82be12cf 100644 --- a/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/serializer/KdbxDecoder.kt +++ b/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/serializer/KdbxDecoder.kt @@ -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 } diff --git a/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/serializer/KdbxSerializer.kt b/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/serializer/KdbxSerializer.kt index 122038f4..bc9ccea1 100644 --- a/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/serializer/KdbxSerializer.kt +++ b/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/serializer/KdbxSerializer.kt @@ -2,8 +2,19 @@ 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 @@ -11,7 +22,11 @@ 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 { @@ -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( @@ -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, + ) + } + } } } diff --git a/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/serializer/internal/_KdbxEncoder.kt b/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/serializer/internal/_KdbxEncoder.kt index ff07418c..1546ff1b 100644 --- a/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/serializer/internal/_KdbxEncoder.kt +++ b/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/serializer/internal/_KdbxEncoder.kt @@ -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 @@ -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) @@ -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), + ) + } } } @@ -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 + } +} diff --git a/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/xml/XmlReader.kt b/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/xml/XmlReader.kt index 9b9807f6..da3414b6 100644 --- a/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/xml/XmlReader.kt +++ b/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/xml/XmlReader.kt @@ -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 { - fun read(element: Element): T + +object XmlReader { + fun read(source: BufferedSource, option: XmlOption): KdbxQuery { + val documentation = xmlParser(source.readByteArray()) + val root = documentation.root() + TODO() + } } diff --git a/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/xml/XmlWriter.kt b/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/xml/XmlWriter.kt index d0a2b762..57c12f54 100644 --- a/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/xml/XmlWriter.kt +++ b/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/xml/XmlWriter.kt @@ -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.* @@ -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) diff --git a/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/xml/internal/_XmlWriter.kt b/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/xml/internal/_XmlWriter.kt new file mode 100644 index 00000000..5934c24d --- /dev/null +++ b/tinypass/src/commonMain/kotlin/io/spherelabs/crypto/tinypass/database/xml/internal/_XmlWriter.kt @@ -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) +} diff --git a/tinypass/src/commonTest/kotlin/core/KdbxSerializerTest.kt b/tinypass/src/commonTest/kotlin/core/KdbxSerializerTest.kt index cb279be3..21e9688a 100644 --- a/tinypass/src/commonTest/kotlin/core/KdbxSerializerTest.kt +++ b/tinypass/src/commonTest/kotlin/core/KdbxSerializerTest.kt @@ -1,6 +1,7 @@ package core import io.spherelabs.crypto.tinypass.database.core.KdbxConfiguration +import io.spherelabs.crypto.tinypass.database.core.KdbxDatabase import io.spherelabs.crypto.tinypass.database.serializer.KdbxSerializer import kotlin.test.Test @@ -8,13 +9,15 @@ class KdbxSerializerTest { @Test fun test() { - val serializer = KdbxSerializer.encode( - config = KdbxConfiguration.of( - passphrase = "encode", - path = "test.kdbx" - ) + val configuration = KdbxConfiguration.of( + path = "test4.kdbx", + passphrase = "kdbx_multiplatform", ) - println(serializer) + val database = KdbxDatabase.write(configuration) + + println(KdbxDatabase.isEmpty("test.kdbx")) + println(KdbxDatabase.isEmpty("test1.kdbx")) + println(database) } } diff --git a/tinypass/src/commonTest/kotlin/xml/DocumentTest.kt b/tinypass/src/commonTest/kotlin/xml/DocumentTest.kt index c73317f6..72fbfe00 100644 --- a/tinypass/src/commonTest/kotlin/xml/DocumentTest.kt +++ b/tinypass/src/commonTest/kotlin/xml/DocumentTest.kt @@ -3,14 +3,17 @@ package xml import com.fleeksoft.ksoup.Ksoup import io.spherelabs.crypto.tinypass.database.common.xmlParser import kotlin.test.Test +import okio.Buffer class DocumentTest { @Test fun `GIVEN the root WHEN select root THEN it returns root`() { val document = xmlParser(htmlContent) - - + val buffer = Buffer().write(htmlContent.encodeToByteArray()) + val xmlParser = xmlParser(buffer.readByteArray()) + val root = xmlParser.root() + println(root) } companion object { diff --git a/tinypass/src/commonTest/kotlin/xml/XmlWriterTest.kt b/tinypass/src/commonTest/kotlin/xml/XmlWriterTest.kt index 5c58cbc6..e63655ca 100644 --- a/tinypass/src/commonTest/kotlin/xml/XmlWriterTest.kt +++ b/tinypass/src/commonTest/kotlin/xml/XmlWriterTest.kt @@ -23,6 +23,7 @@ class XmlWriterTest { lastTopVisibleGroup = uuid4(), lastSelectedGroup = uuid4(), ) + // val writer = XmlWriter.write( // option = XmlOption(KdbxVersion(4, 1), binaries = mapOf()), // XmlComponent.Meta(