mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
fix: Change Android origin
This commit is contained in:
@@ -234,8 +234,7 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
|
||||
Log.d(TAG, "Launch passkey selection")
|
||||
retrievePasskeyUsageRequestParameters(
|
||||
intent = intent,
|
||||
assetManager = assets,
|
||||
appOrigin = appOrigin,
|
||||
assetManager = assets
|
||||
) { usageParameters ->
|
||||
// Save the requested parameters
|
||||
mUsageParameters = usageParameters
|
||||
|
||||
@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.credentialprovider.passkey.data
|
||||
|
||||
import androidx.credentials.exceptions.GetCredentialUnknownException
|
||||
import com.kunzisoft.asymmetric.Signature
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.Base64Helper.Companion.b64Encode
|
||||
import com.kunzisoft.encrypt.Base64Helper.Companion.b64Encode
|
||||
import org.json.JSONObject
|
||||
|
||||
class AuthenticatorAssertionResponse(
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.credentialprovider.passkey.data
|
||||
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.Base64Helper.Companion.b64Encode
|
||||
import com.kunzisoft.encrypt.Base64Helper.Companion.b64Encode
|
||||
import com.kunzisoft.keepass.utils.UUIDUtils.asBytes
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
package com.kunzisoft.keepass.credentialprovider.passkey.data
|
||||
|
||||
import com.kunzisoft.encrypt.HashManager
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.Base64Helper.Companion.b64Encode
|
||||
import com.kunzisoft.encrypt.Base64Helper.Companion.b64Encode
|
||||
import org.json.JSONObject
|
||||
|
||||
open class ClientDataBuildResponse(
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
package com.kunzisoft.keepass.credentialprovider.passkey.data
|
||||
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.Base64Helper
|
||||
import com.kunzisoft.encrypt.Base64Helper
|
||||
import org.json.JSONObject
|
||||
|
||||
class PublicKeyCredentialCreationOptions(
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.credentialprovider.passkey.data
|
||||
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.Base64Helper
|
||||
import com.kunzisoft.encrypt.Base64Helper
|
||||
import org.json.JSONObject
|
||||
|
||||
class PublicKeyCredentialRequestOptions(requestJson: String) {
|
||||
|
||||
@@ -19,12 +19,10 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.credentialprovider.passkey.data
|
||||
|
||||
import com.kunzisoft.keepass.model.AndroidOrigin
|
||||
import com.kunzisoft.keepass.model.WebOrigin
|
||||
import com.kunzisoft.keepass.model.AppOrigin
|
||||
|
||||
data class PublicKeyCredentialUsageParameters(
|
||||
val publicKeyCredentialRequestOptions: PublicKeyCredentialRequestOptions,
|
||||
val clientDataResponse: ClientDataResponse,
|
||||
val androidOrigin: AndroidOrigin,
|
||||
val webOrigin: WebOrigin,
|
||||
val appOrigin: AppOrigin
|
||||
)
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.credentialprovider.passkey.util
|
||||
|
||||
import android.util.Base64
|
||||
|
||||
class Base64Helper {
|
||||
|
||||
companion object {
|
||||
|
||||
fun b64Decode(encodedString: String): ByteArray {
|
||||
return Base64.decode(
|
||||
encodedString,
|
||||
Base64.NO_PADDING or Base64.NO_WRAP or Base64.URL_SAFE
|
||||
)
|
||||
}
|
||||
|
||||
fun b64Encode(data: ByteArray): String {
|
||||
return Base64.encodeToString(
|
||||
data,
|
||||
Base64.NO_PADDING or Base64.NO_WRAP or Base64.URL_SAFE
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,10 +24,9 @@ import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.credentials.provider.CallingAppInfo
|
||||
import com.kunzisoft.encrypt.HashManager.getApplicationSignatures
|
||||
import com.kunzisoft.encrypt.HashManager.getApplicationFingerprints
|
||||
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
|
||||
@@ -81,27 +80,26 @@ 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: (androidOrigin: AndroidOrigin, webOrigin: WebOrigin, clientDataHash: ByteArray) -> Unit,
|
||||
onOriginCreated: (androidOrigin: AndroidOrigin, webOrigin: WebOrigin) -> Unit
|
||||
onOriginRetrieved: (appOrigin: AppOrigin, clientDataHash: ByteArray) -> Unit,
|
||||
onOriginCreated: (appOrigin: AppOrigin) -> Unit
|
||||
) {
|
||||
getOrigin(
|
||||
onOriginRetrieved = { androidOrigin, webOrigin, origin, clientDataHash ->
|
||||
onOriginRetrieved(androidOrigin, webOrigin, clientDataHash)
|
||||
onOriginRetrieved(
|
||||
AppOrigin().apply {
|
||||
addAndroidOrigin(androidOrigin)
|
||||
addWebOrigin(webOrigin)
|
||||
},
|
||||
onOriginNotRetrieved = { appIdentifierToCheck, webOrigin ->
|
||||
clientDataHash
|
||||
)
|
||||
},
|
||||
onOriginNotRetrieved = { androidOrigin, 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
|
||||
AppOrigin().apply {
|
||||
addAndroidOrigin(androidOrigin)
|
||||
addWebOrigin(webOrigin)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -129,23 +127,27 @@ class OriginManager(
|
||||
callOrigin = callingAppInfo.getOrigin(privilegedAllowlist)?.removeSuffix("/")
|
||||
val androidOrigin = AndroidOrigin(
|
||||
packageName = callingAppInfo.packageName,
|
||||
signature = callingAppInfo.signingInfo
|
||||
.getApplicationSignatures(),
|
||||
verification = Verification.NOT_VERIFIED
|
||||
fingerprint = callingAppInfo.signingInfo.getApplicationFingerprints(),
|
||||
verified = false
|
||||
)
|
||||
val webOrigin = WebOrigin.fromRelyingParty(
|
||||
relyingParty = relyingParty,
|
||||
verified = false
|
||||
)
|
||||
// Check if the webDomain is validated for the
|
||||
withContext(Dispatchers.Main) {
|
||||
if (callOrigin != null && providedClientDataHash != null) {
|
||||
Log.d(TAG, "Origin $callOrigin retrieved from callingAppInfo")
|
||||
// TODO verified callOrigin
|
||||
onOriginRetrieved(
|
||||
AndroidOrigin(
|
||||
packageName = androidOrigin.packageName,
|
||||
signature = androidOrigin.signature,
|
||||
verification = Verification.AUTOMATICALLY_VERIFIED
|
||||
fingerprint = androidOrigin.fingerprint,
|
||||
verified = true
|
||||
),
|
||||
WebOrigin.fromRelyingParty(
|
||||
relyingParty = relyingParty,
|
||||
verification = Verification.AUTOMATICALLY_VERIFIED
|
||||
WebOrigin(
|
||||
origin = webOrigin.origin,
|
||||
verified = true
|
||||
),
|
||||
callOrigin,
|
||||
providedClientDataHash
|
||||
@@ -153,10 +155,7 @@ class OriginManager(
|
||||
} else {
|
||||
onOriginNotRetrieved(
|
||||
androidOrigin,
|
||||
WebOrigin.fromRelyingParty(
|
||||
relyingParty = relyingParty,
|
||||
verification = Verification.NOT_VERIFIED
|
||||
)
|
||||
webOrigin
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import androidx.credentials.provider.PendingIntentHandler
|
||||
import androidx.credentials.provider.ProviderCreateCredentialRequest
|
||||
import androidx.credentials.provider.ProviderGetCredentialRequest
|
||||
import com.kunzisoft.asymmetric.Signature
|
||||
import com.kunzisoft.encrypt.Base64Helper.Companion.b64Encode
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.AuthenticatorAssertionResponse
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.AuthenticatorAttestationResponse
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.Cbor
|
||||
@@ -50,12 +51,10 @@ import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredential
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialCreationParameters
|
||||
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.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
|
||||
@@ -436,13 +435,11 @@ object PasskeyHelper {
|
||||
* Utility method to use a passkey and create the associated usage request parameters
|
||||
* [intent] allows to retrieve the request
|
||||
* [assetManager] has been transferred to the origin manager to manage package verification files
|
||||
* [appOrigin] retrieves the origin params stored in an entry, which may be null if not found or if the database is closed.
|
||||
* [result] is called asynchronously after the creation of PublicKeyCredentialUsageParameters, the origin associated with it may or may not be verified
|
||||
*/
|
||||
suspend fun retrievePasskeyUsageRequestParameters(
|
||||
intent: Intent,
|
||||
assetManager: AssetManager,
|
||||
appOrigin: AppOrigin,
|
||||
result: (PublicKeyCredentialUsageParameters) -> Unit
|
||||
) {
|
||||
val getCredentialRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
|
||||
@@ -460,18 +457,16 @@ object PasskeyHelper {
|
||||
assets = assetManager,
|
||||
relyingParty = requestOptions.rpId
|
||||
).getOriginAtUsage(
|
||||
appOrigin = appOrigin,
|
||||
onOriginRetrieved = { androidOrigin, webOrigin, clientDataHash ->
|
||||
onOriginRetrieved = { appOrigin, clientDataHash ->
|
||||
result.invoke(
|
||||
PublicKeyCredentialUsageParameters(
|
||||
publicKeyCredentialRequestOptions = requestOptions,
|
||||
clientDataResponse = ClientDataDefinedResponse(clientDataHash),
|
||||
androidOrigin = androidOrigin,
|
||||
webOrigin = webOrigin
|
||||
appOrigin = appOrigin
|
||||
)
|
||||
)
|
||||
},
|
||||
onOriginCreated = { androidOrigin, webOrigin ->
|
||||
onOriginCreated = { appOrigin ->
|
||||
// By default we crate an usage parameter with Android origin
|
||||
result.invoke(
|
||||
PublicKeyCredentialUsageParameters(
|
||||
@@ -479,10 +474,9 @@ object PasskeyHelper {
|
||||
clientDataResponse = ClientDataBuildResponse(
|
||||
type = ClientDataBuildResponse.Type.GET,
|
||||
challenge = requestOptions.challenge,
|
||||
origin = androidOrigin.toAndroidOrigin()
|
||||
origin = appOrigin.toAppOrigin()
|
||||
),
|
||||
androidOrigin = androidOrigin,
|
||||
webOrigin = webOrigin
|
||||
appOrigin = appOrigin
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -519,37 +513,23 @@ object PasskeyHelper {
|
||||
|
||||
/**
|
||||
* Verify that the application signature is contained in the [appOrigin]
|
||||
* or that the webDomain contains the origin
|
||||
*/
|
||||
fun getVerifiedGETClientDataResponse(
|
||||
usageParameters: PublicKeyCredentialUsageParameters,
|
||||
appOrigin: AppOrigin
|
||||
): ClientDataResponse {
|
||||
val appToCheck = usageParameters.androidOrigin
|
||||
val webToCheck = usageParameters.webOrigin
|
||||
if (appToCheck.verification == Verification.AUTOMATICALLY_VERIFIED) {
|
||||
return usageParameters.clientDataResponse
|
||||
val appToCheck = usageParameters.appOrigin
|
||||
return if (appToCheck.verified) {
|
||||
usageParameters.clientDataResponse
|
||||
} else {
|
||||
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()
|
||||
)
|
||||
}
|
||||
appOrigin.checkAppOrigin(appToCheck)?.let { origin ->
|
||||
// Origin checked by Android app signature
|
||||
return ClientDataBuildResponse(
|
||||
ClientDataBuildResponse(
|
||||
type = ClientDataBuildResponse.Type.GET,
|
||||
challenge = usageParameters.publicKeyCredentialRequestOptions.challenge,
|
||||
origin = appOrigin.firstVerifiedWebOrigin()?.toWebOrigin()
|
||||
?: appToCheck.toAndroidOrigin()
|
||||
origin = origin
|
||||
)
|
||||
} else {
|
||||
throw SecurityException("Wrong signature for ${appToCheck.packageName}")
|
||||
}
|
||||
} ?: throw SecurityException("Wrong signature for $appToCheck")
|
||||
}
|
||||
}
|
||||
}
|
||||
23
crypto/src/main/java/com/kunzisoft/encrypt/Base64Helper.kt
Normal file
23
crypto/src/main/java/com/kunzisoft/encrypt/Base64Helper.kt
Normal file
@@ -0,0 +1,23 @@
|
||||
package com.kunzisoft.encrypt
|
||||
|
||||
import android.util.Base64
|
||||
|
||||
class Base64Helper {
|
||||
|
||||
companion object {
|
||||
|
||||
fun b64Decode(encodedString: String): ByteArray {
|
||||
return Base64.decode(
|
||||
encodedString,
|
||||
Base64.NO_PADDING or Base64.NO_WRAP or Base64.URL_SAFE
|
||||
)
|
||||
}
|
||||
|
||||
fun b64Encode(data: ByteArray): String {
|
||||
return Base64.encodeToString(
|
||||
data,
|
||||
Base64.NO_PADDING or Base64.NO_WRAP or Base64.URL_SAFE
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,7 @@ object HashManager {
|
||||
return StreamCipher(cipher)
|
||||
}
|
||||
|
||||
private const val SIGNATURE_DELIMITER = "##SIG##"
|
||||
const val SIGNATURE_DELIMITER = "##SIG##"
|
||||
|
||||
/**
|
||||
* Converts a Signature object into its SHA-256 fingerprint string.
|
||||
@@ -149,7 +149,7 @@ object HashManager {
|
||||
* @param signingInfo The SigningInfo object to retrieve the strings signatures
|
||||
* @return A List of SHA-256 fingerprint strings, or null if an error occurs or no signatures are found.
|
||||
*/
|
||||
fun getAllSignatures(signingInfo: SigningInfo?): List<String>? {
|
||||
fun getAllFingerprints(signingInfo: SigningInfo?): List<String>? {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
|
||||
throw AndroidException("API level ${Build.VERSION.SDK_INT} not supported")
|
||||
@@ -181,13 +181,51 @@ object HashManager {
|
||||
/**
|
||||
* Combines a list of signature into a single string for database storage.
|
||||
*
|
||||
* @return A single string with fingerprints joined by a delimiter, or null if the input list is null or empty.
|
||||
* @return A single string with fingerprints joined by a ##SIG## delimiter,
|
||||
* or null if the input list is null or empty.
|
||||
*/
|
||||
fun SigningInfo.getApplicationSignatures(): String? {
|
||||
val fingerprints = getAllSignatures(this)
|
||||
fun SigningInfo.getApplicationFingerprints(): String? {
|
||||
val fingerprints = getAllFingerprints(this)
|
||||
if (fingerprints.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
return fingerprints.joinToString(SIGNATURE_DELIMITER)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a colon-separated hex fingerprint string into a URL-safe,
|
||||
* padding-removed Base64 string, mimicking the Python behavior:
|
||||
* base64.urlsafe_b64encode(binascii.a2b_hex(fingerprint.replace(':', ''))).decode('utf8').replace('=', '')
|
||||
*
|
||||
* Only check the first footprint if there are several delimited by ##SIG##.
|
||||
*
|
||||
* @param fingerprint The colon-separated hex fingerprint string (e.g., "91:F7:CB:...").
|
||||
* @return The Android App Origin string.
|
||||
* @throws IllegalArgumentException if the hex string (after removing colons) has an odd length
|
||||
* or contains non-hex characters.
|
||||
*/
|
||||
fun fingerprintToUrlSafeBase64(fingerprint: String): String {
|
||||
val firstFingerprint = fingerprint.split(SIGNATURE_DELIMITER).firstOrNull()?.trim()
|
||||
if (firstFingerprint.isNullOrEmpty()) {
|
||||
throw IllegalArgumentException("Invalid fingerprint $fingerprint")
|
||||
}
|
||||
val hexStringNoColons = fingerprint.replace(":", "")
|
||||
if (hexStringNoColons.length % 2 != 0) {
|
||||
throw IllegalArgumentException("Hex string must have an even number of characters: $hexStringNoColons")
|
||||
}
|
||||
if (hexStringNoColons.length != 64) {
|
||||
throw IllegalArgumentException("Expected a 64-character hex string for a SHA-256 hash, but got ${hexStringNoColons.length} characters.")
|
||||
}
|
||||
val hashBytes = ByteArray(hexStringNoColons.length / 2)
|
||||
for (i in hashBytes.indices) {
|
||||
try {
|
||||
val index = i * 2
|
||||
val byteValue = hexStringNoColons.substring(index, index + 2).toInt(16)
|
||||
hashBytes[i] = byteValue.toByte()
|
||||
} catch (e: NumberFormatException) {
|
||||
throw IllegalArgumentException("Invalid hex character in fingerprint: $hexStringNoColons", e)
|
||||
}
|
||||
}
|
||||
return Base64Helper.b64Encode(hashBytes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package com.kunzisoft.keepass.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.encrypt.HashManager.fingerprintToUrlSafeBase64
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@@ -36,44 +37,22 @@ data class AppOrigin(
|
||||
this.webOrigins.add(webOrigin)
|
||||
}
|
||||
|
||||
fun containsVerifiedAndroidOrigin(androidOrigin: AndroidOrigin): Boolean {
|
||||
return androidOrigins.any {
|
||||
val verified: Boolean
|
||||
get() = androidOrigins.any { it.verified }
|
||||
|
||||
/**
|
||||
* Verify the app origin by comparing it to the list of android origins,
|
||||
* return the first verified origin or null if none is found
|
||||
*/
|
||||
fun checkAppOrigin(appToCheck: AppOrigin): String? {
|
||||
return androidOrigins.firstOrNull { androidOrigin ->
|
||||
appToCheck.androidOrigins.any {
|
||||
it.packageName == androidOrigin.packageName
|
||||
&& it.signature == androidOrigin.signature
|
||||
&& it.verification.verified
|
||||
&& it.fingerprint == androidOrigin.fingerprint
|
||||
}
|
||||
}
|
||||
|
||||
fun getFirstAndroidOrigin(): AndroidOrigin? {
|
||||
return androidOrigins.firstOrNull()
|
||||
}
|
||||
|
||||
fun containsVerifiedWebOrigin(webOrigin: WebOrigin): Boolean {
|
||||
return this.webOrigins.any {
|
||||
it.origin == webOrigin.origin
|
||||
&& it.verification.verified
|
||||
}
|
||||
}
|
||||
|
||||
fun containsUnverifiedWebOrigin(): Boolean {
|
||||
return this.webOrigins.any {
|
||||
it.verification.verified.not()
|
||||
}
|
||||
}
|
||||
|
||||
fun firstVerifiedWebOrigin(): WebOrigin? {
|
||||
return webOrigins.first {
|
||||
it.verification.verified
|
||||
}
|
||||
}
|
||||
|
||||
fun getFirstWebOrigin(): WebOrigin? {
|
||||
return webOrigins.firstOrNull()
|
||||
}
|
||||
|
||||
fun firstUnverifiedOrigin(): WebOrigin? {
|
||||
return webOrigins.first {
|
||||
it.verification.verified.not()
|
||||
}?.let {
|
||||
AndroidOrigin(it.packageName, it.fingerprint, true)
|
||||
.toAndroidOrigin()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +65,11 @@ data class AppOrigin(
|
||||
return androidOrigins.isEmpty() && webOrigins.isEmpty()
|
||||
}
|
||||
|
||||
fun toAppOrigin(): String {
|
||||
return androidOrigins.firstOrNull()?.toAndroidOrigin()
|
||||
?: throw SecurityException("No app origin found")
|
||||
}
|
||||
|
||||
fun toName(): String? {
|
||||
return if (androidOrigins.isNotEmpty()) {
|
||||
androidOrigins.first().packageName
|
||||
@@ -95,29 +79,36 @@ data class AppOrigin(
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
val fingerprint: String?,
|
||||
val verified: Boolean = true
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
* Creates an Android App Origin string of the form "android:apk-key-hash:<base64_urlsafe_hash>"
|
||||
* from a colon-separated hex fingerprint string.
|
||||
*
|
||||
* The input fingerprint is assumed to be the SHA-256 hash of the app's signing certificate.
|
||||
*
|
||||
* @param fingerprint The colon-separated hex fingerprint string (e.g., "91:F7:CB:...").
|
||||
* @return The Android App Origin string.
|
||||
* @throws IllegalArgumentException if the hex string (after removing colons) has an odd length
|
||||
* or contains non-hex characters.
|
||||
*/
|
||||
fun toAndroidOrigin(): String {
|
||||
return "android:apk-key-hash:${packageName}"
|
||||
if (fingerprint == null) {
|
||||
throw IllegalArgumentException("Fingerprint $fingerprint cannot be null")
|
||||
}
|
||||
return "android:apk-key-hash:${fingerprintToUrlSafeBase64(fingerprint)}"
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class WebOrigin(
|
||||
val origin: String,
|
||||
val verification: Verification = Verification.AUTOMATICALLY_VERIFIED,
|
||||
val verified: Boolean = true,
|
||||
) : Parcelable {
|
||||
|
||||
fun toWebOrigin(): String {
|
||||
@@ -130,9 +121,9 @@ data class WebOrigin(
|
||||
|
||||
companion object {
|
||||
const val RELYING_PARTY_DEFAULT_PROTOCOL = "https"
|
||||
fun fromRelyingParty(relyingParty: String, verification: Verification): WebOrigin = WebOrigin(
|
||||
fun fromRelyingParty(relyingParty: String, verified: Boolean): WebOrigin = WebOrigin(
|
||||
origin ="$RELYING_PARTY_DEFAULT_PROTOCOL://$relyingParty",
|
||||
verification = verification
|
||||
verified = verified
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -138,10 +138,10 @@ object AppOriginEntryField {
|
||||
*/
|
||||
fun EntryInfo.setAppOrigin(appOrigin: AppOrigin?, customFieldsAllowed: Boolean) {
|
||||
appOrigin?.androidOrigins?.forEach { appIdentifier ->
|
||||
setApplicationId(appIdentifier.packageName, appIdentifier.signature)
|
||||
setApplicationId(appIdentifier.packageName, appIdentifier.fingerprint)
|
||||
}
|
||||
appOrigin?.webOrigins?.forEach { webOrigin ->
|
||||
if (webOrigin.verification.verified)
|
||||
if (webOrigin.verified)
|
||||
setWebDomain(webOrigin.origin, null, customFieldsAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user