diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 4ccc39f296..8b2295d3b1 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -517,6 +517,13 @@ + + () @@ -62,28 +75,59 @@ class IdentitySignInServiceImpl(private val mContext: Context, private val clien override fun beginSignIn(callback: IBeginSignInCallback, request: BeginSignInRequest) { Log.d(TAG, "method 'beginSignIn' called") Log.d(TAG, "request-> $request") - if (request.googleIdTokenRequestOptions.serverClientId.isNullOrEmpty()) { - Log.d(TAG, "serverClientId is empty, return CANCELED ") - callback.onResult(Status.CANCELED, null) - return - } - val bundle = Bundle().apply { - val options = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) - .requestIdToken(request.googleIdTokenRequestOptions.serverClientId).build() - putByteArray(BEGIN_SIGN_IN_REQUEST, SafeParcelableSerializer.serializeToBytes(request)) - putByteArray(GOOGLE_SIGN_IN_OPTIONS, SafeParcelableSerializer.serializeToBytes(options)) - putString(CLIENT_PACKAGE_NAME, clientPackageName) - requestMap[request.sessionId] = options + if (request.googleIdTokenRequestOptions.isSupported) { + if (request.googleIdTokenRequestOptions.serverClientId.isNullOrEmpty()) { + Log.d(TAG, "serverClientId is empty, return CANCELED ") + callback.onResult(Status.CANCELED, null) + return + } + val bundle = Bundle().apply { + val options = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) + .requestIdToken(request.googleIdTokenRequestOptions.serverClientId).build() + putByteArray(BEGIN_SIGN_IN_REQUEST, SafeParcelableSerializer.serializeToBytes(request)) + putByteArray(GOOGLE_SIGN_IN_OPTIONS, SafeParcelableSerializer.serializeToBytes(options)) + putString(CLIENT_PACKAGE_NAME, clientPackageName) + requestMap[request.sessionId] = options + } + callback.onResult(Status.SUCCESS, BeginSignInResult(performGooogleSignIn(bundle))) + } else if (request.passkeyJsonRequestOptions.isSupported) { + fun JSONObject.getArrayOrNull(key: String) = if (has(key)) getJSONArray(key) else null + fun JSONArray.map(fn: (JSONObject) -> T): List = (0 until length()).map { fn(getJSONObject(it)) } + fun JSONArray.map(fn: (String) -> T): List = (0 until length()).map { fn(getString(it)) } + val json = JSONObject(request.passkeyJsonRequestOptions.requestJson) + val options = PublicKeyCredentialRequestOptions.Builder() + .setAllowList(json.getArrayOrNull("allowCredentials")?.let { allowCredentials -> allowCredentials.map { credential: JSONObject -> + PublicKeyCredentialDescriptor( + credential.getString("type"), + Base64.decode(credential.getString("id"), Base64.URL_SAFE), + credential.getArrayOrNull("transports")?.let { transports -> transports.map { transportString: String -> + Transport.fromString(transportString) + } }.orEmpty() + ) + } }) + .setChallenge(Base64.decode(json.getString("challenge"), Base64.URL_SAFE)) + .setRequireUserVerification(json.optString("userVerification").takeIf { it.isNotBlank() }?.let { UserVerificationRequirement.fromString(it) }) + .setRpId(json.getString("rpId")) + .setTimeoutSeconds(json.optDouble("timeout", -1.0).takeIf { it > 0 }) + .build() + val bundle = bundleOf( + KEY_SERVICE to GmsService.IDENTITY_SIGN_IN.SERVICE_ID, + KEY_SOURCE to "app", + KEY_TYPE to "sign", + KEY_OPTIONS to options.serializeToBytes() + ) + callback.onResult(Status.SUCCESS, BeginSignInResult(performFidoSignIn(bundle))) + } else { + callback.onResult(Status.INTERNAL_ERROR, null) } - callback.onResult(Status.SUCCESS, BeginSignInResult(performSignIn(bundle))) } override fun signOut(callback: IStatusCallback, requestTag: String) { Log.d(TAG, "method signOut called, requestTag=$requestTag") if (requestMap.containsKey(requestTag)) { - val accounts = AccountManager.get(mContext).getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE) + val accounts = AccountManager.get(context).getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE) if (accounts.isNotEmpty()) { - accounts.forEach { performSignOut(mContext, clientPackageName, requestMap[requestTag], it) } + accounts.forEach { performSignOut(context, clientPackageName, requestMap[requestTag], it) } } } callback.onResult(Status.SUCCESS) @@ -108,7 +152,7 @@ class IdentitySignInServiceImpl(private val mContext: Context, private val clien putString(CLIENT_PACKAGE_NAME, clientPackageName) requestMap[request.sessionId] = options } - callback.onResult(Status.SUCCESS, performSignIn(bundle)) + callback.onResult(Status.SUCCESS, performGooogleSignIn(bundle)) } override fun getPhoneNumberHintIntent( @@ -118,13 +162,21 @@ class IdentitySignInServiceImpl(private val mContext: Context, private val clien callback.onResult(Status.CANCELED, null) } - private fun performSignIn(bundle: Bundle): PendingIntent { + private fun performGooogleSignIn(bundle: Bundle): PendingIntent { val intent = Intent(ACTION_ASSISTED_SIGN_IN).apply { - `package` = Constants.GMS_PACKAGE_NAME + `package` = context.packageName + putExtras(bundle) + } + val flags = PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_UPDATE_CURRENT + return PendingIntentCompat.getActivity(context, 0, intent, flags, false)!! + } + + private fun performFidoSignIn(bundle: Bundle): PendingIntent { + val intent = Intent(context, IdentityFidoProxyActivity::class.java).apply { putExtras(bundle) } val flags = PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_UPDATE_CURRENT - return PendingIntentCompat.getActivity(mContext, 0, intent, flags, false)!! + return PendingIntentCompat.getActivity(context, 0, intent, flags, false)!! } } \ No newline at end of file diff --git a/play-services-fido/core/src/main/AndroidManifest.xml b/play-services-fido/core/src/main/AndroidManifest.xml index fa6d4572cf..7a266c5600 100644 --- a/play-services-fido/core/src/main/AndroidManifest.xml +++ b/play-services-fido/core/src/main/AndroidManifest.xml @@ -38,6 +38,11 @@ android:configChanges="orientation|keyboard|keyboardHidden|screenSize" android:exported="false" android:process=":ui" - android:theme="@style/Theme.Translucent" /> + android:theme="@style/Theme.Translucent"> + + + + + diff --git a/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/RequestHandling.kt b/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/RequestHandling.kt index 1963428b67..46b36f048a 100644 --- a/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/RequestHandling.kt +++ b/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/RequestHandling.kt @@ -8,6 +8,7 @@ package org.microg.gms.fido.core import android.content.Context import android.net.Uri import android.util.Base64 +import android.util.Log import com.android.volley.toolbox.JsonArrayRequest import com.android.volley.toolbox.JsonObjectRequest import com.android.volley.toolbox.Volley @@ -23,6 +24,8 @@ import org.microg.gms.utils.* import java.net.HttpURLConnection import java.security.MessageDigest +private const val TAG = "Fido" + class RequestHandlingException(val errorCode: ErrorCode, message: String? = null) : Exception(message) class MissingPinException(message: String? = null): Exception(message) class WrongPinException(message: String? = null): Exception(message) @@ -117,8 +120,10 @@ private suspend fun isAssetLinked(context: Context, rpId: String, facetId: Strin if (fingerprint.equals(fp, ignoreCase = true)) return true } } + Log.w(TAG, "No matching asset link") return false } catch (e: Exception) { + Log.w(TAG, "Failed fetching asset link", e) return false } } diff --git a/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt b/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt index 491ae1ac3b..09550115fa 100644 --- a/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt +++ b/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt @@ -81,7 +81,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback { try { - val callerPackage = callingActivity?.packageName ?: return finish() + val callerPackage = (if (callingActivity?.packageName == packageName && intent.hasExtra(KEY_CALLER)) intent.getStringExtra(KEY_CALLER) else callingActivity?.packageName) ?: return finish() if (!intent.extras?.keySet().orEmpty().containsAll(REQUIRED_EXTRAS)) { return finishWithError(UNKNOWN_ERR, "Extra missing from request") } @@ -245,7 +245,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback { } else { intent.putExtra(FIDO2_KEY_RESPONSE_EXTRA, response.serializeToBytes()) } - setResult(-1, intent) + setResult(RESULT_OK, intent) finish() } @@ -334,6 +334,8 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback { const val TYPE_REGISTER = "register" const val TYPE_SIGN = "sign" + const val KEY_CALLER = "caller" + val IMPLEMENTED_TRANSPORTS = setOf(USB, SCREEN_LOCK, NFC) val INSTANT_SUPPORTED_TRANSPORTS = setOf(SCREEN_LOCK) } diff --git a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRequestOptions.java b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRequestOptions.java index df3069c288..8f67396079 100644 --- a/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRequestOptions.java +++ b/play-services-fido/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRequestOptions.java @@ -169,6 +169,8 @@ public static class Builder { private TokenBinding tokenBinding; @Nullable private AuthenticationExtensions authenticationExtensions; + @Nullable + private UserVerificationRequirement requireUserVerification; /** * The constructor of {@link PublicKeyCredentialRequestOptions.Builder}. @@ -180,6 +182,7 @@ public Builder() { * Sets a list of public key credentials which constrain authentication to authenticators that contain a * private key for at least one of the supplied public keys. */ + @NonNull public Builder setAllowList(@Nullable List allowList) { this.allowList = allowList; return this; @@ -189,6 +192,7 @@ public Builder setAllowList(@Nullable List allowL * Sets additional extensions that may dictate some client behavior during an exchange with a connected * authenticator. */ + @NonNull public Builder setAuthenticationExtensions(@Nullable AuthenticationExtensions authenticationExtensions) { this.authenticationExtensions = authenticationExtensions; return this; @@ -198,6 +202,7 @@ public Builder setAuthenticationExtensions(@Nullable AuthenticationExtensions au * Sets the nonce value that the authenticator should sign using a private key corresponding to a public key * credential that is acceptable for this authentication session. */ + @NonNull public Builder setChallenge(@NonNull byte[] challenge) { this.challenge = challenge; return this; @@ -208,11 +213,19 @@ public Builder setChallenge(@NonNull byte[] challenge) { * time that the server initiates a single FIDO2 request to the client and receives reply) on a single device. * This field is optional. */ + @NonNull public Builder setRequestId(@Nullable Integer requestId) { this.requestId = requestId; return this; } + @Hide + @NonNull + public Builder setRequireUserVerification(@Nullable UserVerificationRequirement requireUserVerification) { + this.requireUserVerification = requireUserVerification; + return this; + } + /** * Sets identifier for a relying party, on whose behalf a given authentication operation is being performed. * A public key credential can only be used for authentication with the same replying party it was registered @@ -222,11 +235,13 @@ public Builder setRequestId(@Nullable Integer requestId) { * context (aka https connection). Apps-facing API needs to check the package signature against Digital Asset * Links, whose resource is the RP ID with prepended "//". Privileged (browser) API doesn't need the check. */ + @NonNull public Builder setRpId(@NonNull String rpId) { this.rpId = rpId; return this; } + @NonNull public Builder setTimeoutSeconds(@Nullable Double timeoutSeconds) { this.timeoutSeconds = timeoutSeconds; return this; @@ -235,6 +250,7 @@ public Builder setTimeoutSeconds(@Nullable Double timeoutSeconds) { /** * Sets the {@link TokenBinding} associated with the calling origin. */ + @NonNull public Builder setTokenBinding(@Nullable TokenBinding tokenBinding) { this.tokenBinding = tokenBinding; return this; @@ -243,8 +259,9 @@ public Builder setTokenBinding(@Nullable TokenBinding tokenBinding) { /** * Builds the {@link PublicKeyCredentialRequestOptions} object. */ + @NonNull public PublicKeyCredentialRequestOptions build() { - return new PublicKeyCredentialRequestOptions(challenge, timeoutSeconds, rpId, allowList, requestId, tokenBinding, null, authenticationExtensions); + return new PublicKeyCredentialRequestOptions(challenge, timeoutSeconds, rpId, allowList, requestId, tokenBinding, requireUserVerification, authenticationExtensions); } }