Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,10 @@
ignore:
- "kotpass/src/main/java/org/apache/commons/lang3/"
- "kotpass/src/main/kotlin/org/redundent/kotlin/xml/"
coverage:
status:
project:
default:
target: 70%
threshold: 10%
informational: true
22 changes: 12 additions & 10 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
[versions]
kotlin = "2.1.0"
dokka = "2.0.0"
kotlin = "2.1.21"
okio = "3.15.0"

junit5 = "5.10.0"
kotest = "5.6.1"
spotless = "6.25.0"
versions = "0.52.0"
kover = "0.9.1"

dokka = "2.0.0"
maven-publish = "0.30.0"
junit5 = "5.10.0"
spotless = "6.25.0"
versions = "0.52.0"

[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
versions = { id = "com.github.ben-manes.versions", version.ref = "versions" }
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "maven-publish" }
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
versions = { id = "com.github.ben-manes.versions", version.ref = "versions" }

[libraries]
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
testing-kotest = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" }
okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
junit-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit5" }

okio = "com.squareup.okio:okio:3.10.2"
testing-kotest = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" }
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
9 changes: 4 additions & 5 deletions gradlew

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions gradlew.bat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
package app.keemobile.kotpass.constants

/**
* Specifies the obfuscation method for Auto-Type to protect against keyloggers.
*/
enum class AutoTypeObfuscation {
/**
* Sends characters as individual keystrokes.
* This is less secure against keyloggers.
*/
None,

/**
* Pastes the password via the system clipboard to bypass keyloggers.
* Also known as Two-Channel Auto-Type Obfuscation (TCATO).
*/
UseClipboard
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
package app.keemobile.kotpass.constants

/**
* Specifies the stream cipher for in-memory protection of sensitive data.
*/
enum class CrsAlgorithm {
/** Unsupported. */
None,

/** Legacy ArcFour (RC4) variant. Unsupported. */
ArcFourVariant,

/** Salsa20 stream cipher. */
Salsa20,

/** ChaCha20 stream cipher. */
ChaCha20
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package app.keemobile.kotpass.constants

/**
* Specifies whether a feature for a group is inherited from its parent
* or is explicitly enabled/disabled.
*/
enum class GroupOverride {
Inherit,
Enabled,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,42 @@
package app.keemobile.kotpass.constants

internal enum class HeaderFieldId {
/** Marks the end of the header fields section. */
EndOfHeader,

@Deprecated("No longer supported.")
/** Legacy comment field — deprecated and no longer used. */
Comment,

/** Identifier for the encryption cipher used (e.g. AES, ChaCha20). */
CipherId,

/** Compression algorithm identifier. */
Compression,

/** Random seed used for master key derivation (32 bytes) */
MasterSeed,

/** Random seed used for key transformation/derivation (legacy AES-KDF). */
TransformSeed,

/** Number of transformation rounds for key derivation (legacy AES-KDF). */
TransformRounds,

/** Initialization vector for the encryption cipher. */
EncryptionIV,

/** Key used for the inner random stream encryption. */
InnerRandomStreamKey,

/** First bytes of decrypted data used to verify correct decryption. */
StreamStartBytes,

/** Identifier for inner random stream algorithm. */
InnerRandomStreamId,

/** Parameters for modern key derivation functions stored as variant dictionary. */
KdfParameters,

/** Custom data that can be read by third-party applications. */
PublicCustomData
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package app.keemobile.kotpass.constants

import app.keemobile.kotpass.xml.FormatXml

/**
* Specifies which sensitive fields of an entry are protected in memory.
*/
enum class MemoryProtectionFlag(val value: String) {
Title(FormatXml.Tags.Meta.MemoryProtection.ProtectTitle),
UserName(FormatXml.Tags.Meta.MemoryProtection.ProtectUserName),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
package app.keemobile.kotpass.constants

/**
* Represents dynamic placeholders used in KeePass.
*/
internal enum class Placeholder(val value: String) {
/** The entry’s title: `{TITLE}`. */
Title("TITLE"),

/** The entry’s user name: `{USERNAME}`. */
UserName("USERNAME"),

/** The entry’s password: `{PASSWORD}`. */
Password("PASSWORD"),

/** The entry’s URL: `{URL}`. */
Url("URL"),

/** The entry’s notes: `{NOTES}`. */
Notes("NOTES"),

/** The entry’s unique ID: `{UUID}`. */
Uuid("UUID"),

/** Prefix for a field reference to another entry, e.g., `{REF:T@...}`. */
Reference("REF:"),

/** Prefix for a custom string field, e.g., `{S:FieldName}`. */
CustomField("S:");

operator fun invoke() = this.value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ import app.keemobile.kotpass.extensions.sha256
import app.keemobile.kotpass.extensions.sha512
import okio.ByteString

private val SalsaNonce = intArrayOf(0xe8, 0x30, 0x09, 0x4b, 0x97, 0x20, 0x5d, 0x2a)
.map(Int::toByte)
.toByteArray()

/**
* Used as inner encryption to improve process memory protection, it does not enhance
* the cryptographic security of the KDBX file format itself.
Expand Down Expand Up @@ -39,8 +35,11 @@ sealed class EncryptionSaltGenerator {
abstract fun processBytes(input: ByteArray): ByteArray

class Salsa20(key: ByteArray) : EncryptionSaltGenerator() {
// Static 'nonce' provided by KeePass specification
private val nonce = byteArrayOf(0xe8.toByte(), 0x30, 0x09, 0x4b, 0x97.toByte(), 0x20, 0x5d, 0x2a)

private val engine = Salsa20Engine().apply {
init(key.sha256(), SalsaNonce)
init(key.sha256(), nonce)
}

override fun getSalt(length: Int) = engine.getBytes(length)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import app.keemobile.kotpass.io.BufferedStream
import okio.BufferedSink
import okio.ByteString

class Signature(
val base: ByteString,
val secondary: ByteString
) {
/**
* This signature is used to identify the file type and version.
*/
class Signature(val base: ByteString, val secondary: ByteString) {
internal fun writeTo(sink: BufferedSink) = with(sink) {
write(base)
write(secondary)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,62 @@
import app.keemobile.kotpass.constants.VariantTypeId
import okio.ByteString

/**
* Represents a variant data type item within KDBX file header.
*/
sealed interface VariantItem {
/**
* The unique identifier for the variant's data type,
* corresponding to [VariantTypeId].
*/
val typeId: Int

/** Represents an unsigned 32-bit integer variant item. */
@JvmInline
value class UInt32(val value: UInt) : VariantItem {
override val typeId: Int get() = VariantTypeId.UInt32
override val typeId: Int
get() = VariantTypeId.UInt32
}

/** Represents an unsigned 64-bit integer variant item. */
@JvmInline
value class UInt64(val value: ULong) : VariantItem {
override val typeId: Int get() = VariantTypeId.UInt64
override val typeId: Int
get() = VariantTypeId.UInt64
}

/** Represents a boolean variant item. */
@JvmInline
value class Bool(val value: Boolean) : VariantItem {
override val typeId: Int get() = VariantTypeId.Bool
override val typeId: Int
get() = VariantTypeId.Bool

Check warning on line 34 in kotpass/src/main/kotlin/app/keemobile/kotpass/database/header/VariantItem.kt

View check run for this annotation

Codecov / codecov/patch

kotpass/src/main/kotlin/app/keemobile/kotpass/database/header/VariantItem.kt#L34

Added line #L34 was not covered by tests
}

/** Represents a signed 32-bit integer variant item. */
@JvmInline
value class Int32(val value: Int) : VariantItem {
override val typeId: Int get() = VariantTypeId.Int32
override val typeId: Int
get() = VariantTypeId.Int32

Check warning on line 41 in kotpass/src/main/kotlin/app/keemobile/kotpass/database/header/VariantItem.kt

View check run for this annotation

Codecov / codecov/patch

kotpass/src/main/kotlin/app/keemobile/kotpass/database/header/VariantItem.kt#L41

Added line #L41 was not covered by tests
}

/** Represents a signed 64-bit integer variant item. */
@JvmInline
value class Int64(val value: Long) : VariantItem {
override val typeId: Int get() = VariantTypeId.Int64
override val typeId: Int
get() = VariantTypeId.Int64

Check warning on line 48 in kotpass/src/main/kotlin/app/keemobile/kotpass/database/header/VariantItem.kt

View check run for this annotation

Codecov / codecov/patch

kotpass/src/main/kotlin/app/keemobile/kotpass/database/header/VariantItem.kt#L48

Added line #L48 was not covered by tests
}

/** Represents a UTF-8 encoded string variant item. */
@JvmInline
value class StringUtf8(val value: String) : VariantItem {
override val typeId: Int get() = VariantTypeId.StringUtf8
override val typeId: Int
get() = VariantTypeId.StringUtf8

Check warning on line 55 in kotpass/src/main/kotlin/app/keemobile/kotpass/database/header/VariantItem.kt

View check run for this annotation

Codecov / codecov/patch

kotpass/src/main/kotlin/app/keemobile/kotpass/database/header/VariantItem.kt#L55

Added line #L55 was not covered by tests
}

/** Represents a byte array variant item. */
@JvmInline
value class Bytes(val value: ByteString) : VariantItem {
override val typeId: Int get() = VariantTypeId.Bytes
override val typeId: Int
get() = VariantTypeId.Bytes
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,20 @@ import app.keemobile.kotpass.models.Group
import okio.ByteString
import java.util.Stack

/**
* Returns a map of binary data associated with [KeePassDatabase].
*/
val KeePassDatabase.binaries
get() = when (this) {
is KeePassDatabase.Ver3x -> content.meta.binaries
is KeePassDatabase.Ver4x -> innerHeader.binaries
}

/**
* Modifies binaries map of [KeePassDatabase] using the provided [block].
*
* @return A new [KeePassDatabase] instance with the modified binaries.
*/
inline fun KeePassDatabase.modifyBinaries(
crossinline block: (Map<ByteString, BinaryData>) -> Map<ByteString, BinaryData>
): KeePassDatabase = when (this) {
Expand All @@ -26,6 +34,12 @@ inline fun KeePassDatabase.modifyBinaries(
)
}

/**
* This function traverses all groups and entries, including historical entries,
* to identify and remove unreferenced binary data.
*
* @return A new [KeePassDatabase] instance with unused binaries removed.
*/
fun KeePassDatabase.removeUnusedBinaries(): KeePassDatabase {
val cleanupList = binaries.keys.toMutableSet()

Expand Down
Loading