mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
fix: Select passkey in passwordless mode #2282
This commit is contained in:
@@ -42,6 +42,7 @@ import androidx.credentials.provider.CredentialEntry
|
||||
import androidx.credentials.provider.CredentialProviderService
|
||||
import androidx.credentials.provider.ProviderClearCredentialStateRequest
|
||||
import androidx.credentials.provider.PublicKeyCredentialEntry
|
||||
import com.kunzisoft.encrypt.Base64Helper.Companion.b64Encode
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.buildIcon
|
||||
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
||||
@@ -90,8 +91,11 @@ class PasskeyProviderService : CredentialProviderService() {
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun buildPasskeySearchInfo(relyingParty: String): SearchInfo {
|
||||
private fun buildPasskeySearchInfo(relyingParty: String, credentialId: String? = null): SearchInfo {
|
||||
return SearchInfo().apply {
|
||||
credentialId?.let {
|
||||
this.credentialId = it
|
||||
}
|
||||
this.relyingParty = relyingParty
|
||||
}
|
||||
}
|
||||
@@ -108,6 +112,10 @@ class PasskeyProviderService : CredentialProviderService() {
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(javaClass.simpleName, "onBeginGetCredentialRequest error", e)
|
||||
when (e) {
|
||||
is IOException -> toastError(e)
|
||||
else -> {}
|
||||
}
|
||||
callback.onError(GetCredentialUnknownException())
|
||||
}
|
||||
}
|
||||
@@ -136,12 +144,13 @@ class PasskeyProviderService : CredentialProviderService() {
|
||||
option: BeginGetPublicKeyCredentialOption,
|
||||
callback: (List<CredentialEntry>) -> Unit
|
||||
) {
|
||||
|
||||
val passkeyEntries: MutableList<CredentialEntry> = mutableListOf()
|
||||
|
||||
val relyingPartyId = PublicKeyCredentialRequestOptions(option.requestJson).rpId
|
||||
val searchInfo = buildPasskeySearchInfo(relyingPartyId)
|
||||
Log.d(TAG, "Build passkey search for relying party $relyingPartyId")
|
||||
val publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions(option.requestJson)
|
||||
val relyingPartyId = publicKeyCredentialRequestOptions.rpId
|
||||
val credentialId = publicKeyCredentialRequestOptions.allowCredentials.firstOrNull()?.id?.let { b64Encode(it) }
|
||||
val searchInfo = buildPasskeySearchInfo(relyingPartyId, credentialId)
|
||||
Log.d(TAG, "Build passkey search for relying party $relyingPartyId, credentialId $credentialId")
|
||||
SearchHelper.checkAutoSearchInfo(
|
||||
context = this,
|
||||
database = mDatabase,
|
||||
@@ -287,10 +296,11 @@ class PasskeyProviderService : CredentialProviderService() {
|
||||
getString(R.string.passkey_database_username)
|
||||
else databaseName
|
||||
val createEntries: MutableList<CreateEntry> = mutableListOf()
|
||||
val relyingPartyId = PublicKeyCredentialCreationOptions(
|
||||
val publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions(
|
||||
requestJson = request.requestJson,
|
||||
clientDataHash = request.clientDataHash
|
||||
).relyingPartyEntity.id
|
||||
)
|
||||
val relyingPartyId = publicKeyCredentialCreationOptions.relyingPartyEntity.id
|
||||
val searchInfo = buildPasskeySearchInfo(relyingPartyId)
|
||||
Log.d(TAG, "Build passkey search for relying party $relyingPartyId")
|
||||
SearchHelper.checkAutoSearchInfo(
|
||||
|
||||
@@ -42,11 +42,10 @@ class PublicKeyCredentialCreationOptions(
|
||||
val rpJson = json.getJSONObject("rp")
|
||||
relyingPartyEntity = PublicKeyCredentialRpEntity(rpJson.getString("name"), rpJson.getString("id"))
|
||||
val rpUser = json.getJSONObject("user")
|
||||
val userId = Base64Helper.b64Decode(rpUser.getString("id"))
|
||||
userEntity =
|
||||
PublicKeyCredentialUserEntity(
|
||||
rpUser.getString("name"),
|
||||
userId,
|
||||
Base64Helper.b64Decode(rpUser.getString("id")),
|
||||
rpUser.getString("displayName")
|
||||
)
|
||||
challenge = Base64Helper.b64Decode(json.getString("challenge"))
|
||||
|
||||
@@ -23,9 +23,32 @@ import com.kunzisoft.encrypt.Base64Helper
|
||||
import org.json.JSONObject
|
||||
|
||||
class PublicKeyCredentialRequestOptions(requestJson: String) {
|
||||
val json: JSONObject = JSONObject(requestJson)
|
||||
private val json: JSONObject = JSONObject(requestJson)
|
||||
val challenge: ByteArray = Base64Helper.b64Decode(json.getString("challenge"))
|
||||
val timeout: Long = json.optLong("timeout", 0)
|
||||
val rpId: String = json.optString("rpId", "")
|
||||
val allowCredentials: List<PublicKeyCredentialDescriptor>
|
||||
val userVerification: String = json.optString("userVerification", "preferred")
|
||||
|
||||
init {
|
||||
val allowCredentialsJson = json.getJSONArray("allowCredentials")
|
||||
val allowCredentialsTmp: MutableList<PublicKeyCredentialDescriptor> = mutableListOf()
|
||||
for (i in 0 until allowCredentialsJson.length()) {
|
||||
val allowCredentialJson = allowCredentialsJson.getJSONObject(i)
|
||||
|
||||
val transports: MutableList<String> = mutableListOf()
|
||||
val transportsJson = allowCredentialJson.getJSONArray("transports")
|
||||
for (j in 0 until transportsJson.length()) {
|
||||
transports.add(transportsJson.getString(j))
|
||||
}
|
||||
allowCredentialsTmp.add(
|
||||
PublicKeyCredentialDescriptor(
|
||||
type = allowCredentialJson.getString("type"),
|
||||
id = Base64Helper.b64Decode(allowCredentialJson.getString("id")),
|
||||
transports = transports
|
||||
)
|
||||
)
|
||||
}
|
||||
allowCredentials = allowCredentialsTmp.toList()
|
||||
}
|
||||
}
|
||||
@@ -94,6 +94,7 @@ object SearchHelper {
|
||||
callback.invoke(
|
||||
SearchParameters().apply {
|
||||
searchQuery = query
|
||||
searchOption = optionString()
|
||||
allowEmptyQuery = false
|
||||
searchInTitles = false
|
||||
searchInUsernames = false
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.kunzisoft.keepass.model.AppOriginEntryField.isAppIdSignature
|
||||
import com.kunzisoft.keepass.model.AppOriginEntryField.isWebDomain
|
||||
import com.kunzisoft.keepass.model.PasskeyEntryFields.isPasskey
|
||||
import com.kunzisoft.keepass.model.PasskeyEntryFields.isRelyingParty
|
||||
import com.kunzisoft.keepass.model.PasskeyEntryFields.isCredentialId
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields.isOTP
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields.isOTPURIField
|
||||
import com.kunzisoft.keepass.utils.UUIDUtils.asHexString
|
||||
@@ -176,11 +177,33 @@ class SearchHelper {
|
||||
}
|
||||
}
|
||||
if (searchParameters.searchInRelyingParty) {
|
||||
if(entry.getExtraFields().any { field ->
|
||||
val relyingParty = searchParameters.searchQuery
|
||||
val credentialId = searchParameters.searchOption
|
||||
val containsRelyingParty = entry.getExtraFields().any { field ->
|
||||
field.isRelyingParty()
|
||||
&& checkSearchQuery(field.protectedValue.stringValue, searchParameters)
|
||||
})
|
||||
return true
|
||||
&& checkSearchQuery(
|
||||
stringToCheck = field.protectedValue.stringValue,
|
||||
searchParameters = SearchParameters().apply {
|
||||
searchQuery = relyingParty
|
||||
searchInRelyingParty = true
|
||||
caseSensitive = false
|
||||
isRegex = false
|
||||
}
|
||||
)
|
||||
}
|
||||
val containsCredentialId = if(credentialId == null) true
|
||||
else entry.getExtraFields().any { field ->
|
||||
field.isCredentialId()
|
||||
&& checkSearchQuery(
|
||||
stringToCheck = field.protectedValue.stringValue,
|
||||
searchParameters = SearchParameters().apply {
|
||||
searchQuery = credentialId
|
||||
caseSensitive = false
|
||||
isRegex = false
|
||||
}
|
||||
)
|
||||
}
|
||||
return containsRelyingParty && containsCredentialId
|
||||
}
|
||||
if (searchParameters.searchInNotes) {
|
||||
if (checkSearchQuery(entry.notes, searchParameters))
|
||||
|
||||
@@ -27,6 +27,8 @@ import android.os.Parcelable
|
||||
*/
|
||||
class SearchParameters() : Parcelable{
|
||||
var searchQuery: String = ""
|
||||
// Add an optional string to search with the main search query
|
||||
var searchOption: String? = null
|
||||
var allowEmptyQuery = true
|
||||
var caseSensitive = false
|
||||
var isRegex = false
|
||||
|
||||
@@ -176,6 +176,13 @@ object PasskeyEntryFields {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if the current field is a Passkey credential id
|
||||
*/
|
||||
fun Field.isCredentialId(): Boolean {
|
||||
return name == FIELD_CREDENTIAL_ID
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if the current field is a Passkey relying party
|
||||
*/
|
||||
|
||||
@@ -35,6 +35,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
||||
return if (webDomain == null) null else field
|
||||
}
|
||||
var relyingParty: String? = null
|
||||
var credentialId: String? = null
|
||||
var otpString: String? = null
|
||||
|
||||
constructor()
|
||||
@@ -46,6 +47,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
||||
webDomain = toCopy?.webDomain
|
||||
webScheme = toCopy?.webScheme
|
||||
relyingParty = toCopy?.relyingParty
|
||||
credentialId = toCopy?.credentialId
|
||||
otpString = toCopy?.otpString
|
||||
}
|
||||
|
||||
@@ -61,6 +63,8 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
||||
webScheme = if (readScheme.isNullOrEmpty()) null else readScheme
|
||||
val readRelyingParty = parcel.readString()
|
||||
relyingParty = if (readRelyingParty.isNullOrEmpty()) null else readRelyingParty
|
||||
val readCredentialId = parcel.readString()
|
||||
credentialId = if (readCredentialId.isNullOrEmpty()) null else readCredentialId
|
||||
val readOtp = parcel.readString()
|
||||
otpString = if (readOtp.isNullOrEmpty()) null else readOtp
|
||||
}
|
||||
@@ -76,6 +80,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
||||
parcel.writeString(webDomain ?: "")
|
||||
parcel.writeString(webScheme ?: "")
|
||||
parcel.writeString(relyingParty ?: "")
|
||||
parcel.writeString(credentialId ?: "")
|
||||
parcel.writeString(otpString ?: "")
|
||||
}
|
||||
|
||||
@@ -94,6 +99,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
||||
&& webDomain == null
|
||||
&& webScheme == null
|
||||
&& relyingParty == null
|
||||
&& credentialId == null
|
||||
&& otpString == null
|
||||
}
|
||||
|
||||
@@ -127,6 +133,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
||||
if (webDomain != other.webDomain) return false
|
||||
if (webScheme != other.webScheme) return false
|
||||
if (relyingParty != other.relyingParty) return false
|
||||
if (credentialId != other.credentialId) return false
|
||||
if (otpString != other.otpString) return false
|
||||
|
||||
return true
|
||||
@@ -139,6 +146,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
||||
result = 31 * result + (webDomain?.hashCode() ?: 0)
|
||||
result = 31 * result + (webScheme?.hashCode() ?: 0)
|
||||
result = 31 * result + (relyingParty?.hashCode() ?: 0)
|
||||
result = 31 * result + (credentialId?.hashCode() ?: 0)
|
||||
result = 31 * result + (otpString?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
@@ -147,6 +155,10 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
||||
return otpString ?: webDomain ?: applicationId ?: relyingParty ?: tag ?: ""
|
||||
}
|
||||
|
||||
fun optionString(): String? {
|
||||
return if (isPasskeySearch && credentialId != null) credentialId else null
|
||||
}
|
||||
|
||||
fun toRegisterInfo(): RegisterInfo {
|
||||
return RegisterInfo(this)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user