feat: Add KPEX_PASSKEY_FLAG_BE and KPEX_PASSKEY_FLAG_BS flags #2212

This commit is contained in:
J-Jamet
2025-10-14 14:21:52 +02:00
parent f1f7dd1e6c
commit fef88ff270
10 changed files with 112 additions and 23 deletions

View File

@@ -3,7 +3,7 @@ KeePassDX(4.2.0)
* Confirm usage of passkey #2165 #2124
* Dialog to manage missing signature #2152 #2155 #2161 #2160
* Capture error #2159
* Change Passkey Backup Eligibility & Backup State #2135 #2150
* Change Passkey Backup Eligibility & Backup State #2135 #2150 #2212
* Search settings #2112 #2181 #2187
* Autofill refactoring #765 #2196
* Small fixes #2157 #2164 #2171 #2122 #2180 #2209

View File

@@ -388,11 +388,15 @@ object PasskeyHelper {
* Utility method to create a passkey and the associated creation request parameters
* [intent] allows to retrieve the request
* [context] context to manage package verification files
* [defaultBackupEligibility] the default backup eligibility to add the the passkey entry
* [defaultBackupState] the default backup state to add the the passkey entry
* [passkeyCreated] is called asynchronously when the passkey has been created
*/
suspend fun retrievePasskeyCreationRequestParameters(
intent: Intent,
context: Context,
defaultBackupEligibility: Boolean?,
defaultBackupState: Boolean?,
passkeyCreated: suspend (Passkey, AppOrigin?, PublicKeyCredentialCreationParameters) -> Unit
) {
val createCredentialRequest = PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
@@ -420,7 +424,9 @@ object PasskeyHelper {
privateKeyPem = privateKeyPem,
credentialId = b64Encode(credentialId),
userHandle = b64Encode(userHandle),
relyingParty = relyingParty
relyingParty = relyingParty,
backupEligibility = defaultBackupEligibility,
backupState = defaultBackupState
)
// create new entry in database
@@ -554,8 +560,8 @@ object PasskeyHelper {
requestOptions: PublicKeyCredentialRequestOptions,
clientDataResponse: ClientDataResponse,
passkey: Passkey,
backupEligibility: Boolean,
backupState: Boolean
defaultBackupEligibility: Boolean,
defaultBackupState: Boolean
): PublicKeyCredential {
val getCredentialResponse = FidoPublicKeyCredential(
id = passkey.credentialId,
@@ -563,8 +569,8 @@ object PasskeyHelper {
requestOptions = requestOptions,
userPresent = true,
userVerified = true,
backupEligibility = backupEligibility,
backupState = backupState,
backupEligibility = passkey.backupEligibility ?: defaultBackupEligibility,
backupState = passkey.backupState ?: defaultBackupState,
userHandle = passkey.userHandle,
privateKey = passkey.privateKeyPem,
clientDataResponse = clientDataResponse

View File

@@ -307,8 +307,8 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView
appOrigin = appOrigin
),
passkey = passkey,
backupEligibility = mBackupEligibility,
backupState = mBackupState
defaultBackupEligibility = mBackupEligibility,
defaultBackupState = mBackupState
)
)
)
@@ -363,8 +363,8 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView
appOrigin = appOrigin
),
passkey = passkey,
backupEligibility = mBackupEligibility,
backupState = mBackupState
defaultBackupEligibility = mBackupEligibility,
defaultBackupState = mBackupState
)
)
)
@@ -400,6 +400,8 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView
retrievePasskeyCreationRequestParameters(
intent = intent,
context = getApplication(),
defaultBackupEligibility = mBackupEligibility,
defaultBackupState = mBackupState,
passkeyCreated = { passkey, appInfoToStore, publicKeyCredentialParameters ->
// Save the requested parameters
mPasskey = passkey
@@ -503,8 +505,10 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView
intent = responseIntent,
response = buildCreatePublicKeyCredentialResponse(
publicKeyCredentialCreationParameters = it,
backupEligibility = mBackupEligibility,
backupState = mBackupState
backupEligibility = passkey?.backupEligibility
?: mBackupEligibility,
backupState = passkey?.backupState
?: mBackupState
)
)
}

