@@ -6,6 +6,7 @@ import com.bitwarden.fido.Origin
6
6
import com.bitwarden.fido.UnverifiedAssetLink
7
7
import com.bitwarden.sdk.Fido2CredentialStore
8
8
import com.bitwarden.vault.CipherView
9
+ import com.x8bit.bitwarden.R
9
10
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialRequest
10
11
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionRequest
11
12
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
@@ -22,9 +23,11 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.AuthenticateFido2Cred
22
23
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.RegisterFido2CredentialRequest
23
24
import com.x8bit.bitwarden.data.vault.datasource.sdk.util.toAndroidAttestationResponse
24
25
import com.x8bit.bitwarden.data.vault.datasource.sdk.util.toAndroidFido2PublicKeyCredential
25
- import com.x8bit.bitwarden.ui.platform.base.util.toHostOrPathOrNull
26
+ import com.x8bit.bitwarden.ui.platform.base.util.asText
27
+ import com.x8bit.bitwarden.ui.platform.base.util.prefixHttpsIfNecessaryOrNull
26
28
import kotlinx.serialization.SerializationException
27
29
import kotlinx.serialization.json.Json
30
+ import timber.log.Timber
28
31
29
32
/* *
30
33
* Primary implementation of [Fido2CredentialManager].
@@ -47,41 +50,45 @@ class Fido2CredentialManagerImpl(
47
50
fido2CreateCredentialRequest : Fido2CreateCredentialRequest ,
48
51
selectedCipherView : CipherView ,
49
52
): Fido2RegisterCredentialResult {
50
- val clientData = if (fido2CreateCredentialRequest.callingAppInfo.isOriginPopulated()) {
51
- fido2CreateCredentialRequest
52
- .callingAppInfo
53
+ val callingAppInfo = fido2CreateCredentialRequest.callingAppInfo
54
+ val clientData = if (fido2CreateCredentialRequest.origin.isNullOrEmpty()) {
55
+ ClientData .DefaultWithExtraData (androidPackageName = callingAppInfo.packageName)
56
+ } else {
57
+ callingAppInfo
53
58
.getAppSigningSignatureFingerprint()
54
59
?.let { ClientData .DefaultWithCustomHash (hash = it) }
55
- ? : return Fido2RegisterCredentialResult .Error
56
- } else {
57
- ClientData .DefaultWithExtraData (
58
- androidPackageName = fido2CreateCredentialRequest
59
- .callingAppInfo
60
- .packageName,
60
+ ? : return Fido2RegisterCredentialResult .Error (
61
+ R .string.passkey_operation_failed_because_app_is_signed_incorrectly.asText(),
62
+ )
63
+ }
64
+ val sdkOrigin = if (fido2CreateCredentialRequest.origin.isNullOrEmpty()) {
65
+ val host = getOriginUrlFromAttestationOptionsOrNull(
66
+ requestJson = fido2CreateCredentialRequest.requestJson,
67
+ )
68
+ ? : return Fido2RegisterCredentialResult .Error (
69
+ R .string.passkey_operation_failed_because_host_url_is_not_present_in_request
70
+ .asText(),
71
+ )
72
+ Origin .Android (
73
+ UnverifiedAssetLink (
74
+ packageName = callingAppInfo.packageName,
75
+ sha256CertFingerprint = callingAppInfo.getSignatureFingerprintAsHexString()
76
+ ? : return Fido2RegisterCredentialResult .Error (
77
+ R .string.passkey_operation_failed_because_app_signature_is_invalid
78
+ .asText(),
79
+ ),
80
+ host = host,
81
+ assetLinkUrl = host,
82
+ ),
61
83
)
84
+ } else {
85
+ Origin .Web (fido2CreateCredentialRequest.origin)
62
86
}
63
- val assetLinkUrl = fido2CreateCredentialRequest
64
- .origin
65
- ? : getOriginUrlFromAttestationOptionsOrNull(fido2CreateCredentialRequest.requestJson)
66
- ? : return Fido2RegisterCredentialResult .Error
67
-
68
- val origin = Origin .Android (
69
- UnverifiedAssetLink (
70
- packageName = fido2CreateCredentialRequest.packageName,
71
- sha256CertFingerprint = fido2CreateCredentialRequest
72
- .callingAppInfo
73
- .getSignatureFingerprintAsHexString()
74
- ? : return Fido2RegisterCredentialResult .Error ,
75
- host = assetLinkUrl.toHostOrPathOrNull()
76
- ? : return Fido2RegisterCredentialResult .Error ,
77
- assetLinkUrl = assetLinkUrl,
78
- ),
79
- )
80
87
return vaultSdkSource
81
88
.registerFido2Credential(
82
89
request = RegisterFido2CredentialRequest (
83
90
userId = userId,
84
- origin = origin ,
91
+ origin = sdkOrigin ,
85
92
requestJson = """ {"publicKey": ${fido2CreateCredentialRequest.requestJson} }""" ,
86
93
clientData = clientData,
87
94
selectedCipherView = selectedCipherView,
@@ -95,7 +102,11 @@ class Fido2CredentialManagerImpl(
95
102
.mapCatching { json.encodeToString(it) }
96
103
.fold(
97
104
onSuccess = { Fido2RegisterCredentialResult .Success (it) },
98
- onFailure = { Fido2RegisterCredentialResult .Error },
105
+ onFailure = {
106
+ Fido2RegisterCredentialResult .Error (
107
+ R .string.passkey_registration_failed_due_to_an_internal_error.asText(),
108
+ )
109
+ },
99
110
)
100
111
}
101
112
@@ -114,8 +125,10 @@ class Fido2CredentialManagerImpl(
114
125
try {
115
126
json.decodeFromString<PasskeyAttestationOptions >(requestJson)
116
127
} catch (e: SerializationException ) {
128
+ Timber .e(e, " Failed to decode passkey attestation options." )
117
129
null
118
130
} catch (e: IllegalArgumentException ) {
131
+ Timber .e(e, " Failed to decode passkey attestation options." )
119
132
null
120
133
}
121
134
@@ -125,11 +138,14 @@ class Fido2CredentialManagerImpl(
125
138
try {
126
139
json.decodeFromString<PasskeyAssertionOptions >(requestJson)
127
140
} catch (e: SerializationException ) {
141
+ Timber .e(e, " Failed to decode passkey assertion options: $e " )
128
142
null
129
143
} catch (e: IllegalArgumentException ) {
144
+ Timber .e(e, " Failed to decode passkey assertion options: $e " )
130
145
null
131
146
}
132
147
148
+ @Suppress(" LongMethod" )
133
149
override suspend fun authenticateFido2Credential (
134
150
userId : String ,
135
151
request : Fido2CredentialAssertionRequest ,
@@ -139,39 +155,52 @@ class Fido2CredentialManagerImpl(
139
155
val clientData = request.clientDataHash
140
156
?.let { ClientData .DefaultWithCustomHash (hash = it) }
141
157
? : ClientData .DefaultWithExtraData (androidPackageName = callingAppInfo.getAppOrigin())
142
- val origin = callingAppInfo.origin
143
- ? : getOriginUrlFromAssertionOptionsOrNull(request.requestJson)
144
- ? : return Fido2CredentialAssertionResult .Error
145
158
val relyingPartyId = json
146
159
.decodeFromStringOrNull<PasskeyAssertionOptions >(request.requestJson)
147
160
?.relyingPartyId
148
- ? : return Fido2CredentialAssertionResult .Error
161
+ ? : return Fido2CredentialAssertionResult .Error (
162
+ R .string.passkey_operation_failed_because_relying_party_cannot_be_identified
163
+ .asText(),
164
+ )
149
165
150
166
val validateOriginResult = validateOrigin(
151
167
callingAppInfo = callingAppInfo,
152
168
relyingPartyId = relyingPartyId,
153
169
)
154
170
171
+ val sdkOrigin = if (! request.origin.isNullOrEmpty()) {
172
+ Origin .Web (request.origin)
173
+ } else {
174
+ val hostUrl = getOriginUrlFromAssertionOptionsOrNull(request.requestJson)
175
+ ? : return Fido2CredentialAssertionResult .Error (
176
+ R .string.passkey_operation_failed_because_host_url_is_not_present_in_request
177
+ .asText(),
178
+ )
179
+ Origin .Android (
180
+ UnverifiedAssetLink (
181
+ packageName = callingAppInfo.packageName,
182
+ sha256CertFingerprint = callingAppInfo.getSignatureFingerprintAsHexString()
183
+ ? : return Fido2CredentialAssertionResult .Error (
184
+ R .string.passkey_operation_failed_because_app_signature_is_invalid
185
+ .asText(),
186
+ ),
187
+ host = hostUrl,
188
+ assetLinkUrl = hostUrl,
189
+ ),
190
+ )
191
+ }
192
+
155
193
return when (validateOriginResult) {
156
194
is Fido2ValidateOriginResult .Error -> {
157
- Fido2CredentialAssertionResult .Error
195
+ Fido2CredentialAssertionResult .Error (validateOriginResult.messageResId.asText())
158
196
}
159
197
160
198
is Fido2ValidateOriginResult .Success -> {
161
199
vaultSdkSource
162
200
.authenticateFido2Credential(
163
201
request = AuthenticateFido2CredentialRequest (
164
202
userId = userId,
165
- origin = Origin .Android (
166
- UnverifiedAssetLink (
167
- callingAppInfo.packageName,
168
- callingAppInfo.getSignatureFingerprintAsHexString()
169
- ? : return Fido2CredentialAssertionResult .Error ,
170
- origin.toHostOrPathOrNull()
171
- ? : return Fido2CredentialAssertionResult .Error ,
172
- origin,
173
- ),
174
- ),
203
+ origin = sdkOrigin,
175
204
requestJson = """ {"publicKey": ${request.requestJson} }""" ,
176
205
clientData = clientData,
177
206
selectedCipherView = selectedCipherView,
@@ -183,7 +212,13 @@ class Fido2CredentialManagerImpl(
183
212
.mapCatching { json.encodeToString(it) }
184
213
.fold(
185
214
onSuccess = { Fido2CredentialAssertionResult .Success (it) },
186
- onFailure = { Fido2CredentialAssertionResult .Error },
215
+ onFailure = {
216
+ Timber .e(it, " Failed to authenticate FIDO2 credential." )
217
+ Fido2CredentialAssertionResult .Error (
218
+ R .string.passkey_authentication_failed_due_to_an_internal_error
219
+ .asText(),
220
+ )
221
+ },
187
222
)
188
223
}
189
224
}
@@ -195,13 +230,13 @@ class Fido2CredentialManagerImpl(
195
230
private fun getOriginUrlFromAssertionOptionsOrNull (requestJson : String ) =
196
231
getPasskeyAssertionOptionsOrNull(requestJson)
197
232
?.relyingPartyId
198
- ?.let { " $HTTPS$it " }
233
+ ?.prefixHttpsIfNecessaryOrNull()
199
234
200
235
private fun getOriginUrlFromAttestationOptionsOrNull (requestJson : String ) =
201
236
getPasskeyAttestationOptionsOrNull(requestJson)
202
237
?.relyingParty
203
238
?.id
204
- ?.let { " $HTTPS$it " }
239
+ ?.prefixHttpsIfNecessaryOrNull()
205
240
}
206
241
207
242
private const val MAX_AUTHENTICATION_ATTEMPTS = 5
0 commit comments