mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
feat: Add KPEX_PASSKEY_FLAG_BE and KPEX_PASSKEY_FLAG_BS flags #2212
This commit is contained in:
@@ -3,7 +3,7 @@ KeePassDX(4.2.0)
|
|||||||
* Confirm usage of passkey #2165 #2124
|
* Confirm usage of passkey #2165 #2124
|
||||||
* Dialog to manage missing signature #2152 #2155 #2161 #2160
|
* Dialog to manage missing signature #2152 #2155 #2161 #2160
|
||||||
* Capture error #2159
|
* 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
|
* Search settings #2112 #2181 #2187
|
||||||
* Autofill refactoring #765 #2196
|
* Autofill refactoring #765 #2196
|
||||||
* Small fixes #2157 #2164 #2171 #2122 #2180 #2209
|
* Small fixes #2157 #2164 #2171 #2122 #2180 #2209
|
||||||
|
|||||||
@@ -388,11 +388,15 @@ object PasskeyHelper {
|
|||||||
* Utility method to create a passkey and the associated creation request parameters
|
* Utility method to create a passkey and the associated creation request parameters
|
||||||
* [intent] allows to retrieve the request
|
* [intent] allows to retrieve the request
|
||||||
* [context] context to manage package verification files
|
* [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
|
* [passkeyCreated] is called asynchronously when the passkey has been created
|
||||||
*/
|
*/
|
||||||
suspend fun retrievePasskeyCreationRequestParameters(
|
suspend fun retrievePasskeyCreationRequestParameters(
|
||||||
intent: Intent,
|
intent: Intent,
|
||||||
context: Context,
|
context: Context,
|
||||||
|
defaultBackupEligibility: Boolean?,
|
||||||
|
defaultBackupState: Boolean?,
|
||||||
passkeyCreated: suspend (Passkey, AppOrigin?, PublicKeyCredentialCreationParameters) -> Unit
|
passkeyCreated: suspend (Passkey, AppOrigin?, PublicKeyCredentialCreationParameters) -> Unit
|
||||||
) {
|
) {
|
||||||
val createCredentialRequest = PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
|
val createCredentialRequest = PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
|
||||||
@@ -420,7 +424,9 @@ object PasskeyHelper {
|
|||||||
privateKeyPem = privateKeyPem,
|
privateKeyPem = privateKeyPem,
|
||||||
credentialId = b64Encode(credentialId),
|
credentialId = b64Encode(credentialId),
|
||||||
userHandle = b64Encode(userHandle),
|
userHandle = b64Encode(userHandle),
|
||||||
relyingParty = relyingParty
|
relyingParty = relyingParty,
|
||||||
|
backupEligibility = defaultBackupEligibility,
|
||||||
|
backupState = defaultBackupState
|
||||||
)
|
)
|
||||||
|
|
||||||
// create new entry in database
|
// create new entry in database
|
||||||
@@ -554,8 +560,8 @@ object PasskeyHelper {
|
|||||||
requestOptions: PublicKeyCredentialRequestOptions,
|
requestOptions: PublicKeyCredentialRequestOptions,
|
||||||
clientDataResponse: ClientDataResponse,
|
clientDataResponse: ClientDataResponse,
|
||||||
passkey: Passkey,
|
passkey: Passkey,
|
||||||
backupEligibility: Boolean,
|
defaultBackupEligibility: Boolean,
|
||||||
backupState: Boolean
|
defaultBackupState: Boolean
|
||||||
): PublicKeyCredential {
|
): PublicKeyCredential {
|
||||||
val getCredentialResponse = FidoPublicKeyCredential(
|
val getCredentialResponse = FidoPublicKeyCredential(
|
||||||
id = passkey.credentialId,
|
id = passkey.credentialId,
|
||||||
@@ -563,8 +569,8 @@ object PasskeyHelper {
|
|||||||
requestOptions = requestOptions,
|
requestOptions = requestOptions,
|
||||||
userPresent = true,
|
userPresent = true,
|
||||||
userVerified = true,
|
userVerified = true,
|
||||||
backupEligibility = backupEligibility,
|
backupEligibility = passkey.backupEligibility ?: defaultBackupEligibility,
|
||||||
backupState = backupState,
|
backupState = passkey.backupState ?: defaultBackupState,
|
||||||
userHandle = passkey.userHandle,
|
userHandle = passkey.userHandle,
|
||||||
privateKey = passkey.privateKeyPem,
|
privateKey = passkey.privateKeyPem,
|
||||||
clientDataResponse = clientDataResponse
|
clientDataResponse = clientDataResponse
|
||||||
|
|||||||
@@ -307,8 +307,8 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView
|
|||||||
appOrigin = appOrigin
|
appOrigin = appOrigin
|
||||||
),
|
),
|
||||||
passkey = passkey,
|
passkey = passkey,
|
||||||
backupEligibility = mBackupEligibility,
|
defaultBackupEligibility = mBackupEligibility,
|
||||||
backupState = mBackupState
|
defaultBackupState = mBackupState
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -363,8 +363,8 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView
|
|||||||
appOrigin = appOrigin
|
appOrigin = appOrigin
|
||||||
),
|
),
|
||||||
passkey = passkey,
|
passkey = passkey,
|
||||||
backupEligibility = mBackupEligibility,
|
defaultBackupEligibility = mBackupEligibility,
|
||||||
backupState = mBackupState
|
defaultBackupState = mBackupState
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -400,6 +400,8 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView
|
|||||||
retrievePasskeyCreationRequestParameters(
|
retrievePasskeyCreationRequestParameters(
|
||||||
intent = intent,
|
intent = intent,
|
||||||
context = getApplication(),
|
context = getApplication(),
|
||||||
|
defaultBackupEligibility = mBackupEligibility,
|
||||||
|
defaultBackupState = mBackupState,
|
||||||
passkeyCreated = { passkey, appInfoToStore, publicKeyCredentialParameters ->
|
passkeyCreated = { passkey, appInfoToStore, publicKeyCredentialParameters ->
|
||||||
// Save the requested parameters
|
// Save the requested parameters
|
||||||
mPasskey = passkey
|
mPasskey = passkey
|
||||||
@@ -503,8 +505,10 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView
|
|||||||
intent = responseIntent,
|
intent = responseIntent,
|
||||||
response = buildCreatePublicKeyCredentialResponse(
|
response = buildCreatePublicKeyCredentialResponse(
|
||||||
publicKeyCredentialCreationParameters = it,
|
publicKeyCredentialCreationParameters = it,
|
||||||
backupEligibility = mBackupEligibility,
|
backupEligibility = passkey?.backupEligibility
|
||||||
backupState = mBackupState
|
?: mBackupEligibility,
|
||||||
|
backupState = passkey?.backupState
|
||||||
|
?: mBackupState
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ import com.kunzisoft.keepass.database.exception.UnknownDatabaseLocationException
|
|||||||
import com.kunzisoft.keepass.database.exception.VersionDatabaseException
|
import com.kunzisoft.keepass.database.exception.VersionDatabaseException
|
||||||
import com.kunzisoft.keepass.database.exception.XMLMalformedDatabaseException
|
import com.kunzisoft.keepass.database.exception.XMLMalformedDatabaseException
|
||||||
import com.kunzisoft.keepass.model.PasskeyEntryFields.FIELD_CREDENTIAL_ID
|
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_PRIVATE_KEY
|
||||||
import com.kunzisoft.keepass.model.PasskeyEntryFields.FIELD_RELYING_PARTY
|
import com.kunzisoft.keepass.model.PasskeyEntryFields.FIELD_RELYING_PARTY
|
||||||
import com.kunzisoft.keepass.model.PasskeyEntryFields.FIELD_USERNAME
|
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_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_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_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
|
else -> name
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -771,5 +771,7 @@
|
|||||||
<string name="passkey_credential_id">Passkey Credential Id</string>
|
<string name="passkey_credential_id">Passkey Credential Id</string>
|
||||||
<string name="passkey_user_handle">Passkey User Handle</string>
|
<string name="passkey_user_handle">Passkey User Handle</string>
|
||||||
<string name="passkey_relying_party">Passkey Relying Party</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>
|
<string name="error_passkey_result">Unable to return the passkey</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -74,6 +74,17 @@ class ProtectedString : Parcelable {
|
|||||||
return arrayOfNulls(size)
|
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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,5 +28,32 @@ data class Passkey(
|
|||||||
val privateKeyPem: String,
|
val privateKeyPem: String,
|
||||||
val credentialId: String,
|
val credentialId: String,
|
||||||
val userHandle: String,
|
val userHandle: String,
|
||||||
val relyingParty: String
|
val relyingParty: String,
|
||||||
): Parcelable
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.kunzisoft.keepass.model
|
|||||||
|
|
||||||
import com.kunzisoft.keepass.database.element.Field
|
import com.kunzisoft.keepass.database.element.Field
|
||||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
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 {
|
object PasskeyEntryFields {
|
||||||
|
|
||||||
@@ -12,6 +14,8 @@ object PasskeyEntryFields {
|
|||||||
const val FIELD_CREDENTIAL_ID = "KPEX_PASSKEY_CREDENTIAL_ID"
|
const val FIELD_CREDENTIAL_ID = "KPEX_PASSKEY_CREDENTIAL_ID"
|
||||||
const val FIELD_USER_HANDLE = "KPEX_PASSKEY_USER_HANDLE"
|
const val FIELD_USER_HANDLE = "KPEX_PASSKEY_USER_HANDLE"
|
||||||
const val FIELD_RELYING_PARTY = "KPEX_PASSKEY_RELYING_PARTY"
|
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_FIELD = "Passkey"
|
||||||
const val PASSKEY_TAG = "Passkey"
|
const val PASSKEY_TAG = "Passkey"
|
||||||
@@ -20,11 +24,14 @@ object PasskeyEntryFields {
|
|||||||
* Parse fields of an entry to retrieve a Passkey
|
* Parse fields of an entry to retrieve a Passkey
|
||||||
*/
|
*/
|
||||||
fun parseFields(getField: (id: String) -> String?): Passkey? {
|
fun parseFields(getField: (id: String) -> String?): Passkey? {
|
||||||
val usernameField = getField(FIELD_USERNAME)
|
val usernameField: String? = getField(FIELD_USERNAME)
|
||||||
val privateKeyField = getField(FIELD_PRIVATE_KEY)
|
val privateKeyField: String? = getField(FIELD_PRIVATE_KEY)
|
||||||
val credentialIdField = getField(FIELD_CREDENTIAL_ID)
|
val credentialIdField: String? = getField(FIELD_CREDENTIAL_ID)
|
||||||
val userHandleField = getField(FIELD_USER_HANDLE)
|
val userHandleField: String? = getField(FIELD_USER_HANDLE)
|
||||||
val relyingPartyField = getField(FIELD_RELYING_PARTY)
|
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
|
if (usernameField == null
|
||||||
|| privateKeyField == null
|
|| privateKeyField == null
|
||||||
|| credentialIdField == null
|
|| credentialIdField == null
|
||||||
@@ -36,7 +43,9 @@ object PasskeyEntryFields {
|
|||||||
privateKeyPem = privateKeyField,
|
privateKeyPem = privateKeyField,
|
||||||
credentialId = credentialIdField,
|
credentialId = credentialIdField,
|
||||||
userHandle = userHandleField,
|
userHandle = userHandleField,
|
||||||
relyingParty = relyingPartyField
|
relyingParty = relyingPartyField,
|
||||||
|
backupEligibility = backupEligibilityField,
|
||||||
|
backupState = backupStateField
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +100,24 @@ object PasskeyEntryFields {
|
|||||||
ProtectedString(enableProtection = false, passkey.relyingParty)
|
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
|
return overwrite
|
||||||
}
|
}
|
||||||
@@ -107,17 +134,23 @@ object PasskeyEntryFields {
|
|||||||
val credentialIdField = Field(FIELD_CREDENTIAL_ID)
|
val credentialIdField = Field(FIELD_CREDENTIAL_ID)
|
||||||
val userHandleField = Field(FIELD_USER_HANDLE)
|
val userHandleField = Field(FIELD_USER_HANDLE)
|
||||||
val relyingPartyField = Field(FIELD_RELYING_PARTY)
|
val relyingPartyField = Field(FIELD_RELYING_PARTY)
|
||||||
|
val backupEligibilityField = Field(FIELD_FLAG_BE)
|
||||||
|
val backupStateField = Field(FIELD_FLAG_BS)
|
||||||
newCustomFields.remove(usernameField)
|
newCustomFields.remove(usernameField)
|
||||||
newCustomFields.remove(privateKeyField)
|
newCustomFields.remove(privateKeyField)
|
||||||
newCustomFields.remove(credentialIdField)
|
newCustomFields.remove(credentialIdField)
|
||||||
newCustomFields.remove(userHandleField)
|
newCustomFields.remove(userHandleField)
|
||||||
newCustomFields.remove(relyingPartyField)
|
newCustomFields.remove(relyingPartyField)
|
||||||
// Empty auto generated OTP Token field
|
newCustomFields.remove(backupEligibilityField)
|
||||||
|
newCustomFields.remove(backupStateField)
|
||||||
|
// Empty auto generated Passkey field
|
||||||
if (fieldsToParse.contains(usernameField)
|
if (fieldsToParse.contains(usernameField)
|
||||||
|| fieldsToParse.contains(privateKeyField)
|
|| fieldsToParse.contains(privateKeyField)
|
||||||
|| fieldsToParse.contains(credentialIdField)
|
|| fieldsToParse.contains(credentialIdField)
|
||||||
|| fieldsToParse.contains(userHandleField)
|
|| fieldsToParse.contains(userHandleField)
|
||||||
|| fieldsToParse.contains(relyingPartyField)
|
|| fieldsToParse.contains(relyingPartyField)
|
||||||
|
|| fieldsToParse.contains(backupEligibilityField)
|
||||||
|
|| fieldsToParse.contains(backupStateField)
|
||||||
)
|
)
|
||||||
newCustomFields.add(
|
newCustomFields.add(
|
||||||
Field(
|
Field(
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
* Passkeys management #1421 (Thx @cali-95)
|
* Passkeys management #1421
|
||||||
|
* Add KPEX_PASSKEY_FLAG_BE and KPEX_PASSKEY_FLAG_BS flags #2212
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user