View File

@@ -47,6 +47,8 @@ import com.kunzisoft.keepass.database.exception.UnknownDatabaseLocationException
import com.kunzisoft.keepass.database.exception.VersionDatabaseException
import com.kunzisoft.keepass.database.exception.XMLMalformedDatabaseException
import com.kunzisoft.keepass.model.PasskeyEntryFields.FIELD_CREDENTIAL_ID
import com.kunzisoft.keepass.model.PasskeyEntryFields.FIELD_FLAG_BE
import com.kunzisoft.keepass.model.PasskeyEntryFields.FIELD_FLAG_BS
import com.kunzisoft.keepass.model.PasskeyEntryFields.FIELD_PRIVATE_KEY
import com.kunzisoft.keepass.model.PasskeyEntryFields.FIELD_RELYING_PARTY
import com.kunzisoft.keepass.model.PasskeyEntryFields.FIELD_USERNAME
@@ -146,6 +148,8 @@ fun TemplateField.getLocalizedName(context: Context?, name: String): String {
FIELD_CREDENTIAL_ID.equals(name, true) -> context.getString(R.string.passkey_credential_id)
FIELD_USER_HANDLE.equals(name, true) -> context.getString(R.string.passkey_user_handle)
FIELD_RELYING_PARTY.equals(name, true) -> context.getString(R.string.passkey_relying_party)
FIELD_FLAG_BE.equals(name, true) -> context.getString(R.string.passkey_backup_eligibility)
FIELD_FLAG_BS.equals(name, true) -> context.getString(R.string.passkey_backup_state)
else -> name
}

View File

@@ -771,5 +771,7 @@
<string name="passkey_credential_id">Passkey Credential Id</string>
<string name="passkey_user_handle">Passkey User Handle</string>
<string name="passkey_relying_party">Passkey Relying Party</string>
<string name="passkey_backup_eligibility">Passkey Backup Eligibility</string>
<string name="passkey_backup_state">Passkey Backup State</string>
<string name="error_passkey_result">Unable to return the passkey</string>
</resources>

View File

@@ -74,6 +74,17 @@ class ProtectedString : Parcelable {
return arrayOfNulls(size)
}
}
fun String.toBooleanCompat(): Boolean {
return if (this.equals("1", ignoreCase = true))
true
else
this.toBoolean()
}
fun Boolean.toFieldValue(): String {
return if (this) "1" else "0"
}
}
}

View File

@@ -28,5 +28,32 @@ data class Passkey(
val privateKeyPem: String,
val credentialId: String,
val userHandle: String,
val relyingParty: String
): Parcelable
val relyingParty: String,
val backupEligibility: Boolean?,
val backupState: Boolean?
): Parcelable {
// Do not compare BE and BS because are modifiable by the user
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Passkey
if (username != other.username) return false
if (privateKeyPem != other.privateKeyPem) return false
if (credentialId != other.credentialId) return false
if (userHandle != other.userHandle) return false
if (relyingParty != other.relyingParty) return false
return true
}
override fun hashCode(): Int {
var result = username.hashCode()
result = 31 * result + privateKeyPem.hashCode()
result = 31 * result + credentialId.hashCode()
result = 31 * result + userHandle.hashCode()
result = 31 * result + relyingParty.hashCode()
return result
}
}

View File

