Skip to content

Commit d9ef87e

Browse files
authored
[PM-15609] Move FIDO2 origin validation logic to Fido2OriginManager (#4426)
1 parent 7b3ad98 commit d9ef87e

File tree

13 files changed

+643
-462
lines changed

13 files changed

+643
-462
lines changed

app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/di/Fido2ProviderModule.kt

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
88
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service.DigitalAssetLinkService
99
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
1010
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManagerImpl
11+
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2OriginManager
12+
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2OriginManagerImpl
1113
import com.x8bit.bitwarden.data.autofill.fido2.processor.Fido2ProviderProcessor
1214
import com.x8bit.bitwarden.data.autofill.fido2.processor.Fido2ProviderProcessorImpl
1315
import com.x8bit.bitwarden.data.platform.manager.AssetManager
@@ -58,17 +60,26 @@ object Fido2ProviderModule {
5860
@Provides
5961
@Singleton
6062
fun provideFido2CredentialManager(
61-
assetManager: AssetManager,
62-
digitalAssetLinkService: DigitalAssetLinkService,
6363
vaultSdkSource: VaultSdkSource,
6464
fido2CredentialStore: Fido2CredentialStore,
65+
fido2OriginManager: Fido2OriginManager,
6566
json: Json,
6667
): Fido2CredentialManager =
6768
Fido2CredentialManagerImpl(
68-
assetManager = assetManager,
69-
digitalAssetLinkService = digitalAssetLinkService,
7069
vaultSdkSource = vaultSdkSource,
7170
fido2CredentialStore = fido2CredentialStore,
71+
fido2OriginManager = fido2OriginManager,
7272
json = json,
7373
)
74+
75+
@Provides
76+
@Singleton
77+
fun provideFido2OriginManager(
78+
assetManager: AssetManager,
79+
digitalAssetLinkService: DigitalAssetLinkService,
80+
): Fido2OriginManager =
81+
Fido2OriginManagerImpl(
82+
assetManager = assetManager,
83+
digitalAssetLinkService = digitalAssetLinkService,
84+
)
7485
}

app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManager.kt

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
package com.x8bit.bitwarden.data.autofill.fido2.manager
22

3-
import androidx.credentials.provider.CallingAppInfo
43
import com.bitwarden.vault.CipherView
54
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialRequest
65
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionRequest
76
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
87
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
9-
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
108
import com.x8bit.bitwarden.data.autofill.fido2.model.PasskeyAssertionOptions
119
import com.x8bit.bitwarden.data.autofill.fido2.model.PasskeyAttestationOptions
1210

@@ -26,14 +24,6 @@ interface Fido2CredentialManager {
2624
*/
2725
var authenticationAttempts: Int
2826

29-
/**
30-
* Attempt to validate the RP and origin of the provided [callingAppInfo] and [relyingPartyId].
31-
*/
32-
suspend fun validateOrigin(
33-
callingAppInfo: CallingAppInfo,
34-
relyingPartyId: String,
35-
): Fido2ValidateOriginResult
36-
3727
/**
3828
* Attempt to extract FIDO 2 passkey attestation options from the system [requestJson], or null.
3929
*/

app/src/main/java/com/x8bit/bitwarden/data/autofill/fido2/manager/Fido2CredentialManagerImpl.kt

Lines changed: 8 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,17 @@ import com.bitwarden.fido.Origin
66
import com.bitwarden.fido.UnverifiedAssetLink
77
import com.bitwarden.sdk.Fido2CredentialStore
88
import com.bitwarden.vault.CipherView
9-
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson
10-
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service.DigitalAssetLinkService
119
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialRequest
1210
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionRequest
1311
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
1412
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
1513
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
1614
import com.x8bit.bitwarden.data.autofill.fido2.model.PasskeyAssertionOptions
1715
import com.x8bit.bitwarden.data.autofill.fido2.model.PasskeyAttestationOptions
18-
import com.x8bit.bitwarden.data.platform.manager.AssetManager
1916
import com.x8bit.bitwarden.data.platform.util.decodeFromStringOrNull
2017
import com.x8bit.bitwarden.data.platform.util.getAppOrigin
2118
import com.x8bit.bitwarden.data.platform.util.getAppSigningSignatureFingerprint
2219
import com.x8bit.bitwarden.data.platform.util.getSignatureFingerprintAsHexString
23-
import com.x8bit.bitwarden.data.platform.util.validatePrivilegedApp
2420
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
2521
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.AuthenticateFido2CredentialRequest
2622
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.RegisterFido2CredentialRequest
@@ -31,18 +27,14 @@ import kotlinx.serialization.SerializationException
3127
import kotlinx.serialization.encodeToString
3228
import kotlinx.serialization.json.Json
3329

34-
private const val GOOGLE_ALLOW_LIST_FILE_NAME = "fido2_privileged_google.json"
35-
private const val COMMUNITY_ALLOW_LIST_FILE_NAME = "fido2_privileged_community.json"
36-
3730
/**
3831
* Primary implementation of [Fido2CredentialManager].
3932
*/
4033
@Suppress("TooManyFunctions")
4134
class Fido2CredentialManagerImpl(
42-
private val assetManager: AssetManager,
43-
private val digitalAssetLinkService: DigitalAssetLinkService,
4435
private val vaultSdkSource: VaultSdkSource,
4536
private val fido2CredentialStore: Fido2CredentialStore,
37+
private val fido2OriginManager: Fido2OriginManager,
4638
private val json: Json,
4739
) : Fido2CredentialManager,
4840
Fido2CredentialStore by fido2CredentialStore {
@@ -108,16 +100,14 @@ class Fido2CredentialManagerImpl(
108100
)
109101
}
110102

111-
override suspend fun validateOrigin(
103+
private suspend fun validateOrigin(
112104
callingAppInfo: CallingAppInfo,
113105
relyingPartyId: String,
114-
): Fido2ValidateOriginResult {
115-
return if (callingAppInfo.isOriginPopulated()) {
116-
validatePrivilegedAppOrigin(callingAppInfo)
117-
} else {
118-
validateCallingApplicationAssetLinks(callingAppInfo, relyingPartyId)
119-
}
120-
}
106+
): Fido2ValidateOriginResult = fido2OriginManager
107+
.validateOrigin(
108+
callingAppInfo = callingAppInfo,
109+
relyingPartyId = relyingPartyId,
110+
)
121111

122112
override fun getPasskeyAttestationOptionsOrNull(
123113
requestJson: String,
@@ -168,7 +158,7 @@ class Fido2CredentialManagerImpl(
168158
Fido2CredentialAssertionResult.Error
169159
}
170160

171-
Fido2ValidateOriginResult.Success -> {
161+
is Fido2ValidateOriginResult.Success -> {
172162
vaultSdkSource
173163
.authenticateFido2Credential(
174164
request = AuthenticateFido2CredentialRequest(
@@ -200,127 +190,6 @@ class Fido2CredentialManagerImpl(
200190
}
201191
}
202192

203-
private suspend fun validateCallingApplicationAssetLinks(
204-
callingAppInfo: CallingAppInfo,
205-
relyingPartyId: String,
206-
): Fido2ValidateOriginResult {
207-
return digitalAssetLinkService
208-
.getDigitalAssetLinkForRp(relyingParty = relyingPartyId)
209-
.onFailure {
210-
return Fido2ValidateOriginResult.Error.AssetLinkNotFound
211-
}
212-
.map { statements ->
213-
statements
214-
.filterMatchingAppStatementsOrNull(
215-
rpPackageName = callingAppInfo.packageName,
216-
)
217-
?: return Fido2ValidateOriginResult.Error.ApplicationNotFound
218-
}
219-
.map { matchingStatements ->
220-
callingAppInfo
221-
.getSignatureFingerprintAsHexString()
222-
?.let { certificateFingerprint ->
223-
matchingStatements
224-
.filterMatchingAppSignaturesOrNull(
225-
signature = certificateFingerprint,
226-
)
227-
}
228-
?: return Fido2ValidateOriginResult.Error.ApplicationNotVerified
229-
}
230-
.fold(
231-
onSuccess = {
232-
Fido2ValidateOriginResult.Success
233-
},
234-
onFailure = {
235-
Fido2ValidateOriginResult.Error.Unknown
236-
},
237-
)
238-
}
239-
240-
private suspend fun validatePrivilegedAppOrigin(
241-
callingAppInfo: CallingAppInfo,
242-
): Fido2ValidateOriginResult {
243-
val googleAllowListResult =
244-
validatePrivilegedAppSignatureWithGoogleList(callingAppInfo)
245-
return when (googleAllowListResult) {
246-
is Fido2ValidateOriginResult.Success -> {
247-
// Application was found and successfully validated against the Google allow list so
248-
// we can return the result as the final validation result.
249-
googleAllowListResult
250-
}
251-
252-
is Fido2ValidateOriginResult.Error -> {
253-
// Check the community allow list if the Google allow list failed, and return the
254-
// result as the final validation result.
255-
validatePrivilegedAppSignatureWithCommunityList(callingAppInfo)
256-
}
257-
}
258-
}
259-
260-
private suspend fun validatePrivilegedAppSignatureWithGoogleList(
261-
callingAppInfo: CallingAppInfo,
262-
): Fido2ValidateOriginResult =
263-
validatePrivilegedAppSignatureWithAllowList(
264-
callingAppInfo = callingAppInfo,
265-
fileName = GOOGLE_ALLOW_LIST_FILE_NAME,
266-
)
267-
268-
private suspend fun validatePrivilegedAppSignatureWithCommunityList(
269-
callingAppInfo: CallingAppInfo,
270-
): Fido2ValidateOriginResult =
271-
validatePrivilegedAppSignatureWithAllowList(
272-
callingAppInfo = callingAppInfo,
273-
fileName = COMMUNITY_ALLOW_LIST_FILE_NAME,
274-
)
275-
276-
private suspend fun validatePrivilegedAppSignatureWithAllowList(
277-
callingAppInfo: CallingAppInfo,
278-
fileName: String,
279-
): Fido2ValidateOriginResult =
280-
assetManager
281-
.readAsset(fileName)
282-
.map { allowList ->
283-
callingAppInfo.validatePrivilegedApp(
284-
allowList = allowList,
285-
)
286-
}
287-
.fold(
288-
onSuccess = { it },
289-
onFailure = { Fido2ValidateOriginResult.Error.Unknown },
290-
)
291-
292-
/**
293-
* Returns statements targeting the calling Android application, or null.
294-
*/
295-
private fun List<DigitalAssetLinkResponseJson>.filterMatchingAppStatementsOrNull(
296-
rpPackageName: String,
297-
): List<DigitalAssetLinkResponseJson>? =
298-
filter { statement ->
299-
val target = statement.target
300-
target.namespace == "android_app" &&
301-
target.packageName == rpPackageName &&
302-
statement.relation.containsAll(
303-
listOf(
304-
"delegate_permission/common.get_login_creds",
305-
"delegate_permission/common.handle_all_urls",
306-
),
307-
)
308-
}
309-
.takeUnless { it.isEmpty() }
310-
311-
/**
312-
* Returns statements that match the given [signature], or null.
313-
*/
314-
private fun List<DigitalAssetLinkResponseJson>.filterMatchingAppSignaturesOrNull(
315-
signature: String,
316-
): List<DigitalAssetLinkResponseJson>? =
317-
filter { statement ->
318-
statement.target.sha256CertFingerprints
319-
?.contains(signature)
320-
?: false
321-
}
322-
.takeUnless { it.isEmpty() }
323-
324193
override fun hasAuthenticationAttemptsRemaining(): Boolean =
325194
authenticationAttempts < MAX_AUTHENTICATION_ATTEMPTS
326195

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.x8bit.bitwarden.data.autofill.fido2.manager
2+
3+
import androidx.credentials.provider.CallingAppInfo
4+
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
5+
6+
/**
7+
* Responsible for managing FIDO2 origin validation.
8+
*/
9+
interface Fido2OriginManager {
10+
11+
/**
12+
* Validates the origin of a calling app.
13+
*
14+
* @param callingAppInfo The calling app info.
15+
* @param relyingPartyId The relying party ID.
16+
*
17+
* @return The result of the validation.
18+
*/
19+
suspend fun validateOrigin(
20+
callingAppInfo: CallingAppInfo,
21+
relyingPartyId: String,
22+
): Fido2ValidateOriginResult
23+
24+
/**
25+
* Returns the privileged app origin, or null if the calling app is not allowed.
26+
*
27+
* @param callingAppInfo The calling app info.
28+
*
29+
* @return The privileged app origin, or null.
30+
*/
31+
suspend fun getPrivilegedAppOriginOrNull(callingAppInfo: CallingAppInfo): String?
32+
}

0 commit comments

Comments
 (0)