@@ -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,8 @@ 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
26
27
import kotlinx.serialization.SerializationException
27
- import kotlinx.serialization.encodeToString
28
28
import kotlinx.serialization.json.Json
29
29
30
30
/* *
@@ -48,41 +48,45 @@ class Fido2CredentialManagerImpl(
48
48
fido2CreateCredentialRequest : Fido2CreateCredentialRequest ,
49
49
selectedCipherView : CipherView ,
50
50
): Fido2RegisterCredentialResult {
51
- val clientData = if (fido2CreateCredentialRequest.callingAppInfo.isOriginPopulated()) {
52
- fido2CreateCredentialRequest
53
- .callingAppInfo
51
+ val callingAppInfo = fido2CreateCredentialRequest.callingAppInfo
52
+ val clientData = if (fido2CreateCredentialRequest.origin.isNullOrEmpty()) {
53
+ ClientData .DefaultWithExtraData (androidPackageName = callingAppInfo.packageName)
54
+ } else {
55
+ callingAppInfo
54
56
.getAppSigningSignatureFingerprint()
55
57
?.let { ClientData .DefaultWithCustomHash (hash = it) }
56
- ? : return Fido2RegisterCredentialResult .Error
57
- } else {
58
- ClientData .DefaultWithExtraData (
59
- androidPackageName = fido2CreateCredentialRequest
60
- .callingAppInfo
61
- .packageName,
58
+ ? : return Fido2RegisterCredentialResult .Error (
59
+ R .string.passkey_operation_failed_because_app_is_signed_incorrectly.asText(),
60
+ )
61
+ }
62
+ val sdkOrigin = if (fido2CreateCredentialRequest.origin.isNullOrEmpty()) {
63
+ val host = getOriginUrlFromAttestationOptionsOrNull(
64
+ requestJson = fido2CreateCredentialRequest.requestJson,
65
+ )
66
+ ? : return Fido2RegisterCredentialResult .Error (
67
+ R .string.passkey_operation_failed_because_host_url_is_not_present_in_request
68
+ .asText(),
69
+ )
70
+ Origin .Android (
71
+ UnverifiedAssetLink (
72
+ packageName = callingAppInfo.packageName,
73
+ sha256CertFingerprint = callingAppInfo.getSignatureFingerprintAsHexString()
74
+ ? : return Fido2RegisterCredentialResult .Error (
75
+ R .string.passkey_operation_failed_because_app_signature_is_invalid
76
+ .asText(),
77
+ ),
78
+ host = host,
79
+ assetLinkUrl = host,
80
+ ),
62
81
)
82
+ } else {
83
+ Origin .Web (fido2CreateCredentialRequest.origin)
63
84
}
64
- val assetLinkUrl = fido2CreateCredentialRequest
65
- .origin
66
- ? : getOriginUrlFromAttestationOptionsOrNull(fido2CreateCredentialRequest.requestJson)
67
- ? : return Fido2RegisterCredentialResult .Error
68
-
69
- val origin = Origin .Android (
70
- UnverifiedAssetLink (
71
- packageName = fido2CreateCredentialRequest.packageName,
72
- sha256CertFingerprint = fido2CreateCredentialRequest
73
- .callingAppInfo
74
- .getSignatureFingerprintAsHexString()
75
- ? : return Fido2RegisterCredentialResult .Error ,
76
- host = assetLinkUrl.toHostOrPathOrNull()
77
- ? : return Fido2RegisterCredentialResult .Error ,
78
- assetLinkUrl = assetLinkUrl,
79
- ),
80
- )
81
85
return vaultSdkSource
82
86
.registerFido2Credential(
83
87
request = RegisterFido2CredentialRequest (
84
88
userId = userId,
85
- origin = origin ,
89
+ origin = sdkOrigin ,
86
90
requestJson = """ {"publicKey": ${fido2CreateCredentialRequest.requestJson} }""" ,
87
91
clientData = clientData,
88
92
selectedCipherView = selectedCipherView,
@@ -96,7 +100,11 @@ class Fido2CredentialManagerImpl(
96
100
.mapCatching { json.encodeToString(it) }
97
101
.fold(
98
102
onSuccess = { Fido2RegisterCredentialResult .Success (it) },
99
- onFailure = { Fido2RegisterCredentialResult .Error },
103
+ onFailure = {
104
+ Fido2RegisterCredentialResult .Error (
105
+ R .string.passkey_registration_failed_due_to_an_internal_error.asText(),
106
+ )
107
+ },
100
108
)
101
109
}
102
110
@@ -114,9 +122,9 @@ class Fido2CredentialManagerImpl(
114
122
): PasskeyAttestationOptions ? =
115
123
try {
116
124
json.decodeFromString<PasskeyAttestationOptions >(requestJson)
117
- } catch (e : SerializationException ) {
125
+ } catch (_ : SerializationException ) {
118
126
null
119
- } catch (e : IllegalArgumentException ) {
127
+ } catch (_ : IllegalArgumentException ) {
120
128
null
121
129
}
122
130
@@ -125,12 +133,13 @@ class Fido2CredentialManagerImpl(
125
133
): PasskeyAssertionOptions ? =
126
134
try {
127
135
json.decodeFromString<PasskeyAssertionOptions >(requestJson)
128
- } catch (e : SerializationException ) {
136
+ } catch (_ : SerializationException ) {
129
137
null
130
- } catch (e : IllegalArgumentException ) {
138
+ } catch (_ : IllegalArgumentException ) {
131
139
null
132
140
}
133
141
142
+ @Suppress(" LongMethod" )
134
143
override suspend fun authenticateFido2Credential (
135
144
userId : String ,
136
145
request : Fido2CredentialAssertionRequest ,
@@ -140,39 +149,52 @@ class Fido2CredentialManagerImpl(
140
149
val clientData = request.clientDataHash
141
150
?.let { ClientData .DefaultWithCustomHash (hash = it) }
142
151
? : ClientData .DefaultWithExtraData (androidPackageName = callingAppInfo.getAppOrigin())
143
- val origin = callingAppInfo.origin
144
- ? : getOriginUrlFromAssertionOptionsOrNull(request.requestJson)
145
- ? : return Fido2CredentialAssertionResult .Error
146
152
val relyingPartyId = json
147
153
.decodeFromStringOrNull<PasskeyAssertionOptions >(request.requestJson)
148
154
?.relyingPartyId
149
- ? : return Fido2CredentialAssertionResult .Error
155
+ ? : return Fido2CredentialAssertionResult .Error (
156
+ R .string.passkey_operation_failed_because_relying_party_cannot_be_identified
157
+ .asText(),
158
+ )
150
159
151
160
val validateOriginResult = validateOrigin(
152
161
callingAppInfo = callingAppInfo,
153
162
relyingPartyId = relyingPartyId,
154
163
)
155
164
165
+ val sdkOrigin = if (! request.origin.isNullOrEmpty()) {
166
+ Origin .Web (request.origin)
167
+ } else {
168
+ val hostUrl = getOriginUrlFromAssertionOptionsOrNull(request.requestJson)
169
+ ? : return Fido2CredentialAssertionResult .Error (
170
+ R .string.passkey_operation_failed_because_host_url_is_not_present_in_request
171
+ .asText(),
172
+ )
173
+ Origin .Android (
174
+ UnverifiedAssetLink (
175
+ packageName = callingAppInfo.packageName,
176
+ sha256CertFingerprint = callingAppInfo.getSignatureFingerprintAsHexString()
177
+ ? : return Fido2CredentialAssertionResult .Error (
178
+ R .string.passkey_operation_failed_because_app_signature_is_invalid
179
+ .asText(),
180
+ ),
181
+ host = hostUrl,
182
+ assetLinkUrl = hostUrl,
183
+ ),
184
+ )
185
+ }
186
+
156
187
return when (validateOriginResult) {
157
188
is Fido2ValidateOriginResult .Error -> {
158
- Fido2CredentialAssertionResult .Error
189
+ Fido2CredentialAssertionResult .Error (validateOriginResult.messageResId.asText())
159
190
}
160
191
161
192
is Fido2ValidateOriginResult .Success -> {
162
193
vaultSdkSource
163
194
.authenticateFido2Credential(
164
195
request = AuthenticateFido2CredentialRequest (
165
196
userId = userId,
166
- origin = Origin .Android (
167
- UnverifiedAssetLink (
168
- callingAppInfo.packageName,
169
- callingAppInfo.getSignatureFingerprintAsHexString()
170
- ? : return Fido2CredentialAssertionResult .Error ,
171
- origin.toHostOrPathOrNull()
172
- ? : return Fido2CredentialAssertionResult .Error ,
173
- origin,
174
- ),
175
- ),
197
+ origin = sdkOrigin,
176
198
requestJson = """ {"publicKey": ${request.requestJson} }""" ,
177
199
clientData = clientData,
178
200
selectedCipherView = selectedCipherView,
@@ -184,7 +206,12 @@ class Fido2CredentialManagerImpl(
184
206
.mapCatching { json.encodeToString(it) }
185
207
.fold(
186
208
onSuccess = { Fido2CredentialAssertionResult .Success (it) },
187
- onFailure = { Fido2CredentialAssertionResult .Error },
209
+ onFailure = {
210
+ Fido2CredentialAssertionResult .Error (
211
+ R .string.passkey_authentication_failed_due_to_an_internal_error
212
+ .asText(),
213
+ )
214
+ },
188
215
)
189
216
}
190
217
}
@@ -196,13 +223,20 @@ class Fido2CredentialManagerImpl(
196
223
private fun getOriginUrlFromAssertionOptionsOrNull (requestJson : String ) =
197
224
getPasskeyAssertionOptionsOrNull(requestJson)
198
225
?.relyingPartyId
199
- ?.let { " $HTTPS$it " }
226
+ ?.prefixWithHttpsIfNecessary()
200
227
201
228
private fun getOriginUrlFromAttestationOptionsOrNull (requestJson : String ) =
202
229
getPasskeyAttestationOptionsOrNull(requestJson)
203
230
?.relyingParty
204
231
?.id
205
- ?.let { " $HTTPS$it " }
232
+ ?.prefixWithHttpsIfNecessary()
233
+
234
+ private fun String.prefixWithHttpsIfNecessary (): String =
235
+ if (! this .startsWith(HTTPS )) {
236
+ " $HTTPS$this "
237
+ } else {
238
+ this
239
+ }
206
240
}
207
241
208
242
private const val MAX_AUTHENTICATION_ATTEMPTS = 5
0 commit comments