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);
}
}