Loading play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/AuthenticatorGetInfo.kt +51 −2 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ package org.microg.gms.fido.core.protocol.msgs import com.upokecenter.cbor.CBORObject import org.microg.gms.fido.core.protocol.AsInt32Sequence import org.microg.gms.fido.core.protocol.AsStringSequence import org.microg.gms.utils.ToStringHelper class AuthenticatorGetInfoCommand : Ctap2Command<AuthenticatorGetInfoRequest, AuthenticatorGetInfoResponse>(AuthenticatorGetInfoRequest()) { override fun decodeResponse(obj: CBORObject) = AuthenticatorGetInfoResponse.decodeFromCbor(obj) Loading @@ -31,6 +32,20 @@ class AuthenticatorGetInfoResponse( val clientPin: Boolean?, val userPresence: Boolean, val userVerification: Boolean?, val pinUvAuthToken: Boolean?, val noMcGaPermissionsWithClientPin: Boolean, val largeBlobs: Boolean?, val enterpriseAttestation: Boolean?, val bioEnroll: Boolean?, val userVerificationMgmtPreview: Boolean?, val uvBioEnroll: Boolean?, val authenticatorConfigSupported: Boolean?, val uvAcfg: Boolean?, val credentialManagementSupported: Boolean?, val credentialMgmtPreview: Boolean?, val setMinPINLengthSupported: Boolean?, val makeCredUvNotRqd: Boolean, val alwaysUv: Boolean?, ) { companion object { fun decodeFromCbor(map: CBORObject?) = Options( Loading @@ -38,12 +53,46 @@ class AuthenticatorGetInfoResponse( residentKey = map?.get("rk")?.AsBoolean() == true, clientPin = map?.get("clientPin")?.AsBoolean(), userPresence = map?.get("up")?.AsBoolean() != false, userVerification = map?.get("uv")?.AsBoolean() userVerification = map?.get("uv")?.AsBoolean(), pinUvAuthToken = map?.get("pinUvAuthToken")?.AsBoolean(), noMcGaPermissionsWithClientPin = map?.get("noMcGaPermissionsWithClientPin")?.AsBoolean() == true, largeBlobs = map?.get("largeBlobs")?.AsBoolean(), enterpriseAttestation = map?.get("ep")?.AsBoolean(), bioEnroll = map?.get("bioEnroll")?.AsBoolean(), userVerificationMgmtPreview = map?.get("userVerificationMgmtPreview")?.AsBoolean(), uvBioEnroll = map?.get("uvBioEnroll")?.AsBoolean(), authenticatorConfigSupported = map?.get("authnrCfg")?.AsBoolean(), uvAcfg = map?.get("uvAcfg")?.AsBoolean(), credentialManagementSupported = map?.get("credMgmt")?.AsBoolean(), credentialMgmtPreview = map?.get("credentialMgmtPreview")?.AsBoolean(), setMinPINLengthSupported = map?.get("setMinPINLength")?.AsBoolean(), makeCredUvNotRqd = map?.get("makeCredUvNotRqd")?.AsBoolean() == true, alwaysUv = map?.get("alwaysUv")?.AsBoolean(), ) } override fun toString(): String { return "Options(platformDevice=$platformDevice, residentKey=$residentKey, clientPin=$clientPin, userPresence=$userPresence, userVerification=$userVerification)" return ToStringHelper.name("Options") .field("platformDevice", platformDevice) .field("residentKey", residentKey) .field("clientPin", clientPin) .field("userPresence", userPresence) .field("userVerification", userVerification) .field("pinUvAuthToken", pinUvAuthToken) .field("noMcGaPermissionsWithClientPin", noMcGaPermissionsWithClientPin) .field("largeBlobs", largeBlobs) .field("enterpriseAttestation", enterpriseAttestation) .field("bioEnroll", bioEnroll) .field("userVerificationMgmtPreview", userVerificationMgmtPreview) .field("uvBioEnroll", uvBioEnroll) .field("authenticatorConfigSupported", authenticatorConfigSupported) .field("uvAcfg", uvAcfg) .field("credentialManagementSupported", credentialManagementSupported) .field("credentialMgmtPreview", credentialMgmtPreview) .field("setMinPINLengthSupported", setMinPINLengthSupported) .field("makeCredUvNotRqd", makeCredUvNotRqd) .field("alwaysUv", alwaysUv) .end() } } Loading play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/CtapConnection.kt +21 −0 Original line number Diff line number Diff line Loading @@ -9,10 +9,31 @@ import com.google.android.gms.fido.fido2.api.common.ErrorCode import org.microg.gms.fido.core.RequestHandlingException import org.microg.gms.fido.core.protocol.msgs.* const val CAPABILITY_CTAP_1 = 1 shl 0 const val CAPABILITY_CTAP_2 = 1 shl 1 const val CAPABILITY_CTAP_2_1 = 1 shl 2 const val CAPABILITY_CLIENT_PIN = 1 shl 3 const val CAPABILITY_WINK = 1 shl 4 const val CAPABILITY_MAKE_CRED_WITHOUT_UV = 1 shl 5 interface CtapConnection { val capabilities: Int val hasCtap1Support: Boolean get() = capabilities and CAPABILITY_CTAP_1 > 0 val hasCtap2Support: Boolean get() = capabilities and CAPABILITY_CTAP_2 > 0 val hasCtap21Support: Boolean get() = capabilities and CAPABILITY_CTAP_2_1 > 0 val hasClientPin: Boolean get() = capabilities and CAPABILITY_CLIENT_PIN > 0 val hasWinkSupport: Boolean get() = capabilities and CAPABILITY_WINK > 0 val canMakeCredentialWithoutUserVerification: Boolean get() = capabilities and CAPABILITY_MAKE_CRED_WITHOUT_UV > 0 suspend fun <Q : Ctap1Request, S : Ctap1Response> runCommand(command: Ctap1Command<Q, S>): S suspend fun <Q : Ctap2Request, S : Ctap2Response> runCommand(command: Ctap2Command<Q, S>): S } class Ctap2StatusException(val status: Byte) : Exception("Received status ${(status.toInt() and 0xff).toString(16)}") play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/TransportHandler.kt +41 −4 Original line number Diff line number Diff line Loading @@ -8,7 +8,9 @@ package org.microg.gms.fido.core.transport import android.content.Context import android.content.Intent import android.os.Bundle import android.util.Log import com.google.android.gms.fido.fido2.api.common.* import com.google.android.gms.fido.fido2.api.common.UserVerificationRequirement.REQUIRED import com.upokecenter.cbor.CBORObject import kotlinx.coroutines.delay import org.microg.gms.fido.core.* Loading Loading @@ -48,7 +50,7 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor ): Pair<AuthenticatorMakeCredentialResponse, ByteArray?> { val reqOptions = AuthenticatorMakeCredentialRequest.Companion.Options( options.registerOptions.authenticatorSelection?.requireResidentKey == true, options.registerOptions.authenticatorSelection?.requireUserVerification == UserVerificationRequirement.REQUIRED options.registerOptions.authenticatorSelection?.requireUserVerification == REQUIRED ) val extensions = mutableMapOf<String, CBORObject>() if (options.authenticationExtensions?.fidoAppIdExtension?.appId != null) { Loading Loading @@ -144,7 +146,18 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor ): AuthenticatorAttestationResponse { val (clientData, clientDataHash) = getClientDataAndHash(context, options, callerPackage) val (response, keyHandle) = when { connection.hasCtap2Support -> ctap2register(connection, options, clientDataHash) connection.hasCtap2Support -> { if (connection.hasCtap1Support && !connection.canMakeCredentialWithoutUserVerification && connection.hasClientPin && options.registerOptions.authenticatorSelection.requireUserVerification != REQUIRED && !options.registerOptions.authenticatorSelection.requireResidentKey ) { Log.d(TAG, "Using CTAP1/U2F for PIN-less registration") ctap1register(connection, options, clientDataHash) } else { ctap2register(connection, options, clientDataHash) } } connection.hasCtap1Support -> ctap1register(connection, options, clientDataHash) else -> throw IllegalStateException() } Loading @@ -162,7 +175,7 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor clientDataHash: ByteArray ): Pair<AuthenticatorGetAssertionResponse, ByteArray?> { val reqOptions = AuthenticatorGetAssertionRequest.Companion.Options( userVerification = options.signOptions.requireUserVerification == UserVerificationRequirement.REQUIRED userVerification = options.signOptions.requireUserVerification == REQUIRED ) val extensions = mutableMapOf<String, CBORObject>() if (options.authenticationExtensions?.fidoAppIdExtension?.appId != null) { Loading Loading @@ -244,7 +257,27 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor ): AuthenticatorAssertionResponse { val (clientData, clientDataHash) = getClientDataAndHash(context, options, callerPackage) val (response, credentialId) = when { connection.hasCtap2Support -> ctap2sign(connection, options, clientDataHash) connection.hasCtap2Support -> { try { ctap2sign(connection, options, clientDataHash) } catch (e: Ctap2StatusException) { if (e.status == 0x2e.toByte() && connection.hasCtap1Support && connection.hasClientPin && options.signOptions.allowList.isNotEmpty() && options.signOptions.requireUserVerification != REQUIRED ) { Log.d(TAG, "Falling back to CTAP1/U2F") try { ctap1sign(connection, options, clientDataHash) } catch (e2: Exception) { // Throw original exception throw e } } else { throw e } } } connection.hasCtap1Support -> ctap1sign(connection, options, clientDataHash) else -> throw IllegalStateException() } Loading @@ -256,6 +289,10 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor null ) } companion object { const val TAG = "FidoTransportHandler" } } interface TransportHandlerCallback { Loading play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/nfc/CtapNfcConnection.kt +23 −20 Original line number Diff line number Diff line Loading @@ -13,7 +13,7 @@ import android.util.Log import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.microg.gms.fido.core.protocol.msgs.* import org.microg.gms.fido.core.transport.CtapConnection import org.microg.gms.fido.core.transport.* import org.microg.gms.utils.toBase64 class CtapNfcConnection( Loading @@ -21,19 +21,13 @@ class CtapNfcConnection( val tag: Tag ) : CtapConnection { private val isoDep = IsoDep.get(tag) private var capabilities: Int = 0 override val hasCtap1Support: Boolean get() = capabilities and CAPABILITY_CTAP_1 > 0 override val hasCtap2Support: Boolean get() = capabilities and CAPABILITY_CTAP_2 > 0 override var capabilities: Int = 0 override suspend fun <Q : Ctap1Request, S : Ctap1Response> runCommand(command: Ctap1Command<Q, S>): S { require(hasCtap1Support) Log.d(TAG, "Send command: ${command.request.apdu.toBase64(Base64.NO_WRAP)}") Log.d(TAG, "Send CTAP1 command: ${command.request.apdu.toBase64(Base64.NO_WRAP)}") val (statusCode, payload) = decodeResponseApdu(isoDep.transceive(command.request.apdu)) Log.d(TAG, "Received response(${(statusCode.toInt() and 0xffff).toString(16)}): ${payload.toBase64(Base64.NO_WRAP)}") Log.d(TAG, "Received CTAP1 response(${(statusCode.toInt() and 0xffff).toString(16)}): ${payload.toBase64(Base64.NO_WRAP)}") if (statusCode != 0x9000.toShort()) { throw CtapNfcMessageStatusException(statusCode.toInt() and 0xffff) } Loading @@ -43,13 +37,13 @@ class CtapNfcConnection( override suspend fun <Q : Ctap2Request, S : Ctap2Response> runCommand(command: Ctap2Command<Q, S>): S { require(hasCtap2Support) val request = encodeCommandApdu(0x80.toByte(), 0x10, 0x00, 0x00, byteArrayOf(command.request.commandByte) + command.request.payload, extended = true) Log.d(TAG, "Send command: ${request.toBase64(Base64.NO_WRAP)}") Log.d(TAG, "Send CTAP2 command: ${request.toBase64(Base64.NO_WRAP)}") var (statusCode, payload) = decodeResponseApdu(isoDep.transceive(request)) Log.d(TAG, "Received response(${(statusCode.toInt() and 0xffff).toString(16)}): ${payload.toBase64(Base64.NO_WRAP)}") Log.d(TAG, "Received CTAP2 response(${(statusCode.toInt() and 0xffff).toString(16)}): ${payload.toBase64(Base64.NO_WRAP)}") while (statusCode == 0x9100.toShort()) { Log.d(TAG, "Sending GETRESPONSE") val res = decodeResponseApdu(isoDep.transceive(encodeCommandApdu(0x00, 0xC0.toByte(), 0x00,0x00))) Log.d(TAG, "Received response(${(statusCode.toInt() and 0xffff).toString(16)}): ${payload.toBase64(Base64.NO_WRAP)}") Log.d(TAG, "Received CTAP2 response(${(statusCode.toInt() and 0xffff).toString(16)}): ${payload.toBase64(Base64.NO_WRAP)}") statusCode = res.first payload = res.second } Loading @@ -59,7 +53,7 @@ class CtapNfcConnection( require(payload.isNotEmpty()) val ctapStatusCode = payload[0] if (ctapStatusCode != 0x00.toByte()) { throw CtapNfcMessageStatusException(ctapStatusCode.toInt() and 0xff) throw Ctap2StatusException(ctapStatusCode) } return command.decodeResponse(payload, 1) } Loading @@ -71,6 +65,14 @@ class CtapNfcConnection( private fun deselect() = isoDep.transceive(encodeCommandApdu(0x80.toByte(), 0x12, 0x01, 0x02)) private suspend fun fetchCapabilities() { val response = runCommand(AuthenticatorGetInfoCommand()) Log.d(TAG, "Got info: $response") capabilities = capabilities or CAPABILITY_CTAP_2 or (if (response.versions.contains("FIDO_2_1")) CAPABILITY_CTAP_2_1 else 0) or (if (response.options.clientPin == true) CAPABILITY_CLIENT_PIN else 0) } suspend fun open(): Boolean = withContext(Dispatchers.IO) { isoDep.connect() val (statusCode, version) = select(FIDO2_AID) Loading @@ -79,15 +81,19 @@ class CtapNfcConnection( when (version.decodeToString()) { "FIDO_2_0" -> { capabilities = CAPABILITY_CTAP_2 try { fetchCapabilities() } catch (e: Exception) { Log.w(TAG, e) } true } "U2F_V2" -> { capabilities = CAPABILITY_CTAP_1 or CAPABILITY_CTAP_2 try { val response = runCommand(AuthenticatorGetInfoCommand()) Log.d(TAG, "Got info: $response") capabilities = capabilities or (if (response.versions.contains("FIDO_2_1")) CAPABILITY_CTAP_2_1 else 0) fetchCapabilities() } catch (e: Exception) { Log.w(TAG, e) capabilities = CAPABILITY_CTAP_1 } true Loading Loading @@ -131,9 +137,6 @@ class CtapNfcConnection( companion object { const val TAG = "FidoCtapNfcConnection" private val FIDO2_AID = byteArrayOf(0xA0.toByte(), 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01) private const val CAPABILITY_CTAP_1 = 1 private const val CAPABILITY_CTAP_2 = 2 private const val CAPABILITY_CTAP_2_1 = 4 } } Loading play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/screenlock/ScreenLockTransportHandler.kt +0 −2 Original line number Diff line number Diff line Loading @@ -214,8 +214,6 @@ class ScreenLockTransportHandler(private val activity: FragmentActivity, callbac val credentialId = candidates.first() val keyId = credentialId.data val (x, y) = (credentialId.publicKey as ECPublicKey).w.let { it.affineX to it.affineY } val authenticatorData = getAuthenticatorData(options.rpId, null) val signature = getActiveSignature(options, callerPackage, keyId) Loading Loading
play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/AuthenticatorGetInfo.kt +51 −2 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ package org.microg.gms.fido.core.protocol.msgs import com.upokecenter.cbor.CBORObject import org.microg.gms.fido.core.protocol.AsInt32Sequence import org.microg.gms.fido.core.protocol.AsStringSequence import org.microg.gms.utils.ToStringHelper class AuthenticatorGetInfoCommand : Ctap2Command<AuthenticatorGetInfoRequest, AuthenticatorGetInfoResponse>(AuthenticatorGetInfoRequest()) { override fun decodeResponse(obj: CBORObject) = AuthenticatorGetInfoResponse.decodeFromCbor(obj) Loading @@ -31,6 +32,20 @@ class AuthenticatorGetInfoResponse( val clientPin: Boolean?, val userPresence: Boolean, val userVerification: Boolean?, val pinUvAuthToken: Boolean?, val noMcGaPermissionsWithClientPin: Boolean, val largeBlobs: Boolean?, val enterpriseAttestation: Boolean?, val bioEnroll: Boolean?, val userVerificationMgmtPreview: Boolean?, val uvBioEnroll: Boolean?, val authenticatorConfigSupported: Boolean?, val uvAcfg: Boolean?, val credentialManagementSupported: Boolean?, val credentialMgmtPreview: Boolean?, val setMinPINLengthSupported: Boolean?, val makeCredUvNotRqd: Boolean, val alwaysUv: Boolean?, ) { companion object { fun decodeFromCbor(map: CBORObject?) = Options( Loading @@ -38,12 +53,46 @@ class AuthenticatorGetInfoResponse( residentKey = map?.get("rk")?.AsBoolean() == true, clientPin = map?.get("clientPin")?.AsBoolean(), userPresence = map?.get("up")?.AsBoolean() != false, userVerification = map?.get("uv")?.AsBoolean() userVerification = map?.get("uv")?.AsBoolean(), pinUvAuthToken = map?.get("pinUvAuthToken")?.AsBoolean(), noMcGaPermissionsWithClientPin = map?.get("noMcGaPermissionsWithClientPin")?.AsBoolean() == true, largeBlobs = map?.get("largeBlobs")?.AsBoolean(), enterpriseAttestation = map?.get("ep")?.AsBoolean(), bioEnroll = map?.get("bioEnroll")?.AsBoolean(), userVerificationMgmtPreview = map?.get("userVerificationMgmtPreview")?.AsBoolean(), uvBioEnroll = map?.get("uvBioEnroll")?.AsBoolean(), authenticatorConfigSupported = map?.get("authnrCfg")?.AsBoolean(), uvAcfg = map?.get("uvAcfg")?.AsBoolean(), credentialManagementSupported = map?.get("credMgmt")?.AsBoolean(), credentialMgmtPreview = map?.get("credentialMgmtPreview")?.AsBoolean(), setMinPINLengthSupported = map?.get("setMinPINLength")?.AsBoolean(), makeCredUvNotRqd = map?.get("makeCredUvNotRqd")?.AsBoolean() == true, alwaysUv = map?.get("alwaysUv")?.AsBoolean(), ) } override fun toString(): String { return "Options(platformDevice=$platformDevice, residentKey=$residentKey, clientPin=$clientPin, userPresence=$userPresence, userVerification=$userVerification)" return ToStringHelper.name("Options") .field("platformDevice", platformDevice) .field("residentKey", residentKey) .field("clientPin", clientPin) .field("userPresence", userPresence) .field("userVerification", userVerification) .field("pinUvAuthToken", pinUvAuthToken) .field("noMcGaPermissionsWithClientPin", noMcGaPermissionsWithClientPin) .field("largeBlobs", largeBlobs) .field("enterpriseAttestation", enterpriseAttestation) .field("bioEnroll", bioEnroll) .field("userVerificationMgmtPreview", userVerificationMgmtPreview) .field("uvBioEnroll", uvBioEnroll) .field("authenticatorConfigSupported", authenticatorConfigSupported) .field("uvAcfg", uvAcfg) .field("credentialManagementSupported", credentialManagementSupported) .field("credentialMgmtPreview", credentialMgmtPreview) .field("setMinPINLengthSupported", setMinPINLengthSupported) .field("makeCredUvNotRqd", makeCredUvNotRqd) .field("alwaysUv", alwaysUv) .end() } } Loading
play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/CtapConnection.kt +21 −0 Original line number Diff line number Diff line Loading @@ -9,10 +9,31 @@ import com.google.android.gms.fido.fido2.api.common.ErrorCode import org.microg.gms.fido.core.RequestHandlingException import org.microg.gms.fido.core.protocol.msgs.* const val CAPABILITY_CTAP_1 = 1 shl 0 const val CAPABILITY_CTAP_2 = 1 shl 1 const val CAPABILITY_CTAP_2_1 = 1 shl 2 const val CAPABILITY_CLIENT_PIN = 1 shl 3 const val CAPABILITY_WINK = 1 shl 4 const val CAPABILITY_MAKE_CRED_WITHOUT_UV = 1 shl 5 interface CtapConnection { val capabilities: Int val hasCtap1Support: Boolean get() = capabilities and CAPABILITY_CTAP_1 > 0 val hasCtap2Support: Boolean get() = capabilities and CAPABILITY_CTAP_2 > 0 val hasCtap21Support: Boolean get() = capabilities and CAPABILITY_CTAP_2_1 > 0 val hasClientPin: Boolean get() = capabilities and CAPABILITY_CLIENT_PIN > 0 val hasWinkSupport: Boolean get() = capabilities and CAPABILITY_WINK > 0 val canMakeCredentialWithoutUserVerification: Boolean get() = capabilities and CAPABILITY_MAKE_CRED_WITHOUT_UV > 0 suspend fun <Q : Ctap1Request, S : Ctap1Response> runCommand(command: Ctap1Command<Q, S>): S suspend fun <Q : Ctap2Request, S : Ctap2Response> runCommand(command: Ctap2Command<Q, S>): S } class Ctap2StatusException(val status: Byte) : Exception("Received status ${(status.toInt() and 0xff).toString(16)}")
play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/TransportHandler.kt +41 −4 Original line number Diff line number Diff line Loading @@ -8,7 +8,9 @@ package org.microg.gms.fido.core.transport import android.content.Context import android.content.Intent import android.os.Bundle import android.util.Log import com.google.android.gms.fido.fido2.api.common.* import com.google.android.gms.fido.fido2.api.common.UserVerificationRequirement.REQUIRED import com.upokecenter.cbor.CBORObject import kotlinx.coroutines.delay import org.microg.gms.fido.core.* Loading Loading @@ -48,7 +50,7 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor ): Pair<AuthenticatorMakeCredentialResponse, ByteArray?> { val reqOptions = AuthenticatorMakeCredentialRequest.Companion.Options( options.registerOptions.authenticatorSelection?.requireResidentKey == true, options.registerOptions.authenticatorSelection?.requireUserVerification == UserVerificationRequirement.REQUIRED options.registerOptions.authenticatorSelection?.requireUserVerification == REQUIRED ) val extensions = mutableMapOf<String, CBORObject>() if (options.authenticationExtensions?.fidoAppIdExtension?.appId != null) { Loading Loading @@ -144,7 +146,18 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor ): AuthenticatorAttestationResponse { val (clientData, clientDataHash) = getClientDataAndHash(context, options, callerPackage) val (response, keyHandle) = when { connection.hasCtap2Support -> ctap2register(connection, options, clientDataHash) connection.hasCtap2Support -> { if (connection.hasCtap1Support && !connection.canMakeCredentialWithoutUserVerification && connection.hasClientPin && options.registerOptions.authenticatorSelection.requireUserVerification != REQUIRED && !options.registerOptions.authenticatorSelection.requireResidentKey ) { Log.d(TAG, "Using CTAP1/U2F for PIN-less registration") ctap1register(connection, options, clientDataHash) } else { ctap2register(connection, options, clientDataHash) } } connection.hasCtap1Support -> ctap1register(connection, options, clientDataHash) else -> throw IllegalStateException() } Loading @@ -162,7 +175,7 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor clientDataHash: ByteArray ): Pair<AuthenticatorGetAssertionResponse, ByteArray?> { val reqOptions = AuthenticatorGetAssertionRequest.Companion.Options( userVerification = options.signOptions.requireUserVerification == UserVerificationRequirement.REQUIRED userVerification = options.signOptions.requireUserVerification == REQUIRED ) val extensions = mutableMapOf<String, CBORObject>() if (options.authenticationExtensions?.fidoAppIdExtension?.appId != null) { Loading Loading @@ -244,7 +257,27 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor ): AuthenticatorAssertionResponse { val (clientData, clientDataHash) = getClientDataAndHash(context, options, callerPackage) val (response, credentialId) = when { connection.hasCtap2Support -> ctap2sign(connection, options, clientDataHash) connection.hasCtap2Support -> { try { ctap2sign(connection, options, clientDataHash) } catch (e: Ctap2StatusException) { if (e.status == 0x2e.toByte() && connection.hasCtap1Support && connection.hasClientPin && options.signOptions.allowList.isNotEmpty() && options.signOptions.requireUserVerification != REQUIRED ) { Log.d(TAG, "Falling back to CTAP1/U2F") try { ctap1sign(connection, options, clientDataHash) } catch (e2: Exception) { // Throw original exception throw e } } else { throw e } } } connection.hasCtap1Support -> ctap1sign(connection, options, clientDataHash) else -> throw IllegalStateException() } Loading @@ -256,6 +289,10 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor null ) } companion object { const val TAG = "FidoTransportHandler" } } interface TransportHandlerCallback { Loading
play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/nfc/CtapNfcConnection.kt +23 −20 Original line number Diff line number Diff line Loading @@ -13,7 +13,7 @@ import android.util.Log import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.microg.gms.fido.core.protocol.msgs.* import org.microg.gms.fido.core.transport.CtapConnection import org.microg.gms.fido.core.transport.* import org.microg.gms.utils.toBase64 class CtapNfcConnection( Loading @@ -21,19 +21,13 @@ class CtapNfcConnection( val tag: Tag ) : CtapConnection { private val isoDep = IsoDep.get(tag) private var capabilities: Int = 0 override val hasCtap1Support: Boolean get() = capabilities and CAPABILITY_CTAP_1 > 0 override val hasCtap2Support: Boolean get() = capabilities and CAPABILITY_CTAP_2 > 0 override var capabilities: Int = 0 override suspend fun <Q : Ctap1Request, S : Ctap1Response> runCommand(command: Ctap1Command<Q, S>): S { require(hasCtap1Support) Log.d(TAG, "Send command: ${command.request.apdu.toBase64(Base64.NO_WRAP)}") Log.d(TAG, "Send CTAP1 command: ${command.request.apdu.toBase64(Base64.NO_WRAP)}") val (statusCode, payload) = decodeResponseApdu(isoDep.transceive(command.request.apdu)) Log.d(TAG, "Received response(${(statusCode.toInt() and 0xffff).toString(16)}): ${payload.toBase64(Base64.NO_WRAP)}") Log.d(TAG, "Received CTAP1 response(${(statusCode.toInt() and 0xffff).toString(16)}): ${payload.toBase64(Base64.NO_WRAP)}") if (statusCode != 0x9000.toShort()) { throw CtapNfcMessageStatusException(statusCode.toInt() and 0xffff) } Loading @@ -43,13 +37,13 @@ class CtapNfcConnection( override suspend fun <Q : Ctap2Request, S : Ctap2Response> runCommand(command: Ctap2Command<Q, S>): S { require(hasCtap2Support) val request = encodeCommandApdu(0x80.toByte(), 0x10, 0x00, 0x00, byteArrayOf(command.request.commandByte) + command.request.payload, extended = true) Log.d(TAG, "Send command: ${request.toBase64(Base64.NO_WRAP)}") Log.d(TAG, "Send CTAP2 command: ${request.toBase64(Base64.NO_WRAP)}") var (statusCode, payload) = decodeResponseApdu(isoDep.transceive(request)) Log.d(TAG, "Received response(${(statusCode.toInt() and 0xffff).toString(16)}): ${payload.toBase64(Base64.NO_WRAP)}") Log.d(TAG, "Received CTAP2 response(${(statusCode.toInt() and 0xffff).toString(16)}): ${payload.toBase64(Base64.NO_WRAP)}") while (statusCode == 0x9100.toShort()) { Log.d(TAG, "Sending GETRESPONSE") val res = decodeResponseApdu(isoDep.transceive(encodeCommandApdu(0x00, 0xC0.toByte(), 0x00,0x00))) Log.d(TAG, "Received response(${(statusCode.toInt() and 0xffff).toString(16)}): ${payload.toBase64(Base64.NO_WRAP)}") Log.d(TAG, "Received CTAP2 response(${(statusCode.toInt() and 0xffff).toString(16)}): ${payload.toBase64(Base64.NO_WRAP)}") statusCode = res.first payload = res.second } Loading @@ -59,7 +53,7 @@ class CtapNfcConnection( require(payload.isNotEmpty()) val ctapStatusCode = payload[0] if (ctapStatusCode != 0x00.toByte()) { throw CtapNfcMessageStatusException(ctapStatusCode.toInt() and 0xff) throw Ctap2StatusException(ctapStatusCode) } return command.decodeResponse(payload, 1) } Loading @@ -71,6 +65,14 @@ class CtapNfcConnection( private fun deselect() = isoDep.transceive(encodeCommandApdu(0x80.toByte(), 0x12, 0x01, 0x02)) private suspend fun fetchCapabilities() { val response = runCommand(AuthenticatorGetInfoCommand()) Log.d(TAG, "Got info: $response") capabilities = capabilities or CAPABILITY_CTAP_2 or (if (response.versions.contains("FIDO_2_1")) CAPABILITY_CTAP_2_1 else 0) or (if (response.options.clientPin == true) CAPABILITY_CLIENT_PIN else 0) } suspend fun open(): Boolean = withContext(Dispatchers.IO) { isoDep.connect() val (statusCode, version) = select(FIDO2_AID) Loading @@ -79,15 +81,19 @@ class CtapNfcConnection( when (version.decodeToString()) { "FIDO_2_0" -> { capabilities = CAPABILITY_CTAP_2 try { fetchCapabilities() } catch (e: Exception) { Log.w(TAG, e) } true } "U2F_V2" -> { capabilities = CAPABILITY_CTAP_1 or CAPABILITY_CTAP_2 try { val response = runCommand(AuthenticatorGetInfoCommand()) Log.d(TAG, "Got info: $response") capabilities = capabilities or (if (response.versions.contains("FIDO_2_1")) CAPABILITY_CTAP_2_1 else 0) fetchCapabilities() } catch (e: Exception) { Log.w(TAG, e) capabilities = CAPABILITY_CTAP_1 } true Loading Loading @@ -131,9 +137,6 @@ class CtapNfcConnection( companion object { const val TAG = "FidoCtapNfcConnection" private val FIDO2_AID = byteArrayOf(0xA0.toByte(), 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01) private const val CAPABILITY_CTAP_1 = 1 private const val CAPABILITY_CTAP_2 = 2 private const val CAPABILITY_CTAP_2_1 = 4 } } Loading
play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/screenlock/ScreenLockTransportHandler.kt +0 −2 Original line number Diff line number Diff line Loading @@ -214,8 +214,6 @@ class ScreenLockTransportHandler(private val activity: FragmentActivity, callbac val credentialId = candidates.first() val keyId = credentialId.data val (x, y) = (credentialId.publicKey as ECPublicKey).w.let { it.affineX to it.affineY } val authenticatorData = getAuthenticatorData(options.rpId, null) val signature = getActiveSignature(options, callerPackage, keyId) Loading