Skip to content

Commit

Permalink
Added a screen for choosing credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
TheMartinizer committed Feb 28, 2024
1 parent 34d14d4 commit 21e1460
Show file tree
Hide file tree
Showing 14 changed files with 508 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.google.android.gms.fido.fido2.api.common.*
import kotlinx.coroutines.CancellationException
import org.json.JSONArray
import org.json.JSONObject
import org.microg.gms.fido.core.AuthenticatorResponseWrapper
import org.microg.gms.fido.core.RequestHandlingException
import org.microg.gms.fido.core.transport.Transport
import org.microg.gms.fido.core.transport.TransportHandlerCallback
Expand Down Expand Up @@ -68,9 +69,11 @@ class FidoHandler(private val activity: LoginActivity) : TransportHandlerCallbac
})
}

private fun sendSuccessResult(response: AuthenticatorResponse, transport: Transport) {
Log.d(TAG, "Finish with success response: $response")
private suspend fun sendSuccessResult(responseWrapper: AuthenticatorResponseWrapper, transport: Transport) {
val response = responseWrapper.responseChoices.get(0).second.invoke()
if (response is AuthenticatorAssertionResponse) {
// TODO: Choose credentials here?
Log.d(TAG, "Finish with success response: $response")
sendResult(JSONObject().apply {
val base64Flags = Base64.NO_PADDING + Base64.NO_WRAP + Base64.URL_SAFE
put("keyHandle", response.keyHandle?.toBase64(base64Flags))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ class MissingPinException(message: String? = null): Exception(message)
class WrongPinException(message: String? = null): Exception(message)

enum class RequestOptionsType { REGISTER, SIGN }
class UserInfo(
val name: String,
val displayName: String? = null,
val icon: String? = null
)
class AuthenticatorResponseWrapper (
val responseChoices: List<Pair<UserInfo?, suspend () -> AuthenticatorResponse>>
)

val RequestOptions.registerOptions: PublicKeyCredentialCreationOptions
get() = when (this) {
Expand Down Expand Up @@ -150,11 +158,6 @@ private suspend fun isAppIdAllowed(context: Context, appId: String, facetId: Str
}

suspend fun RequestOptions.checkIsValid(context: Context, facetId: String, packageName: String?) {
if (type == SIGN) {
if (signOptions.allowList.isNullOrEmpty()) {
throw RequestHandlingException(NOT_ALLOWED_ERR, "Request doesn't have a valid list of allowed credentials.")
}
}
if (facetId.startsWith("https://")) {
if (topDomainOf(Uri.parse(facetId).host) != topDomainOf(rpId)) {
throw RequestHandlingException(NOT_ALLOWED_ERR, "RP ID $rpId not allowed from facet $facetId")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.microg.gms.fido.core.protocol.msgs

import com.upokecenter.cbor.CBORObject

class AuthenticatorGetNextAssertionCommand(request: AuthenticatorGetNextAssertionRequest) :
Ctap2Command<AuthenticatorGetNextAssertionRequest, AuthenticatorGetAssertionResponse>(request) {
override fun decodeResponse(obj: CBORObject) = AuthenticatorGetAssertionResponse.decodeFromCbor(obj)
override val timeout: Long
get() = 60000
}

class AuthenticatorGetNextAssertionRequest() : Ctap2Request(0x08)
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
open val isSupported: Boolean
get() = false

open suspend fun start(options: RequestOptions, callerPackage: String, pinRequested: Boolean = false, pin: String? = null): AuthenticatorResponse =
open suspend fun start(options: RequestOptions, callerPackage: String, pinRequested: Boolean = false, pin: String? = null): AuthenticatorResponseWrapper =
throw RequestHandlingException(ErrorCode.NOT_SUPPORTED_ERR)

open fun shouldBeUsedInstantly(options: RequestOptions): Boolean = false
Expand Down Expand Up @@ -197,7 +197,7 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
callerPackage: String,
pinRequested: Boolean,
pin: String?
): AuthenticatorAttestationResponse {
): suspend () -> AuthenticatorAttestationResponse {
val (clientData, clientDataHash) = getClientDataAndHash(context, options, callerPackage)

val authenticatorCapableOfResidentKeys = (connection.capabilities and CAPABILITY_RESIDENT_KEY != 0)
Expand Down Expand Up @@ -255,12 +255,13 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
connection.hasCtap1Support -> ctap1register(connection, options, clientDataHash)
else -> throw IllegalStateException()
}
return AuthenticatorAttestationResponse(
val authenticatorResponse = AuthenticatorAttestationResponse(
keyHandle ?: ByteArray(0).also { Log.w(TAG, "keyHandle was null") },
clientData,
AnyAttestationObject(response.authData, response.fmt, response.attStmt).encode(),
connection.transports.toTypedArray()
)
return suspend { authenticatorResponse }
}


Expand All @@ -270,7 +271,9 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
clientDataHash: ByteArray,
requireUserVerification: Boolean,
pinToken: ByteArray? = null
): Pair<AuthenticatorGetAssertionResponse, ByteArray?> {
): List<Pair<AuthenticatorGetAssertionResponse, ByteArray?>> {
val responseList = ArrayList<Pair<AuthenticatorGetAssertionResponse, ByteArray?>>()

val reqOptions = AuthenticatorGetAssertionRequest.Companion.Options(
// The specification states that the WebAuthn requireUserVerification option should map to
// the CTAP2 "uv" flag OR pinAuth/pinProtocol. Therefore, set this flag to false if
Expand Down Expand Up @@ -306,7 +309,16 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
pinProtocol
)
val ctap2Response = connection.runCommand(AuthenticatorGetAssertionCommand(request))
return ctap2Response to ctap2Response.credential?.id
responseList.add(ctap2Response to ctap2Response.credential?.id)

for (i in 1..< (ctap2Response.numberOfCredentials ?: 0)) {
val nextRequest = AuthenticatorGetNextAssertionRequest()
val nextResponse = connection.runCommand(AuthenticatorGetNextAssertionCommand(nextRequest))

responseList.add(nextResponse to nextResponse.credential?.id)
}

return responseList
}

@RequiresApi(Build.VERSION_CODES.S)
Expand Down Expand Up @@ -403,7 +415,7 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
options: RequestOptions,
clientDataHash: ByteArray,
rpIdHash: ByteArray
): Pair<AuthenticatorGetAssertionResponse, ByteArray> {
): List<Pair<AuthenticatorGetAssertionResponse, ByteArray>> {
val cred = options.signOptions.allowList.orEmpty().firstOrNull { cred ->
ctap1DeviceHasCredential(connection, clientDataHash, rpIdHash, cred)
} ?: options.signOptions.allowList!!.first()
Expand All @@ -419,7 +431,7 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
null,
null
)
return ctap2Response to cred.id
return listOf(ctap2Response to cred.id)
} catch (e: CtapHidMessageStatusException) {
if (e.status != 0x6985) {
throw e
Expand All @@ -433,7 +445,7 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
connection: CtapConnection,
options: RequestOptions,
clientDataHash: ByteArray
): Pair<AuthenticatorGetAssertionResponse, ByteArray> {
): List<Pair<AuthenticatorGetAssertionResponse, ByteArray>> {
try {
val rpIdHash = options.rpId.toByteArray().digest("SHA-256")
return ctap1sign(connection, options, clientDataHash, rpIdHash)
Expand All @@ -458,10 +470,10 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
callerPackage: String,
pinRequested: Boolean,
pin: String?
): AuthenticatorAssertionResponse {
): List<Pair<UserInfo?, suspend () -> AuthenticatorAssertionResponse>> {
val (clientData, clientDataHash) = getClientDataAndHash(context, options, callerPackage)

val (response, credentialId) = when {
val responses: List<Pair<AuthenticatorGetAssertionResponse, ByteArray?>> = when {
connection.hasCtap2Support -> {
try {
var pinToken: ByteArray? = null
Expand Down Expand Up @@ -520,13 +532,30 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
connection.hasCtap1Support -> ctap1sign(connection, options, clientDataHash)
else -> throw IllegalStateException()
}
return AuthenticatorAssertionResponse(
credentialId ?: ByteArray(0).also { Log.w(TAG, "keyHandle was null") },
clientData,
response.authData,
response.signature,
null
)

val assertionResponses = ArrayList<Pair<UserInfo?, suspend () -> AuthenticatorAssertionResponse>>()

for ((response, credentialId) in responses) {
var name = response.user?.name
var displayName = response.user?.displayName
var icon = response.user?.icon

var userInfo: UserInfo? = null
if (name != null) {
userInfo = UserInfo(name, displayName, icon)
}

val assertionResponse = AuthenticatorAssertionResponse(
credentialId ?: ByteArray(0).also { Log.w(TAG, "keyHandle was null for key with display name $displayName") },
clientData,
response.authData,
response.signature,
null
)
assertionResponses.add(userInfo to suspend { assertionResponse })
}

return assertionResponses
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import com.google.android.gms.fido.fido2.api.common.AuthenticatorResponse
import com.google.android.gms.fido.fido2.api.common.RequestOptions
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import org.microg.gms.fido.core.AuthenticatorResponseWrapper
import org.microg.gms.fido.core.MissingPinException
import org.microg.gms.fido.core.RequestOptionsType
import org.microg.gms.fido.core.UserInfo
import org.microg.gms.fido.core.WrongPinException
import org.microg.gms.fido.core.transport.Transport
import org.microg.gms.fido.core.transport.TransportHandler
Expand Down Expand Up @@ -57,7 +59,7 @@ class NfcTransportHandler(private val activity: Activity, callback: TransportHan
tag: Tag,
pinRequested: Boolean,
pin: String?
): AuthenticatorAttestationResponse {
): suspend () -> AuthenticatorAttestationResponse {
return CtapNfcConnection(activity, tag).open {
register(it, activity, options, callerPackage, pinRequested, pin)
}
Expand All @@ -69,7 +71,7 @@ class NfcTransportHandler(private val activity: Activity, callback: TransportHan
tag: Tag,
pinRequested: Boolean,
pin: String?
): AuthenticatorAssertionResponse {
): List<Pair<UserInfo?, suspend () -> AuthenticatorAssertionResponse>> {
return CtapNfcConnection(activity, tag).open {
sign(it, activity, options, callerPackage, pinRequested, pin)
}
Expand All @@ -82,15 +84,15 @@ class NfcTransportHandler(private val activity: Activity, callback: TransportHan
tag: Tag,
pinRequested: Boolean,
pin: String?
): AuthenticatorResponse {
): AuthenticatorResponseWrapper {
return when (options.type) {
RequestOptionsType.REGISTER -> register(options, callerPackage, tag, pinRequested, pin)
RequestOptionsType.SIGN -> sign(options, callerPackage, tag, pinRequested, pin)
RequestOptionsType.REGISTER -> AuthenticatorResponseWrapper(listOf(Pair(null, register(options, callerPackage, tag, pinRequested, pin))))
RequestOptionsType.SIGN -> AuthenticatorResponseWrapper(sign(options, callerPackage, tag, pinRequested, pin))
}
}


override suspend fun start(options: RequestOptions, callerPackage: String, pinRequested: Boolean, pin: String?): AuthenticatorResponse {
override suspend fun start(options: RequestOptions, callerPackage: String, pinRequested: Boolean, pin: String?): AuthenticatorResponseWrapper {
val adapter = NfcAdapter.getDefaultAdapter(activity)
val newIntentListener = Consumer<Intent> {
if (it?.action != NfcAdapter.ACTION_TECH_DISCOVERED) return@Consumer
Expand Down
Loading

0 comments on commit 21e1460

Please sign in to comment.