mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
fix: Add check security
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user