Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
val source: String?
get() = intent.getStringExtra(KEY_SOURCE)

enum class AllowedInstantLevel {
NONE, PRESELECT, INSTANT
}

private val service: GmsService
get() = GmsService.byServiceId(intent.getIntExtra(KEY_SERVICE, GmsService.UNKNOWN.SERVICE_ID))
private val database by lazy { Database(this) }
Expand All @@ -79,6 +83,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
lateinit var callerSignature: String
private lateinit var navHostFragment: NavHostFragment
private var preselectedCredentialId: String? = null
private var preselectedTransport: Transport? = null

private inline fun <reified T : TransportHandler> getTransportHandler(): T? =
transportHandlers.filterIsInstance<T>().firstOrNull { it.isSupported }
Expand Down Expand Up @@ -106,14 +111,23 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
this.callerSignature = packageManager.getFirstSignatureDigest(callerPackage, "SHA-256")?.toBase64()
?: return finishWithError(UNKNOWN_ERR, "Could not determine signature of app")
this.preselectedCredentialId = intent.getStringExtra(KEY_CREDENTIAL_ID)
this.preselectedTransport = intent.getStringExtra(KEY_PRESELECTED_TRANSPORT)?.let {
runCatching { Transport.valueOf(it) }.getOrNull()
}

Log.d(TAG, "onCreate caller=$callerPackage options=$options preselectedCredentialId=$preselectedCredentialId")

// By default, we allow instants. This extra may be used by app using fido-core library
// To avoid instant.
val allowInstant = AllowedInstantLevel.entries.getOrNull(
intent.getIntExtra(KEY_ALLOW_INSTANT, AllowedInstantLevel.INSTANT.ordinal)
) ?: AllowedInstantLevel.INSTANT

val requiresPrivilege =
source == SOURCE_BROWSER && !database.isPrivileged(callerPackage, callerSignature)

// Check if we can directly open screen lock handling
if (!requiresPrivilege) {
if (allowInstant == AllowedInstantLevel.INSTANT && !requiresPrivilege) {
val instantTransport = transportHandlers.firstOrNull {
it.isSupported && it.shouldBeUsedInstantly(options, preselectedCredentialId)
}
Expand All @@ -128,7 +142,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
setContentView(R.layout.fido_authenticator_activity)

lifecycleScope.launchWhenCreated {
handleRequest(options)
handleRequest(options, allowInstant)
}
} catch (e: RequestHandlingException) {
finishWithError(e.errorCode, e.message ?: e.errorCode.name)
Expand All @@ -139,7 +153,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
}

@RequiresApi(24)
suspend fun handleRequest(options: RequestOptions, allowInstant: Boolean = true) {
suspend fun handleRequest(options: RequestOptions, allowInstantLevel: AllowedInstantLevel = AllowedInstantLevel.INSTANT) {
try {
val origin = getOrigin(this, options, callerPackage)
options.checkIsValid(this, origin, callerPackage)
Expand All @@ -154,7 +168,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
val noLocalUserForSignInstantBlock = options.type == RequestOptionsType.SIGN && database.getKnownRegistrationInfo(options.rpId).isEmpty()

// Check if we can directly open screen lock handling
if (!requiresPrivilege && allowInstant && !noLocalUserForSignInstantBlock) {
if (!requiresPrivilege && allowInstantLevel == AllowedInstantLevel.INSTANT && !noLocalUserForSignInstantBlock) {
val instantTransport = transportHandlers.firstOrNull {
it.isSupported && it.shouldBeUsedInstantly(options, preselectedCredentialId)
}
Expand All @@ -171,7 +185,11 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
this.requiresPrivilege = requiresPrivilege
this.supportedTransports = transportHandlers.filter { it.isSupported }.map { it.transport }.toSet()
}.arguments
val next = if (!requiresPrivilege) {
val next = if (requiresPrivilege) {
null
} else if (allowInstantLevel == AllowedInstantLevel.NONE) {
R.id.transportSelectionFragment
} else {
val knownRegistrationTransports = mutableSetOf<Transport>()
val allowedTransports = mutableSetOf<Transport>()
if (options.type == RequestOptionsType.SIGN) {
Expand Down Expand Up @@ -203,7 +221,12 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
}
}
}
val preselectedTransport = knownRegistrationTransports.singleOrNull() ?: allowedTransports.singleOrNull()
// We do not control if preselectedTransport is in allowedTransports, or if
// allowedTransports is empty, as allowedTransports is a *hint*.
// This is particularly useful if the application sends a credential with
// transport=internal, but we login with another device (via hybrid connection)
val preselectedTransport = preselectedTransport
?: knownRegistrationTransports.singleOrNull() ?: allowedTransports.singleOrNull()
if (database.wasUsed()) {
when (preselectedTransport) {
USB -> R.id.usbFragment
Expand All @@ -215,8 +238,6 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
} else {
null
}
} else {
null
}
navHostFragment = NavHostFragment()
supportFragmentManager.commit {
Expand Down Expand Up @@ -321,7 +342,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
} catch (e: SecurityException) {
Log.w(TAG, e)
if (instant) {
handleRequest(options, false)
handleRequest(options, AllowedInstantLevel.PRESELECT)
} else {
finishWithError(SECURITY_ERR, e.message ?: e.javaClass.simpleName)
}
Expand Down Expand Up @@ -381,9 +402,11 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
const val KEY_SERVICE = "service"
const val KEY_SOURCE = "source"
const val KEY_TYPE = "type"
const val KEY_ALLOW_INSTANT = "allowInstant"
const val KEY_OPTIONS = "options"
const val KEY_USER_JSON = "userInfo"
const val KEY_CREDENTIAL_ID = "credential"
const val KEY_PRESELECTED_TRANSPORT = "transport"
val REQUIRED_EXTRAS = setOf(KEY_SOURCE, KEY_TYPE, KEY_OPTIONS)

const val SOURCE_BROWSER = "browser"
Expand Down