@@ -2,6 +2,8 @@ package com.kunzisoft.keepass.model
import com.kunzisoft.keepass.database.element.Field
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.element.security.ProtectedString.Companion.toBooleanCompat
import com.kunzisoft.keepass.database.element.security.ProtectedString.Companion.toFieldValue
object PasskeyEntryFields {
@@ -12,6 +14,8 @@ object PasskeyEntryFields {
const val FIELD_CREDENTIAL_ID = "KPEX_PASSKEY_CREDENTIAL_ID"
const val FIELD_USER_HANDLE = "KPEX_PASSKEY_USER_HANDLE"
const val FIELD_RELYING_PARTY = "KPEX_PASSKEY_RELYING_PARTY"
const val FIELD_FLAG_BE = "KPEX_PASSKEY_FLAG_BE"
const val FIELD_FLAG_BS = "KPEX_PASSKEY_FLAG_BS"
const val PASSKEY_FIELD = "Passkey"
const val PASSKEY_TAG = "Passkey"
@@ -20,11 +24,14 @@ object PasskeyEntryFields {
* Parse fields of an entry to retrieve a Passkey
*/
fun parseFields(getField: (id: String) -> String?): Passkey? {
val usernameField = getField(FIELD_USERNAME)
val privateKeyField = getField(FIELD_PRIVATE_KEY)
val credentialIdField = getField(FIELD_CREDENTIAL_ID)
val userHandleField = getField(FIELD_USER_HANDLE)
val relyingPartyField = getField(FIELD_RELYING_PARTY)
val usernameField: String? = getField(FIELD_USERNAME)
val privateKeyField: String? = getField(FIELD_PRIVATE_KEY)
val credentialIdField: String? = getField(FIELD_CREDENTIAL_ID)
val userHandleField: String? = getField(FIELD_USER_HANDLE)
val relyingPartyField: String? = getField(FIELD_RELYING_PARTY)
// Optional fields
val backupEligibilityField: Boolean? = getField(FIELD_FLAG_BE)?.toBooleanCompat()
val backupStateField: Boolean? = getField(FIELD_FLAG_BS)?.toBooleanCompat()
if (usernameField == null
|| privateKeyField == null
|| credentialIdField == null
@@ -36,7 +43,9 @@ object PasskeyEntryFields {
privateKeyPem = privateKeyField,
credentialId = credentialIdField,
userHandle = userHandleField,
relyingParty = relyingPartyField
relyingParty = relyingPartyField,
backupEligibility = backupEligibilityField,
backupState = backupStateField
)
}
@@ -91,6 +100,24 @@ object PasskeyEntryFields {
ProtectedString(enableProtection = false, passkey.relyingParty)
)
)
passkey.backupEligibility?.let { backupEligibility ->
addOrReplaceField(
Field(
FIELD_FLAG_BE,
ProtectedString(enableProtection = false,
backupEligibility.toFieldValue())
)
)
}
passkey.backupState?.let { backupState ->
addOrReplaceField(
Field(
FIELD_FLAG_BS,
ProtectedString(enableProtection = false,
backupState.toFieldValue())
)
)
}
}
return overwrite
}
@@ -107,17 +134,23 @@ object PasskeyEntryFields {
val credentialIdField = Field(FIELD_CREDENTIAL_ID)
val userHandleField = Field(FIELD_USER_HANDLE)
val relyingPartyField = Field(FIELD_RELYING_PARTY)
val backupEligibilityField = Field(FIELD_FLAG_BE)
val backupStateField = Field(FIELD_FLAG_BS)
newCustomFields.remove(usernameField)
newCustomFields.remove(privateKeyField)
newCustomFields.remove(credentialIdField)
newCustomFields.remove(userHandleField)
newCustomFields.remove(relyingPartyField)
// Empty auto generated OTP Token field
newCustomFields.remove(backupEligibilityField)
newCustomFields.remove(backupStateField)
// Empty auto generated Passkey field
if (fieldsToParse.contains(usernameField)
|| fieldsToParse.contains(privateKeyField)
|| fieldsToParse.contains(credentialIdField)
|| fieldsToParse.contains(userHandleField)
|| fieldsToParse.contains(relyingPartyField)
|| fieldsToParse.contains(backupEligibilityField)
|| fieldsToParse.contains(backupStateField)
)
newCustomFields.add(
Field(

View File

@@ -1 +1,2 @@
* Passkeys management #1421 (Thx @cali-95)
* Passkeys management #1421
* Add KPEX_PASSKEY_FLAG_BE and KPEX_PASSKEY_FLAG_BS flags #2212

View File

@@ -1 +1,2 @@
* Gestion de Passkeys #1421 (Thx @cali-95)
* Gestion de Passkeys #1421
* Ajout des flags KPEX_PASSKEY_FLAG_BE et KPEX_PASSKEY_FLAG_BS #2212