diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseModeActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseModeActivity.kt index c7832033b..ba1a200ee 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseModeActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseModeActivity.kt @@ -9,6 +9,7 @@ import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.isIntentSenderMode import com.kunzisoft.keepass.credentialprovider.SpecialMode import com.kunzisoft.keepass.credentialprovider.TypeMode +import com.kunzisoft.keepass.model.RegisterInfo import com.kunzisoft.keepass.model.SearchInfo import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.view.ToolbarSpecial @@ -113,7 +114,8 @@ abstract class DatabaseModeActivity : DatabaseActivity() { mSpecialMode = EntrySelectionHelper.retrieveSpecialModeFromIntent(intent) mTypeMode = EntrySelectionHelper.retrieveTypeModeFromIntent(intent) - val searchInfo: SearchInfo? = EntrySelectionHelper.retrieveRegisterInfoFromIntent(intent)?.searchInfo + val registerInfo: RegisterInfo? = EntrySelectionHelper.retrieveRegisterInfoFromIntent(intent) + val searchInfo: SearchInfo? = registerInfo?.searchInfo ?: EntrySelectionHelper.retrieveSearchInfoFromIntent(intent) // To show the selection mode @@ -137,7 +139,7 @@ abstract class DatabaseModeActivity : DatabaseActivity() { if (mTypeMode != TypeMode.DEFAULT) title = "$title (${getString(typeModeStringId)})" // Populate subtitle - subtitle = searchInfo?.getName(resources) + subtitle = registerInfo?.getName(resources) ?: searchInfo?.getName(resources) // Show the toolbar or not visible = when (mSpecialMode) { diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/PasskeyLauncherActivity.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/PasskeyLauncherActivity.kt index 75fdce993..d04a36075 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/PasskeyLauncherActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/PasskeyLauncherActivity.kt @@ -48,7 +48,7 @@ import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addSe 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.getVerifiedClientDataResponse +import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.getVerifiedGETClientDataResponse import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removeAppOrigin import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removePasskey import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.retrieveAppOrigin @@ -85,37 +85,31 @@ class PasskeyLauncherActivity : DatabaseModeActivity() { val responseIntent = Intent() try { Log.d(TAG, "Passkey selection result") - val passkey = intent?.retrievePasskey() - val appOrigin = intent?.retrieveAppOrigin() - intent?.removePasskey() - intent?.removeAppOrigin() - passkey?.let { - mUsageParameters?.let { usageParameters -> - // Check verified origin - getVerifiedClientDataResponse( - usageParameters = usageParameters, - appOrigin = appOrigin, - onOriginChecked = { clientDataResponse -> - PendingIntentHandler.setGetCredentialResponse( - responseIntent, - GetCredentialResponse( - buildPasskeyPublicKeyCredential( - requestOptions = usageParameters.publicKeyCredentialRequestOptions, - clientDataResponse = clientDataResponse, - passkey = passkey - ) - ) - ) - }, - onOriginNotChecked = { - throw SecurityException("Wrong signature for ${usageParameters.androidApp.id}") - } + if (intent == null) + throw IOException("Intent is null") + val passkey = intent.retrievePasskey() + ?: throw IOException("Passkey is null") + val appOrigin = intent.retrieveAppOrigin() + ?: throw IOException("App origin is null") + intent.removePasskey() + intent.removeAppOrigin() + mUsageParameters?.let { usageParameters -> + // Check verified origin + PendingIntentHandler.setGetCredentialResponse( + responseIntent, + GetCredentialResponse( + buildPasskeyPublicKeyCredential( + requestOptions = usageParameters.publicKeyCredentialRequestOptions, + clientDataResponse = getVerifiedGETClientDataResponse( + usageParameters = usageParameters, + appOrigin = appOrigin + ), + passkey = passkey + ) ) - } ?: run { - throw IOException("Usage parameters is null") - } + ) } ?: run { - throw IOException("Passkey is null") + throw IOException("Usage parameters is null") } } catch (e: Exception) { Log.e(TAG, "Unable to create selection response for passkey", e) @@ -197,7 +191,8 @@ class PasskeyLauncherActivity : DatabaseModeActivity() { private fun autoSelectPasskeyAndSetResult( database: ContextualDatabase?, - nodeId: UUID + nodeId: UUID, + appOrigin: AppOrigin ) { mUsageParameters?.let { usageParameters -> // To get the passkey from the database @@ -213,7 +208,10 @@ class PasskeyLauncherActivity : DatabaseModeActivity() { GetCredentialResponse( buildPasskeyPublicKeyCredential( requestOptions = usageParameters.publicKeyCredentialRequestOptions, - clientDataResponse = usageParameters.clientDataResponse, + clientDataResponse = getVerifiedGETClientDataResponse( + usageParameters = usageParameters, + appOrigin = appOrigin + ), passkey = passkey ) ) @@ -230,23 +228,20 @@ class PasskeyLauncherActivity : DatabaseModeActivity() { private suspend fun launchSelection( database: ContextualDatabase?, nodeId: UUID?, - searchInfo: SearchInfo?, - appOrigin: AppOrigin? + searchInfo: SearchInfo, + appOrigin: AppOrigin ) { Log.d(TAG, "Launch passkey selection") retrievePasskeyUsageRequestParameters( intent = intent, assetManager = assets, - appOrigin = appOrigin + appOrigin = appOrigin, ) { usageParameters -> // Save the requested parameters mUsageParameters = usageParameters // Manage the passkey to use nodeId?.let { nodeId -> - if (usageParameters.androidAppVerified.not()) { - throw SecurityException("Wrong signature for ${usageParameters.androidApp.id}") - } - autoSelectPasskeyAndSetResult(database, nodeId) + autoSelectPasskeyAndSetResult(database, nodeId, appOrigin) } ?: run { SearchHelper.checkAutoSearchInfo( context = this, diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/data/PublicKeyCredentialUsageParameters.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/data/PublicKeyCredentialUsageParameters.kt index 9eeed4cff..9bb0f80fb 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/data/PublicKeyCredentialUsageParameters.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/data/PublicKeyCredentialUsageParameters.kt @@ -19,11 +19,12 @@ */ package com.kunzisoft.keepass.credentialprovider.passkey.data -import com.kunzisoft.keepass.model.AppIdentifier +import com.kunzisoft.keepass.model.AndroidOrigin +import com.kunzisoft.keepass.model.WebOrigin data class PublicKeyCredentialUsageParameters( val publicKeyCredentialRequestOptions: PublicKeyCredentialRequestOptions, val clientDataResponse: ClientDataResponse, - val androidApp: AppIdentifier, - val androidAppVerified: Boolean + val androidOrigin: AndroidOrigin, + val webOrigin: WebOrigin, ) \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/util/OriginManager.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/util/OriginManager.kt index 98d6bd93a..01f94e60d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/util/OriginManager.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/util/OriginManager.kt @@ -25,8 +25,10 @@ import android.util.Log import androidx.annotation.RequiresApi import androidx.credentials.provider.CallingAppInfo import com.kunzisoft.encrypt.HashManager.getApplicationSignatures -import com.kunzisoft.keepass.model.AppIdentifier +import com.kunzisoft.keepass.model.AndroidOrigin import com.kunzisoft.keepass.model.AppOrigin +import com.kunzisoft.keepass.model.Verification +import com.kunzisoft.keepass.model.WebOrigin import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -37,7 +39,8 @@ import kotlinx.coroutines.withContext class OriginManager( private val providedClientDataHash: ByteArray?, private val callingAppInfo: CallingAppInfo?, - private val assets: AssetManager + private val assets: AssetManager, + private val relyingParty: String, ) { /** @@ -50,20 +53,23 @@ class OriginManager( onOriginCreated: (appInfoToStore: AppOrigin, origin: String) -> Unit ) { getOrigin( - onOriginRetrieved = { appIdentifier, callOrigin, clientDataHash -> + onOriginRetrieved = { androidOrigin, webOrigin, callOrigin, clientDataHash -> onOriginRetrieved( AppOrigin().apply { - // Do not store Web Browser AppId -> addIdentifier(appIdentifier) - addWebDomain(callOrigin) + addAndroidOrigin(androidOrigin) + addWebOrigin(webOrigin) }, clientDataHash ) }, - onOriginNotRetrieved = { appIdentifier -> + onOriginNotRetrieved = { appIdentifier, webOrigin -> // Create a new Android Origin and prepare the signature app storage onOriginCreated( - AppOrigin().apply { addIdentifier(appIdentifier) }, - appIdentifier.buildAndroidOrigin() + AppOrigin().apply { + addAndroidOrigin(appIdentifier) + addWebOrigin(webOrigin) + }, + appIdentifier.toAndroidOrigin() ) } ) @@ -75,25 +81,27 @@ class OriginManager( * calls [onOriginCreated] if the origin was created manually, origin is verified if present in the KeePass database */ suspend fun getOriginAtUsage( - appOrigin: AppOrigin?, - onOriginRetrieved: (appIdentifier: AppIdentifier, clientDataHash: ByteArray) -> Unit, - onOriginCreated: (appIdentifier: AppIdentifier, origin: String, originVerified: Boolean) -> Unit + appOrigin: AppOrigin, + onOriginRetrieved: (androidOrigin: AndroidOrigin, webOrigin: WebOrigin, clientDataHash: ByteArray) -> Unit, + onOriginCreated: (androidOrigin: AndroidOrigin, webOrigin: WebOrigin) -> Unit ) { getOrigin( - onOriginRetrieved = { appIdentifier, origin, clientDataHash -> - onOriginRetrieved(appIdentifier, clientDataHash) + onOriginRetrieved = { androidOrigin, webOrigin, origin, clientDataHash -> + onOriginRetrieved(androidOrigin, webOrigin, clientDataHash) }, - onOriginNotRetrieved = { appIdentifierToCheck -> - // Verify the app signature to retrieve the origin - val androidOrigin = appIdentifierToCheck.buildAndroidOrigin() - appIdentifierToCheck.checkInAppOrigin( - appOrigin = appOrigin, - onOriginChecked = { - onOriginCreated(appIdentifierToCheck, androidOrigin, true) - }, - onOriginNotChecked = { - onOriginCreated(appIdentifierToCheck, androidOrigin, false) - } + onOriginNotRetrieved = { appIdentifierToCheck, webOrigin -> + // Check the app signature in the appOrigin, webOrigin cannot be checked now + onOriginCreated( + AndroidOrigin( + packageName = appIdentifierToCheck.packageName, + signature = appIdentifierToCheck.signature, + verification = + if (appOrigin.containsVerifiedAndroidOrigin(appIdentifierToCheck)) + Verification.MANUALLY_VERIFIED + else + Verification.NOT_VERIFIED + ), + webOrigin ) } ) @@ -106,8 +114,8 @@ class OriginManager( * call [onOriginNotRetrieved] if the origin is not retrieved from the system */ private suspend fun getOrigin( - onOriginRetrieved: (appInfoRetrieved: AppIdentifier, origin: String, clientDataHash: ByteArray) -> Unit, - onOriginNotRetrieved: (appInfoRetrieved: AppIdentifier) -> Unit + onOriginRetrieved: (androidOrigin: AndroidOrigin, webOrigin: WebOrigin, origin: String, clientDataHash: ByteArray) -> Unit, + onOriginNotRetrieved: (androidOrigin: AndroidOrigin, webOrigin: WebOrigin) -> Unit ) { if (callingAppInfo == null) { throw SecurityException("Calling app info cannot be retrieved") @@ -119,17 +127,37 @@ class OriginManager( } // for trusted browsers like Chrome and Firefox callOrigin = callingAppInfo.getOrigin(privilegedAllowlist)?.removeSuffix("/") - val appIdentifier = AppIdentifier( - id = callingAppInfo.packageName, + val androidOrigin = AndroidOrigin( + packageName = callingAppInfo.packageName, signature = callingAppInfo.signingInfo - .getApplicationSignatures() + .getApplicationSignatures(), + verification = Verification.NOT_VERIFIED ) + // Check if the webDomain is validated for the withContext(Dispatchers.Main) { if (callOrigin != null && providedClientDataHash != null) { Log.d(TAG, "Origin $callOrigin retrieved from callingAppInfo") - onOriginRetrieved(appIdentifier, callOrigin, providedClientDataHash) + onOriginRetrieved( + AndroidOrigin( + packageName = androidOrigin.packageName, + signature = androidOrigin.signature, + verification = Verification.AUTOMATICALLY_VERIFIED + ), + WebOrigin.fromRelyingParty( + relyingParty = relyingParty, + verification = Verification.AUTOMATICALLY_VERIFIED + ), + callOrigin, + providedClientDataHash + ) } else { - onOriginNotRetrieved(appIdentifier) + onOriginNotRetrieved( + androidOrigin, + WebOrigin.fromRelyingParty( + relyingParty = relyingParty, + verification = Verification.NOT_VERIFIED + ) + ) } } } @@ -137,42 +165,5 @@ class OriginManager( companion object { private val TAG = OriginManager::class.simpleName - - /** - * Verify that the application signature is contained in the [appOrigin] - */ - fun AppIdentifier.checkInAppOrigin( - appOrigin: AppOrigin?, - onOriginChecked: (origin: String) -> Unit, - onOriginNotChecked: () -> Unit - ) { - // Verify the app signature to retrieve the origin - val appIdentifierStored = appOrigin?.appIdentifiers?.filter { - it.id == this.id - } - if (appIdentifierStored?.any { it.signature == this.signature } == true) { - onOriginChecked(this.buildAndroidOrigin()) - } else { - onOriginNotChecked() - } - } - - /** - * Builds an Android Origin from a AppIdentifier - */ - fun AppIdentifier.buildAndroidOrigin(): String { - return buildAndroidOrigin(this.id) - } - - /** - * Builds an Android Origin from a package name. - */ - private fun buildAndroidOrigin(packageName: String?): String { - if (packageName.isNullOrEmpty()) - throw SecurityException("Package name cannot be empty") - val packageOrigin = "androidapp://${packageName}" - Log.d(TAG, "Origin $packageOrigin retrieved from package name") - return packageOrigin - } } } \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/util/PasskeyHelper.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/util/PasskeyHelper.kt index 01be9c148..8b4350886 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/util/PasskeyHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/util/PasskeyHelper.kt @@ -51,11 +51,11 @@ import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredential import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialRequestOptions import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialUsageParameters import com.kunzisoft.keepass.credentialprovider.passkey.util.Base64Helper.Companion.b64Encode -import com.kunzisoft.keepass.credentialprovider.passkey.util.OriginManager.Companion.checkInAppOrigin import com.kunzisoft.keepass.model.AppOrigin import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.Passkey import com.kunzisoft.keepass.model.SearchInfo +import com.kunzisoft.keepass.model.Verification import com.kunzisoft.keepass.utils.StringUtil.toHexString import com.kunzisoft.keepass.utils.getParcelableExtraCompat import com.kunzisoft.random.KeePassDXRandom @@ -364,7 +364,8 @@ object PasskeyHelper { OriginManager( providedClientDataHash = clientDataHash, callingAppInfo = callingAppInfo, - assets = assetManager + assets = assetManager, + relyingParty = relyingParty ).getOriginAtCreation( onOriginRetrieved = { appInfoToStore, clientDataHash -> passkeyCreated.invoke( @@ -441,7 +442,7 @@ object PasskeyHelper { suspend fun retrievePasskeyUsageRequestParameters( intent: Intent, assetManager: AssetManager, - appOrigin: AppOrigin?, + appOrigin: AppOrigin, result: (PublicKeyCredentialUsageParameters) -> Unit ) { val getCredentialRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent) @@ -456,30 +457,32 @@ object PasskeyHelper { OriginManager( providedClientDataHash = clientDataHash, callingAppInfo = callingAppInfo, - assets = assetManager + assets = assetManager, + relyingParty = requestOptions.rpId ).getOriginAtUsage( appOrigin = appOrigin, - onOriginRetrieved = { appIdentifier, clientDataHash -> + onOriginRetrieved = { androidOrigin, webOrigin, clientDataHash -> result.invoke( PublicKeyCredentialUsageParameters( publicKeyCredentialRequestOptions = requestOptions, clientDataResponse = ClientDataDefinedResponse(clientDataHash), - androidApp = appIdentifier, - androidAppVerified = true + androidOrigin = androidOrigin, + webOrigin = webOrigin ) ) }, - onOriginCreated = { appIdentifier, origin, verified -> + onOriginCreated = { androidOrigin, webOrigin -> + // By default we crate an usage parameter with Android origin result.invoke( PublicKeyCredentialUsageParameters( publicKeyCredentialRequestOptions = requestOptions, clientDataResponse = ClientDataBuildResponse( type = ClientDataBuildResponse.Type.GET, challenge = requestOptions.challenge, - origin = origin + origin = androidOrigin.toAndroidOrigin() ), - androidApp = appIdentifier, - androidAppVerified = verified + androidOrigin = androidOrigin, + webOrigin = webOrigin ) ) } @@ -518,30 +521,35 @@ object PasskeyHelper { * Verify that the application signature is contained in the [appOrigin] * or that the webDomain contains the origin */ - fun getVerifiedClientDataResponse( + fun getVerifiedGETClientDataResponse( usageParameters: PublicKeyCredentialUsageParameters, - appOrigin: AppOrigin?, - onOriginChecked: (clientDataResponse: ClientDataResponse) -> Unit, - onOriginNotChecked: () -> Unit - ) { - if (usageParameters.androidAppVerified) { - onOriginChecked(usageParameters.clientDataResponse) + appOrigin: AppOrigin + ): ClientDataResponse { + val appToCheck = usageParameters.androidOrigin + val webToCheck = usageParameters.webOrigin + if (appToCheck.verification == Verification.AUTOMATICALLY_VERIFIED) { + return usageParameters.clientDataResponse } else { - usageParameters.androidApp.checkInAppOrigin( - appOrigin = appOrigin, - onOriginChecked = { origin -> - // Origin checked by Android app signature - onOriginChecked( - ClientDataBuildResponse( - type = ClientDataBuildResponse.Type.GET, - challenge = usageParameters.publicKeyCredentialRequestOptions.challenge, - origin = origin - ) + if (appOrigin.containsVerifiedAndroidOrigin(appToCheck)) { + if (webToCheck.verification.verified + || appOrigin.containsVerifiedWebOrigin(webToCheck)) { + // Origin checked by URL + return ClientDataBuildResponse( + type = ClientDataBuildResponse.Type.GET, + challenge = usageParameters.publicKeyCredentialRequestOptions.challenge, + origin = webToCheck.toWebOrigin() ) - }, - onOriginNotChecked - ) + } + // Origin checked by Android app signature + return ClientDataBuildResponse( + type = ClientDataBuildResponse.Type.GET, + challenge = usageParameters.publicKeyCredentialRequestOptions.challenge, + origin = appOrigin.firstVerifiedWebOrigin()?.toWebOrigin() + ?: appToCheck.toAndroidOrigin() + ) + } else { + throw SecurityException("Wrong signature for ${appToCheck.packageName}") + } } } - } \ No newline at end of file diff --git a/database/src/main/java/com/kunzisoft/keepass/model/AppOrigin.kt b/database/src/main/java/com/kunzisoft/keepass/model/AppOrigin.kt index 58c35849e..9d0785640 100644 --- a/database/src/main/java/com/kunzisoft/keepass/model/AppOrigin.kt +++ b/database/src/main/java/com/kunzisoft/keepass/model/AppOrigin.kt @@ -24,38 +24,96 @@ import kotlinx.parcelize.Parcelize @Parcelize data class AppOrigin( - val appIdentifiers: MutableList = mutableListOf(), - val webDomains: MutableList = mutableListOf() + val androidOrigins: MutableList = mutableListOf(), + val webOrigins: MutableList = mutableListOf() ) : Parcelable { - fun addIdentifier(appIdentifier: AppIdentifier) { - appIdentifiers.add(appIdentifier) + fun addAndroidOrigin(androidOrigin: AndroidOrigin) { + androidOrigins.add(androidOrigin) } - fun addWebDomain(webDomain: String) { - this.webDomains.add(webDomain) + fun addWebOrigin(webOrigin: WebOrigin) { + this.webOrigins.add(webOrigin) } - fun removeAppElement(appIdentifier: AppIdentifier) { - appIdentifiers.remove(appIdentifier) + fun containsVerifiedAndroidOrigin(androidOrigin: AndroidOrigin): Boolean { + return androidOrigins.any { + it.packageName == androidOrigin.packageName + && it.signature == androidOrigin.signature + && it.verification.verified + } } - fun removeWebDomain(webDomain: String) { - this.webDomains.remove(webDomain) + fun containsVerifiedWebOrigin(webOrigin: WebOrigin): Boolean { + return this.webOrigins.any { + it.origin == webOrigin.origin + && it.verification.verified + } + } + + fun firstVerifiedWebOrigin(): WebOrigin? { + return webOrigins.first { + it.verification.verified + } } fun clear() { - appIdentifiers.clear() - webDomains.clear() + androidOrigins.clear() + webOrigins.clear() } fun isEmpty(): Boolean { - return appIdentifiers.isEmpty() && webDomains.isEmpty() + return androidOrigins.isEmpty() && webOrigins.isEmpty() + } + + fun toName(): String? { + return if (androidOrigins.isNotEmpty()) { + androidOrigins.first().packageName + } else if (webOrigins.isNotEmpty()){ + webOrigins.first().origin + } else null + } +} + +enum class Verification { + MANUALLY_VERIFIED, AUTOMATICALLY_VERIFIED, NOT_VERIFIED; + + val verified: Boolean + get() = this == MANUALLY_VERIFIED || this == AUTOMATICALLY_VERIFIED +} + +@Parcelize +data class AndroidOrigin( + val packageName: String, + val signature: String? = null, + val verification: Verification = Verification.AUTOMATICALLY_VERIFIED, +) : Parcelable { + + fun toAndroidOrigin(): String { + return "androidapp://${packageName}" } } @Parcelize -data class AppIdentifier( - val id: String, - val signature: String? = null, -) : Parcelable \ No newline at end of file +data class WebOrigin( + val origin: String, + val assetLinks: String? = null, + val verification: Verification = Verification.AUTOMATICALLY_VERIFIED, +) : Parcelable { + + fun toWebOrigin(): String { + return origin + } + + fun defaultAssetLinks(): String { + return "${origin}/.well-known/assetlinks.json" + } + + companion object { + const val RELYING_PARTY_DEFAULT_PROTOCOL = "https" + fun fromRelyingParty(relyingParty: String, verification: Verification): WebOrigin = WebOrigin( + origin ="$RELYING_PARTY_DEFAULT_PROTOCOL://$relyingParty", + verification = verification + ) + } +} \ No newline at end of file diff --git a/database/src/main/java/com/kunzisoft/keepass/model/AppOriginEntryField.kt b/database/src/main/java/com/kunzisoft/keepass/model/AppOriginEntryField.kt index 8a26903b8..d7c84a4ac 100644 --- a/database/src/main/java/com/kunzisoft/keepass/model/AppOriginEntryField.kt +++ b/database/src/main/java/com/kunzisoft/keepass/model/AppOriginEntryField.kt @@ -48,8 +48,8 @@ object AppOriginEntryField { } }.takeWhile { it != null } .forEach { pair -> - appOrigin.addIdentifier( - AppIdentifier(pair!!.first, pair.second) + appOrigin.addAndroidOrigin( + AndroidOrigin(pair!!.first, pair.second) ) } // Get Domains @@ -58,7 +58,7 @@ object AppOriginEntryField { val domainKey = WEB_DOMAIN_FIELD_NAME + suffixFieldNamePosition(domainFieldPosition) val domainValue = getField(domainKey) if (domainValue != null) { - appOrigin.addWebDomain(domainValue) + appOrigin.addWebOrigin(WebOrigin(origin = domainValue)) domainFieldPosition++ } else { break // No more domain found @@ -137,11 +137,12 @@ object AppOriginEntryField { * Only if [customFieldsAllowed] is true */ fun EntryInfo.setAppOrigin(appOrigin: AppOrigin?, customFieldsAllowed: Boolean) { - appOrigin?.appIdentifiers?.forEach { appIdentifier -> - setApplicationId(appIdentifier.id, appIdentifier.signature) + appOrigin?.androidOrigins?.forEach { appIdentifier -> + setApplicationId(appIdentifier.packageName, appIdentifier.signature) } - appOrigin?.webDomains?.forEach { webDomain -> - setWebDomain(webDomain, null, customFieldsAllowed) + appOrigin?.webOrigins?.forEach { webOrigin -> + if (webOrigin.verification.verified) + setWebDomain(webOrigin.origin, null, customFieldsAllowed) } } } \ No newline at end of file diff --git a/database/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt b/database/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt index 850313cbd..14a881300 100644 --- a/database/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt +++ b/database/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt @@ -176,7 +176,16 @@ class EntryInfo : NodeInfo { } /** - * Add searchInfo to current EntryInfo, return true if new data, false if no modification + * Capitalize and remove suffix of a title + */ + fun String.toTitle(): String { + return this.replaceFirstChar { + if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() + } + } + + /** + * Add searchInfo to current EntryInfo */ fun saveSearchInfo(database: Database?, searchInfo: SearchInfo) { searchInfo.otpString?.let { otpString -> @@ -191,20 +200,13 @@ class EntryInfo : NodeInfo { setApplicationId(applicationId) } if (title.isEmpty()) { - title = searchInfo.toTitle() + title = searchInfo.toString().toTitle() } } /** - * Capitalize and remove suffix of web domain to create a title + * Add registerInfo to current EntryInfo */ - fun SearchInfo.toTitle(): String { - val webDomain = this.webDomain - return webDomain?.substring(0, webDomain.lastIndexOf('.'))?.replaceFirstChar { - if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() - } ?: this.toString() - } - fun saveRegisterInfo(database: Database?, registerInfo: RegisterInfo) { saveSearchInfo(database, registerInfo.searchInfo) registerInfo.username?.let { username = it } @@ -215,6 +217,9 @@ class EntryInfo : NodeInfo { registerInfo.appOrigin, database?.allowEntryCustomFields() == true ) + if (title.isEmpty()) { + title = registerInfo.toString().toTitle() + } } fun getVisualTitle(): String { diff --git a/database/src/main/java/com/kunzisoft/keepass/model/RegisterInfo.kt b/database/src/main/java/com/kunzisoft/keepass/model/RegisterInfo.kt index a475b6955..1ec709f41 100644 --- a/database/src/main/java/com/kunzisoft/keepass/model/RegisterInfo.kt +++ b/database/src/main/java/com/kunzisoft/keepass/model/RegisterInfo.kt @@ -1,7 +1,9 @@ package com.kunzisoft.keepass.model +import android.content.res.Resources import android.os.Parcel import android.os.Parcelable +import com.kunzisoft.keepass.utils.ObjectNameResource import com.kunzisoft.keepass.utils.readParcelableCompat data class RegisterInfo( @@ -11,12 +13,12 @@ data class RegisterInfo( val creditCard: CreditCard? = null, val passkey: Passkey? = null, val appOrigin: AppOrigin? = null -): Parcelable { +) : ObjectNameResource, Parcelable { constructor(parcel: Parcel) : this( searchInfo = parcel.readParcelableCompat() ?: SearchInfo(), - username = parcel.readString() ?: "", - password = parcel.readString() ?: "", + username = parcel.readString(), + password = parcel.readString(), creditCard = parcel.readParcelableCompat(), passkey = parcel.readParcelableCompat(), appOrigin = parcel.readParcelableCompat() @@ -35,6 +37,20 @@ data class RegisterInfo( return 0 } + override fun getName(resources: Resources): String { + return username + ?: passkey?.relyingParty + ?: appOrigin?.toName() + ?: searchInfo.getName(resources) + } + + override fun toString(): String { + return username + ?: passkey?.relyingParty + ?: appOrigin?.toName() + ?: searchInfo.toString() + } + companion object CREATOR : Parcelable.Creator { override fun createFromParcel(parcel: Parcel): RegisterInfo { return RegisterInfo(parcel)