package com.kunzisoft.keepass.model import com.kunzisoft.keepass.database.element.Field import com.kunzisoft.keepass.database.element.security.ProtectedString object PasskeyEntryFields { // field names from KeypassXC are used const val FIELD_USERNAME = "KPEX_PASSKEY_USERNAME" const val FIELD_PRIVATE_KEY = "KPEX_PASSKEY_PRIVATE_KEY_PEM" 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 PASSKEY_FIELD = "Passkey" const val PASSKEY_TAG = "Passkey" /** * 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) if (usernameField == null || privateKeyField == null || credentialIdField == null || userHandleField == null || relyingPartyField == null) return null return Passkey( username = usernameField, privateKeyPem = privateKeyField, credentialId = credentialIdField, userHandle = userHandleField, relyingParty = relyingPartyField ) } fun EntryInfo.setPasskey(passkey: Passkey?) { if (passkey != null) { tags.put(PASSKEY_TAG) if (this.username.isEmpty()) this.username = passkey.username addOrReplaceField( Field( FIELD_USERNAME, ProtectedString(enableProtection = false, passkey.username) ) ) addOrReplaceField( Field( FIELD_PRIVATE_KEY, ProtectedString(enableProtection = true, passkey.privateKeyPem) ) ) addOrReplaceField( Field( FIELD_CREDENTIAL_ID, ProtectedString(enableProtection = true, passkey.credentialId) ) ) addOrReplaceField( Field( FIELD_USER_HANDLE, ProtectedString(enableProtection = true, passkey.userHandle) ) ) addOrReplaceField( Field( FIELD_RELYING_PARTY, ProtectedString(enableProtection = false, passkey.relyingParty) ) ) } } /** * Build new generated fields in a new list from [fieldsToParse] in parameter, * Remove parameters fields use to generate auto fields */ fun generateAutoFields(fieldsToParse: List): MutableList { val newCustomFields: MutableList = ArrayList(fieldsToParse) // Remove parameter fields val usernameField = Field(FIELD_USERNAME) val privateKeyField = Field(FIELD_PRIVATE_KEY) val credentialIdField = Field(FIELD_CREDENTIAL_ID) val userHandleField = Field(FIELD_USER_HANDLE) val relyingPartyField = Field(FIELD_RELYING_PARTY) newCustomFields.remove(usernameField) newCustomFields.remove(privateKeyField) newCustomFields.remove(credentialIdField) newCustomFields.remove(userHandleField) newCustomFields.remove(relyingPartyField) // Empty auto generated OTP Token field if (fieldsToParse.contains(usernameField) || fieldsToParse.contains(privateKeyField) || fieldsToParse.contains(credentialIdField) || fieldsToParse.contains(userHandleField) || fieldsToParse.contains(relyingPartyField) ) newCustomFields.add( Field( name = PASSKEY_FIELD, value = ProtectedString(enableProtection = false) ) ) return newCustomFields } /** * Detect if the current field is a Passkey */ fun Field.isPasskey(): Boolean { return when(name) { PASSKEY_FIELD -> true FIELD_USERNAME -> true FIELD_PRIVATE_KEY -> true FIELD_CREDENTIAL_ID -> true FIELD_USER_HANDLE -> true FIELD_RELYING_PARTY -> true else -> false } } /** * Detect if the current field is a Passkey relying party */ fun Field.isRelyingParty(): Boolean { return name == FIELD_RELYING_PARTY } }