fix: Add check security

This commit is contained in:
J-Jamet
2025-08-20 20:56:40 +02:00
parent 44e8f4f406
commit 7e09532d5d
3 changed files with 68 additions and 45 deletions

View File

@@ -19,7 +19,6 @@
*/
package com.kunzisoft.keepass.credentialprovider.activity
import android.app.Activity
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
@@ -42,13 +41,17 @@ import com.kunzisoft.keepass.credentialprovider.TypeMode
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialCreationParameters
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialUsageParameters
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAuthCode
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addNodeId
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addSearchInfo
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildCreatePublicKeyCredentialResponse
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyPublicKeyCredential
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.checkSecurity
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removePasskey
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.retrieveNodeId
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.retrievePasskey
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.retrievePasskeyCreationRequestParameters
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.retrievePasskeyUsageRequestParameters
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.retrieveSearchInfo
import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.helper.SearchHelper
@@ -56,7 +59,6 @@ import com.kunzisoft.keepass.model.EntryInfoPasskey.getPasskey
import com.kunzisoft.keepass.model.Passkey
import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
import java.util.UUID
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@@ -138,17 +140,20 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
override fun onDatabaseRetrieved(database: ContextualDatabase?) {
super.onDatabaseRetrieved(database)
// TODO nodeId Really useful ? checkSecurity(intent, nodeId)
val nodeId = intent.retrieveNodeId()
checkSecurity(intent, nodeId)
when (mSpecialMode) {
SpecialMode.SELECTION -> {
launchSelection(database, mSearchInfo)
launchSelection(database, nodeId, mSearchInfo)
}
SpecialMode.REGISTRATION -> {
launchRegistration(database, mSearchInfo)
// TODO Registration in predefined group
// launchRegistration(database, nodeId, mSearchInfo)
launchRegistration(database, null, mSearchInfo)
}
else -> {
Log.e(TAG, "Passkey launch mode not supported")
setResult(Activity.RESULT_CANCELED)
setResult(RESULT_CANCELED)
finish()
}
}
@@ -187,6 +192,7 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
private fun launchSelection(
database: ContextualDatabase?,
nodeId: UUID?,
searchInfo: SearchInfo?
) {
Log.d(TAG, "Launch passkey selection")
@@ -194,7 +200,7 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
// Save the requested parameters
mUsageParameters = usageParameters
// Manage the passkey to use
intent.retrieveNodeId()?.let { nodeId ->
nodeId?.let { nodeId ->
autoSelectPasskeyAndSetResult(database, nodeId)
} ?: run {
SearchHelper.checkAutoSearchInfo(
@@ -232,9 +238,10 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
private fun autoRegisterPasskeyAndSetResult(
database: ContextualDatabase?,
nodeId: UUID
nodeId: UUID,
passkey: Passkey
) {
// TODO Overwrite automatic selection
// TODO Overwrite and Register in a predefined group
mCreationParameters?.let { creationParameters ->
// To set the passkey to the database
setResult(RESULT_OK)
@@ -248,6 +255,7 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
private fun launchRegistration(
database: ContextualDatabase?,
nodeId: UUID?,
searchInfo: SearchInfo
) {
Log.d(TAG, "Launch passkey registration")
@@ -266,8 +274,8 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
passkey = passkey
)
// If nodeId already provided
intent.retrieveNodeId()?.let { nodeId ->
autoRegisterPasskeyAndSetResult(database, nodeId)
nodeId?.let { nodeId ->
autoRegisterPasskeyAndSetResult(database, nodeId, passkey)
} ?: run {
SearchHelper.checkAutoSearchInfo(
context = this,
@@ -311,21 +319,21 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
companion object {
private val TAG = PasskeyLauncherActivity::class.java.name
private const val EXTRA_SEARCH_INFO = "com.kunzisoft.keepass.extra.SEARCH_INFO"
fun Intent.retrieveSearchInfo(): SearchInfo? {
return this.getParcelableExtraCompat(EXTRA_SEARCH_INFO)
}
fun Intent.removeSearchInfo() {
return this.removeExtra(EXTRA_SEARCH_INFO)
}
/**
* Get a pending intent to launch the passkey launcher activity
* [nodeId] can be :
* - null if manual selection is requested
* - null if manual registration is requested
* - an entry node id if direct selection is requested
* - a group node id if direct registration is requested in a default group
* - an entry node id if overwriting is requested in an existing entry
*/
fun getPendingIntent(
context: Context,
specialMode: SpecialMode,
searchInfo: SearchInfo? = null,
passkeyEntryNodeId: UUID? = null
nodeId: UUID? = null
): PendingIntent? {
return PendingIntent.getActivity(
context,
@@ -333,10 +341,9 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
Intent(context, PasskeyLauncherActivity::class.java).apply {
addSpecialMode(specialMode)
addTypeMode(TypeMode.PASSKEY)
searchInfo?.let {
putExtra(EXTRA_SEARCH_INFO, searchInfo)
}
addAuthCode(passkeyEntryNodeId)
addSearchInfo(searchInfo)
addNodeId(nodeId)
addAuthCode(nodeId)
},
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)

View File

@@ -137,7 +137,7 @@ class PasskeyProviderService : CredentialProviderService() {
PasskeyLauncherActivity.getPendingIntent(
context = applicationContext,
specialMode = SpecialMode.SELECTION,
passkeyEntryNodeId = passkeyEntry.id
nodeId = passkeyEntry.id
)?.let { usagePendingIntent ->
val passkey = passkeyEntry.getPasskey()
passkeyEntries.add(

View File

@@ -55,6 +55,7 @@ import com.kunzisoft.keepass.credentialprovider.passkey.util.OriginManager.Compa
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.EntryInfoPasskey.getPasskey
import com.kunzisoft.keepass.model.Passkey
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.utils.StringUtil.toHexString
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
import com.kunzisoft.random.KeePassDXRandom
@@ -73,9 +74,11 @@ object PasskeyHelper {
private const val HMAC_TYPE = "HmacSHA256"
private const val KEY_NODE_ID = "nodeId"
private const val KEY_TIMESTAMP = "timestamp"
private const val KEY_AUTHENTICATION_CODE = "authenticationCode"
private const val EXTRA_SEARCH_INFO = "com.kunzisoft.keepass.extra.SEARCH_INFO"
private const val EXTRA_NODE_ID = "com.kunzisoft.keepass.extra.nodeId"
private const val EXTRA_TIMESTAMP = "com.kunzisoft.keepass.extra.timestamp"
private const val EXTRA_AUTHENTICATION_CODE = "com.kunzisoft.keepass.extra.authenticationCode"
private const val SEPARATOR = "_"
@@ -117,19 +120,17 @@ object PasskeyHelper {
}
fun Intent.addAuthCode(passkeyEntryNodeId: UUID? = null) {
passkeyEntryNodeId?.let {
putExtras(Bundle().apply {
val timestamp = Instant.now().epochSecond
putParcelable(KEY_NODE_ID, ParcelUuid(passkeyEntryNodeId))
putString(KEY_TIMESTAMP, timestamp.toString())
putString(EXTRA_TIMESTAMP, timestamp.toString())
putString(
KEY_AUTHENTICATION_CODE, generatedAuthenticationCode(
EXTRA_AUTHENTICATION_CODE,
generatedAuthenticationCode(
passkeyEntryNodeId, timestamp
).toHexString()
)
})
}
}
fun Intent.retrievePasskey(): Passkey? {
return this.getParcelableExtraCompat(EXTRA_PASSKEY_ELEMENT)
@@ -139,12 +140,28 @@ object PasskeyHelper {
return this.removeExtra(EXTRA_PASSKEY_ELEMENT)
}
fun Intent.addSearchInfo(searchInfo: SearchInfo?) {
searchInfo?.let {
putExtra(EXTRA_SEARCH_INFO, searchInfo)
}
}
fun Intent.retrieveSearchInfo(): SearchInfo? {
return this.getParcelableExtraCompat(EXTRA_SEARCH_INFO)
}
fun Intent.addNodeId(nodeId: UUID?) {
nodeId?.let {
putExtra(EXTRA_NODE_ID, ParcelUuid(nodeId))
}
}
fun Intent.retrieveNodeId(): UUID? {
return getParcelableExtraCompat<ParcelUuid>(KEY_NODE_ID)?.uuid
return getParcelableExtraCompat<ParcelUuid>(EXTRA_NODE_ID)?.uuid
}
fun checkSecurity(intent: Intent, nodeId: UUID?) {
val timestampString = intent.getStringExtra(KEY_TIMESTAMP)
val timestampString = intent.getStringExtra(EXTRA_TIMESTAMP)
if (timestampString.isNullOrEmpty())
throw CreateCredentialUnknownException("Timestamp null")
if (timestampString.matches(REGEX_TIMESTAMP).not()) {
@@ -155,9 +172,8 @@ object PasskeyHelper {
if (diff < 0 || diff > MAX_DIFF_IN_SECONDS) {
throw CreateCredentialUnknownException("Out of time")
}
verifyAuthenticationCode(
intent.getStringExtra(KEY_AUTHENTICATION_CODE),
intent.getStringExtra(EXTRA_AUTHENTICATION_CODE),
generatedAuthenticationCode(nodeId, timestamp)
)
}