fix: Allow to check multiple app signatures #1421

This commit is contained in:
J-Jamet
2025-08-27 23:09:51 +02:00
parent fcf723849b
commit 5f27f161a5
13 changed files with 311 additions and 164 deletions

View File

@@ -33,9 +33,9 @@ import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.model.AppOrigin
import com.kunzisoft.keepass.model.AppOriginEntryField
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.OriginApp
import com.kunzisoft.keepass.model.OriginAppEntryField
import com.kunzisoft.keepass.model.Passkey
import com.kunzisoft.keepass.model.PasskeyEntryFields
import com.kunzisoft.keepass.otp.OtpElement
@@ -367,9 +367,9 @@ class Entry : Node, EntryVersionedInterface<Group> {
return null
}
fun getOriginApp(): OriginApp? {
fun getAppOrigin(): AppOrigin? {
entryKDBX?.let {
return OriginAppEntryField.parseFields { key ->
return AppOriginEntryField.parseFields { key ->
it.getFieldValue(key)?.toString()
}
}
@@ -494,7 +494,7 @@ class Entry : Node, EntryVersionedInterface<Group> {
entryInfo.otpModel = getOtpElement()?.otpModel
// Add Passkey
entryInfo.passkey = getPasskey()
entryInfo.originApp = getOriginApp()
entryInfo.appOrigin = getAppOrigin()
if (!raw) {
// Replace parameter fields by generated OTP fields
entryInfo.customFields = OtpEntryFields.generateAutoFields(entryInfo.customFields)

View File

@@ -23,8 +23,39 @@ import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class OriginApp(
val appId: String? = null,
val appSignature: String? = null,
val webDomain: String? = null
data class AppOrigin(
val appIdentifiers: MutableList<AppIdentifier> = mutableListOf(),
val webDomains: MutableList<String> = mutableListOf()
) : Parcelable {
fun addIdentifier(appIdentifier: AppIdentifier) {
appIdentifiers.add(appIdentifier)
}
fun addWebDomain(webDomain: String) {
this.webDomains.add(webDomain)
}
fun removeAppElement(appIdentifier: AppIdentifier) {
appIdentifiers.remove(appIdentifier)
}
fun removeWebDomain(webDomain: String) {
this.webDomains.remove(webDomain)
}
fun clear() {
appIdentifiers.clear()
webDomains.clear()
}
fun isEmpty(): Boolean {
return appIdentifiers.isEmpty() && webDomains.isEmpty()
}
}
@Parcelize
data class AppIdentifier(
val id: String,
val signature: String? = null,
) : Parcelable

View File

@@ -21,25 +21,51 @@ package com.kunzisoft.keepass.model
import com.kunzisoft.keepass.database.element.Field
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.model.EntryInfo.Companion.suffixFieldNamePosition
object OriginAppEntryField {
object AppOriginEntryField {
const val WEB_DOMAIN_FIELD_NAME = "URL"
const val APPLICATION_ID_FIELD_NAME = "AndroidApp"
const val APPLICATION_SIGNATURE_FIELD_NAME = "AndroidApp Signature"
/**
* Parse fields of an entry to retrieve a an OriginApp
* Parse fields of an entry to retrieve a an AppOrigin
*/
fun parseFields(getField: (id: String) -> String?): OriginApp {
val appIdField = getField(APPLICATION_ID_FIELD_NAME)
val appSignatureField = getField(APPLICATION_SIGNATURE_FIELD_NAME)
val webDomainField = getField(WEB_DOMAIN_FIELD_NAME)
return OriginApp(
appId = appIdField,
appSignature = appSignatureField,
webDomain = webDomainField
)
fun parseFields(getField: (id: String) -> String?): AppOrigin {
val appOrigin = AppOrigin()
// Get Application identifiers
generateSequence(0) { it + 1 }
.map { position ->
val appId = getField(APPLICATION_ID_FIELD_NAME + suffixFieldNamePosition(position))
val appSignature = getField(APPLICATION_SIGNATURE_FIELD_NAME + suffixFieldNamePosition(position))
// Pair them up, if appId is null, we stop
if (appId != null) {
appId to (appSignature ?: "")
} else {
// Stop
null
}
}.takeWhile { it != null }
.forEach { pair ->
appOrigin.addIdentifier(
AppIdentifier(pair!!.first, pair.second)
)
}
// Get Domains
var domainFieldPosition = 0
while (true) {
val domainKey = WEB_DOMAIN_FIELD_NAME + suffixFieldNamePosition(domainFieldPosition)
val domainValue = getField(domainKey)
if (domainValue != null) {
appOrigin.addWebDomain(domainValue)
domainFieldPosition++
} else {
break // No more domain found
}
}
return appOrigin
}
/**
@@ -102,10 +128,16 @@ object OriginAppEntryField {
}
}
fun EntryInfo.setOriginApp(originApp: OriginApp?, customFieldsAllowed: Boolean) {
if (originApp != null) {
setApplicationId(originApp.appId, originApp.appSignature)
setWebDomain(originApp.webDomain, null, customFieldsAllowed)
/**
* Assign an AppOrigin to an EntryInfo,
* Only if [customFieldsAllowed] is true
*/
fun EntryInfo.setAppOrigin(appOrigin: AppOrigin?, customFieldsAllowed: Boolean) {
appOrigin?.appIdentifiers?.forEach { appIdentifier ->
setApplicationId(appIdentifier.id, appIdentifier.signature)
}
appOrigin?.webDomains?.forEach { webDomain ->
setWebDomain(webDomain, null, customFieldsAllowed)
}
}
}

View File

@@ -27,10 +27,10 @@ import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Field
import com.kunzisoft.keepass.database.element.Tags
import com.kunzisoft.keepass.database.element.entry.AutoType
import com.kunzisoft.keepass.model.AppOriginEntryField.setAppOrigin
import com.kunzisoft.keepass.model.AppOriginEntryField.setApplicationId
import com.kunzisoft.keepass.model.AppOriginEntryField.setWebDomain
import com.kunzisoft.keepass.model.CreditCardEntryFields.setCreditCard
import com.kunzisoft.keepass.model.OriginAppEntryField.setApplicationId
import com.kunzisoft.keepass.model.OriginAppEntryField.setOriginApp
import com.kunzisoft.keepass.model.OriginAppEntryField.setWebDomain
import com.kunzisoft.keepass.model.PasskeyEntryFields.isPasskeyExclusion
import com.kunzisoft.keepass.model.PasskeyEntryFields.setPasskey
import com.kunzisoft.keepass.otp.OtpElement
@@ -59,7 +59,7 @@ class EntryInfo : NodeInfo {
var autoType: AutoType = AutoType()
var otpModel: OtpModel? = null
var passkey: Passkey? = null
var originApp: OriginApp? = null
var appOrigin: AppOrigin? = null
var isTemplate: Boolean = false
constructor() : super()
@@ -80,7 +80,7 @@ class EntryInfo : NodeInfo {
autoType = parcel.readParcelableCompat() ?: autoType
otpModel = parcel.readParcelableCompat() ?: otpModel
passkey = parcel.readParcelableCompat() ?: passkey
originApp = parcel.readParcelableCompat() ?: originApp
appOrigin = parcel.readParcelableCompat() ?: appOrigin
isTemplate = parcel.readBooleanCompat()
}
@@ -103,7 +103,7 @@ class EntryInfo : NodeInfo {
parcel.writeParcelable(autoType, flags)
parcel.writeParcelable(otpModel, flags)
parcel.writeParcelable(passkey, flags)
parcel.writeParcelable(originApp, flags)
parcel.writeParcelable(appOrigin, flags)
parcel.writeBooleanCompat(isTemplate)
}
@@ -138,13 +138,6 @@ class EntryInfo : NodeInfo {
} ?: customFields.add(field)
}
/**
* Create a field name suffix depending on the field position
*/
private fun suffixFieldNamePosition(position: Int): String {
return if (position > 0) "_$position" else ""
}
/**
* Add a field to the custom fields list with a suffix position,
* replace if name already exists
@@ -218,8 +211,8 @@ class EntryInfo : NodeInfo {
registerInfo.password?.let { password = it }
setCreditCard(registerInfo.creditCard)
setPasskey(registerInfo.passkey)
setOriginApp(
registerInfo.originApp,
setAppOrigin(
registerInfo.appOrigin,
database?.allowEntryCustomFields() == true
)
}
@@ -250,7 +243,7 @@ class EntryInfo : NodeInfo {
if (autoType != other.autoType) return false
if (otpModel != other.otpModel) return false
if (passkey != other.passkey) return false
if (originApp != other.originApp) return false
if (appOrigin != other.appOrigin) return false
if (isTemplate != other.isTemplate) return false
return true
@@ -271,7 +264,7 @@ class EntryInfo : NodeInfo {
result = 31 * result + autoType.hashCode()
result = 31 * result + (otpModel?.hashCode() ?: 0)
result = 31 * result + (passkey?.hashCode() ?: 0)
result = 31 * result + (originApp?.hashCode() ?: 0)
result = 31 * result + (appOrigin?.hashCode() ?: 0)
result = 31 * result + isTemplate.hashCode()
return result
}
@@ -279,6 +272,13 @@ class EntryInfo : NodeInfo {
companion object {
/**
* Create a field name suffix depending on the field position
*/
fun suffixFieldNamePosition(position: Int): String {
return if (position > 0) "_$position" else ""
}
@JvmField
val CREATOR: Parcelable.Creator<EntryInfo> = object : Parcelable.Creator<EntryInfo> {
override fun createFromParcel(parcel: Parcel): EntryInfo {

View File

@@ -10,7 +10,7 @@ data class RegisterInfo(
val password: String? = null,
val creditCard: CreditCard? = null,
val passkey: Passkey? = null,
val originApp: OriginApp? = null
val appOrigin: AppOrigin? = null
): Parcelable {
constructor(parcel: Parcel) : this(
@@ -19,7 +19,7 @@ data class RegisterInfo(
password = parcel.readString() ?: "",
creditCard = parcel.readParcelableCompat(),
passkey = parcel.readParcelableCompat(),
originApp = parcel.readParcelableCompat()
appOrigin = parcel.readParcelableCompat()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
@@ -28,7 +28,7 @@ data class RegisterInfo(
parcel.writeString(password)
parcel.writeParcelable(creditCard, flags)
parcel.writeParcelable(passkey, flags)
parcel.writeParcelable(originApp, flags)
parcel.writeParcelable(appOrigin, flags)
}
override fun describeContents(): Int {