Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit b3032b94 authored by Marvin W.'s avatar Marvin W. 🐿️
Browse files

Fido: Add full nullability information

And fix the bugs found by that
parent ae607f16
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -149,7 +149,7 @@ private suspend fun isAppIdAllowed(context: Context, appId: String, facetId: Str

suspend fun RequestOptions.checkIsValid(context: Context, facetId: String, packageName: String?) {
    if (type == REGISTER) {
        if (registerOptions.authenticatorSelection.requireResidentKey == true) {
        if (registerOptions.authenticatorSelection?.requireResidentKey == true) {
            throw RequestHandlingException(
                NOT_SUPPORTED_ERR,
                "Resident credentials or empty 'allowCredentials' lists are not supported  at this time."
+11 −8
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@
 */
package org.microg.gms.fido.core.protocol

import android.util.Log
import com.google.android.gms.fido.common.Transport
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialDescriptor
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialParameters
@@ -11,6 +12,8 @@ import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialRpEntity
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialUserEntity
import com.upokecenter.cbor.CBORObject

private const val TAG = "FidoCbor"

fun CBORObject.AsStringSequence(): Iterable<String> = Iterable {
    object : Iterator<String> {
        var index = 0
@@ -35,21 +38,21 @@ fun Boolean.encodeAsCbor() = CBORObject.FromObject(this)
fun PublicKeyCredentialRpEntity.encodeAsCbor() = CBORObject.NewMap().apply {
    set("id", id.encodeAsCbor())
    if (!name.isNullOrBlank()) set("name", name.encodeAsCbor())
    if (!icon.isNullOrBlank()) set("icon", icon.encodeAsCbor())
    if (!icon.isNullOrBlank()) set("icon", icon!!.encodeAsCbor())
}

fun PublicKeyCredentialUserEntity.encodeAsCbor() = CBORObject.NewMap().apply {
    set("id", id.encodeAsCbor())
    if (!name.isNullOrBlank()) set("name", name.encodeAsCbor())
    if (!icon.isNullOrBlank()) set("icon", icon.encodeAsCbor())
    if (!icon.isNullOrBlank()) set("icon", icon!!.encodeAsCbor())
    if (!displayName.isNullOrBlank()) set("displayName", displayName.encodeAsCbor())
}

fun CBORObject.decodeAsPublicKeyCredentialUserEntity() = PublicKeyCredentialUserEntity(
    get("id")?.GetByteString(),
    get("name")?.AsString(),
    get("id")?.GetByteString() ?: ByteArray(0).also { Log.w(TAG, "id was not present") },
    get("name")?.AsString() ?: "".also { Log.w(TAG, "name was not present") },
    get("icon")?.AsString(),
    get("displayName")?.AsString()
    get("displayName")?.AsString() ?: "".also { Log.w(TAG, "displayName was not present") }
)

fun PublicKeyCredentialParameters.encodeAsCbor() = CBORObject.NewMap().apply {
@@ -65,12 +68,12 @@ fun CBORObject.decodeAsPublicKeyCredentialParameters() = PublicKeyCredentialPara
fun PublicKeyCredentialDescriptor.encodeAsCbor() = CBORObject.NewMap().apply {
    set("type", typeAsString.encodeAsCbor())
    set("id", id.encodeAsCbor())
    set("transports", transports.encodeAsCbor { it.toString().encodeAsCbor() })
    set("transports", transports.orEmpty().encodeAsCbor { it.toString().encodeAsCbor() })
}

fun CBORObject.decodeAsPublicKeyCredentialDescriptor() = PublicKeyCredentialDescriptor(
    get("type")?.AsString(),
    get("id")?.GetByteString(),
    get("type")?.AsString() ?: "".also { Log.w(TAG, "type was not present") },
    get("id")?.GetByteString() ?: ByteArray(0).also { Log.w(TAG, "id was not present") },
    get("transports")?.AsStringSequence()?.map { Transport.fromString(it) }
)

+14 −14
Original line number Diff line number Diff line
@@ -69,11 +69,11 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
        val extensions = mutableMapOf<String, CBORObject>()
        if (options.authenticationExtensions?.fidoAppIdExtension?.appId != null) {
            extensions["appidExclude"] =
                options.authenticationExtensions.fidoAppIdExtension.appId.encodeAsCbor()
                options.authenticationExtensions!!.fidoAppIdExtension!!.appId.encodeAsCbor()
        }
        if (options.authenticationExtensions?.userVerificationMethodExtension?.uvm != null) {
            extensions["uvm"] =
                options.authenticationExtensions.userVerificationMethodExtension.uvm.encodeAsCbor()
                options.authenticationExtensions!!.userVerificationMethodExtension!!.uvm.encodeAsCbor()
        }
        val request = AuthenticatorMakeCredentialRequest(
            clientDataHash,
@@ -99,7 +99,7 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
            options.authenticationExtensions?.fidoAppIdExtension?.appId?.toByteArray()?.digest("SHA-256")
        if (!options.registerOptions.parameters.isNullOrEmpty() && options.registerOptions.parameters.all { it.algorithmIdAsInteger != -7 })
            throw IllegalArgumentException("Can't use CTAP1 protocol for non ES256 requests")
        if (options.registerOptions.authenticatorSelection.requireResidentKey == true)
        if (options.registerOptions.authenticatorSelection?.requireResidentKey == true)
            throw IllegalArgumentException("Can't use CTAP1 protocol when resident key required")
        val hasCredential = options.registerOptions.excludeList.orEmpty().any { cred ->
            ctap1DeviceHasCredential(connection, clientDataHash, rpIdHash, cred) ||
@@ -163,8 +163,8 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
            connection.hasCtap2Support -> {
                if (connection.hasCtap1Support &&
                    !connection.canMakeCredentialWithoutUserVerification && connection.hasClientPin &&
                    options.registerOptions.authenticatorSelection.requireUserVerification != REQUIRED &&
                    options.registerOptions.authenticatorSelection.requireResidentKey != true
                    options.registerOptions.authenticatorSelection?.requireUserVerification != REQUIRED &&
                    options.registerOptions.authenticatorSelection?.requireResidentKey != true
                ) {
                    Log.d(TAG, "Using CTAP1/U2F for PIN-less registration")
                    ctap1register(connection, options, clientDataHash)
@@ -176,7 +176,7 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
            else -> throw IllegalStateException()
        }
        return AuthenticatorAttestationResponse(
            keyHandle,
            keyHandle ?: ByteArray(0).also { Log.w(TAG, "keyHandle was null") },
            clientData,
            AnyAttestationObject(response.authData, response.fmt, response.attStmt).encode(),
            connection.transports.toTypedArray()
@@ -194,16 +194,16 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
        )
        val extensions = mutableMapOf<String, CBORObject>()
        if (options.authenticationExtensions?.fidoAppIdExtension?.appId != null) {
            extensions["appid"] = options.authenticationExtensions.fidoAppIdExtension.appId.encodeAsCbor()
            extensions["appid"] = options.authenticationExtensions!!.fidoAppIdExtension!!.appId.encodeAsCbor()
        }
        if (options.authenticationExtensions?.userVerificationMethodExtension?.uvm != null) {
            extensions["uvm"] =
                options.authenticationExtensions.userVerificationMethodExtension.uvm.encodeAsCbor()
                options.authenticationExtensions!!.userVerificationMethodExtension!!.uvm.encodeAsCbor()
        }
        val request = AuthenticatorGetAssertionRequest(
            options.rpId,
            clientDataHash,
            options.signOptions.allowList,
            options.signOptions.allowList.orEmpty(),
            extensions,
            reqOptions
        )
@@ -217,9 +217,9 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
        clientDataHash: ByteArray,
        rpIdHash: ByteArray
    ): Pair<AuthenticatorGetAssertionResponse, ByteArray> {
        val cred = options.signOptions.allowList.firstOrNull { cred ->
        val cred = options.signOptions.allowList.orEmpty().firstOrNull { cred ->
            ctap1DeviceHasCredential(connection, clientDataHash, rpIdHash, cred)
        } ?: options.signOptions.allowList.first()
        } ?: options.signOptions.allowList!!.first()

        while (true) {
            try {
@@ -253,7 +253,7 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
        } catch (e: Exception) {
            try {
                if (options.authenticationExtensions?.fidoAppIdExtension?.appId != null) {
                    val appIdHash = options.authenticationExtensions.fidoAppIdExtension.appId.toByteArray()
                    val appIdHash = options.authenticationExtensions!!.fidoAppIdExtension!!.appId.toByteArray()
                        .digest("SHA-256")
                    return ctap1sign(connection, options, clientDataHash, appIdHash)
                }
@@ -278,7 +278,7 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
                } catch (e: Ctap2StatusException) {
                    if (e.status == 0x2e.toByte() &&
                        connection.hasCtap1Support && connection.hasClientPin &&
                        options.signOptions.allowList.isNotEmpty() &&
                        options.signOptions.allowList.orEmpty().isNotEmpty() &&
                        options.signOptions.requireUserVerification != REQUIRED
                    ) {
                        Log.d(TAG, "Falling back to CTAP1/U2F")
@@ -297,7 +297,7 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
            else -> throw IllegalStateException()
        }
        return AuthenticatorAssertionResponse(
            credentialId,
            credentialId ?: ByteArray(0).also { Log.w(TAG, "keyHandle was null") },
            clientData,
            response.authData,
            response.signature,
+2 −2
Original line number Diff line number Diff line
@@ -191,7 +191,7 @@ class ScreenLockTransportHandler(private val activity: FragmentActivity, callbac
    ): AuthenticatorAssertionResponse {
        if (options.type != RequestOptionsType.SIGN) throw RequestHandlingException(ErrorCode.INVALID_STATE_ERR)
        val candidates = mutableListOf<CredentialId>()
        for (descriptor in options.signOptions.allowList) {
        for (descriptor in options.signOptions.allowList.orEmpty()) {
            try {
                val (type, data) = CredentialId.decodeTypeAndData(descriptor.id)
                if (type == 1.toByte() && store.containsKey(options.rpId, data)) {
@@ -238,7 +238,7 @@ class ScreenLockTransportHandler(private val activity: FragmentActivity, callbac

    override fun shouldBeUsedInstantly(options: RequestOptions): Boolean {
        if (options.type != RequestOptionsType.SIGN) return false
        for (descriptor in options.signOptions.allowList) {
        for (descriptor in options.signOptions.allowList.orEmpty()) {
            try {
                val (type, data) = CredentialId.decodeTypeAndData(descriptor.id)
                if (type == 1.toByte() && store.containsKey(options.rpId, data)) {
+4 −4
Original line number Diff line number Diff line
@@ -154,14 +154,14 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
                val knownRegistrationTransports = mutableSetOf<Transport>()
                val allowedTransports = mutableSetOf<Transport>()
                if (options.type == RequestOptionsType.SIGN) {
                    for (descriptor in options.signOptions.allowList) {
                    for (descriptor in options.signOptions.allowList.orEmpty()) {
                        val knownTransport = database.getKnownRegistrationTransport(options.rpId, descriptor.id.toBase64(Base64.URL_SAFE, Base64.NO_WRAP, Base64.NO_PADDING))
                        if (knownTransport != null && knownTransport in IMPLEMENTED_TRANSPORTS)
                            knownRegistrationTransports.add(knownTransport)
                        if (descriptor.transports.isNullOrEmpty()) {
                            allowedTransports.addAll(Transport.values())
                        } else {
                            for (transport in descriptor.transports) {
                            for (transport in descriptor.transports.orEmpty()) {
                                val allowedTransport = when (transport) {
                                    com.google.android.gms.fido.common.Transport.BLUETOOTH_CLASSIC -> BLUETOOTH
                                    com.google.android.gms.fido.common.Transport.BLUETOOTH_LOW_ENERGY -> BLUETOOTH
@@ -228,8 +228,8 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
        if (rpId != null && id != null) database.insertKnownRegistration(rpId, id, transport)
        finishWithCredential(PublicKeyCredential.Builder()
            .setResponse(response)
            .setRawId(rawId)
            .setId(id)
            .setRawId(rawId ?: ByteArray(0).also { Log.w(TAG, "rawId was null") })
            .setId(id ?: "".also { Log.w(TAG, "id was null") })
            .setAuthenticatorAttachment(if (transport == SCREEN_LOCK) "platform" else "cross-platform")
            .build()
        )
Loading