Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow using FIDO2 authenticators with a PIN #2194

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import java.net.HttpURLConnection
import java.security.MessageDigest

class RequestHandlingException(val errorCode: ErrorCode, message: String? = null) : Exception(message)
class MissingPinException(message: String? = null): Exception(message)
class WrongPinException(message: String? = null): Exception(message)

enum class RequestOptionsType { REGISTER, SIGN }

Expand Down Expand Up @@ -148,14 +150,6 @@ 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) {
throw RequestHandlingException(
NOT_SUPPORTED_ERR,
"Resident credentials or empty 'allowCredentials' lists are not supported at this time."
)
}
}
if (type == SIGN) {
if (signOptions.allowList.isNullOrEmpty()) {
throw RequestHandlingException(NOT_ALLOWED_ERR, "Request doesn't have a valid list of allowed credentials.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ 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.Algorithm
import com.google.android.gms.fido.fido2.api.common.EC2Algorithm
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialDescriptor
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialParameters
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialRpEntity
import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialUserEntity
import com.google.android.gms.fido.fido2.api.common.RSAAlgorithm
import com.upokecenter.cbor.CBORObject

private const val TAG = "FidoCbor"
Expand Down Expand Up @@ -55,6 +58,33 @@ fun CBORObject.decodeAsPublicKeyCredentialUserEntity() = PublicKeyCredentialUser
get("displayName")?.AsString() ?: "".also { Log.w(TAG, "displayName was not present") }
)

fun CBORObject.decodeAsCoseKey() = CoseKey(
getAlgorithm(get(CoseKey.ALG).AsInt32Value()),
get(CoseKey.X).GetByteString(),
get(CoseKey.Y).GetByteString(),
get(CoseKey.CRV).AsInt32Value()
)

fun getAlgorithm(algorithmInt: Int): Algorithm {
return when (algorithmInt) {
-65535 -> RSAAlgorithm.RS1
-262 -> RSAAlgorithm.LEGACY_RS1
-261 -> EC2Algorithm.ED512
-260 -> EC2Algorithm.ED256
-259 -> RSAAlgorithm.RS512
-258 -> RSAAlgorithm.RS384
-257 -> RSAAlgorithm.RS256
-39 -> RSAAlgorithm.PS512
-38 -> RSAAlgorithm.PS384
-37 -> RSAAlgorithm.PS256
-36 -> EC2Algorithm.ES512
-35 -> EC2Algorithm.ES384
-7 -> EC2Algorithm.ES256

else -> Algorithm { algorithmInt }
}
}

fun PublicKeyCredentialParameters.encodeAsCbor() = CBORObject.NewMap().apply {
set("alg", algorithmIdAsInteger.encodeAsCbor())
set("type", typeAsString.encodeAsCbor())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,22 @@ class CoseKey(
constructor(algorithm: Algorithm, x: BigInteger, y: BigInteger, curveId: Int, curvePointSize: Int) :
this(algorithm, x.toByteArray(curvePointSize), y.toByteArray(curvePointSize), curveId)

fun encode(): ByteArray = CBORObject.NewMap().apply {
fun encode(): ByteArray = encodeAsCbor().EncodeToBytes(CBOREncodeOptions.DefaultCtap2Canonical)

fun encodeAsCbor(): CBORObject = CBORObject.NewMap().apply {
set(KTY, 2.encodeAsCbor())
set(ALG, algorithm.algoValue.encodeAsCbor())
set(CRV, curveId.encodeAsCbor())
set(X, x.encodeAsCbor())
set(Y, y.encodeAsCbor())
}.EncodeToBytes(CBOREncodeOptions.DefaultCtap2Canonical)
}

companion object {
private const val KTY = 1
private const val ALG = 3
private const val CRV = -1
private const val X = -2
private const val Y = -3
const val KTY = 1
const val ALG = 3
const val CRV = -1
const val X = -2
const val Y = -3

fun BigInteger.toByteArray(size: Int): ByteArray {
val res = ByteArray(size)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.microg.gms.fido.core.protocol.msgs

import com.upokecenter.cbor.CBORObject
import org.microg.gms.fido.core.protocol.CoseKey
import org.microg.gms.fido.core.protocol.decodeAsCoseKey
import org.microg.gms.fido.core.protocol.encodeAsCbor

class AuthenticatorClientPINCommand(request: AuthenticatorClientPINRequest) :
Ctap2Command<AuthenticatorClientPINRequest, AuthenticatorClientPINResponse>(request) {

override fun decodeResponse(obj: CBORObject) = AuthenticatorClientPINResponse.decodeFromCbor(obj)

override val timeout: Long
get() = 60000

}

class AuthenticatorClientPINRequest(
val pinProtocol: Int,
val subCommand: Int,
val keyAgreement: CoseKey? = null,
val pinAuth: ByteArray? = null,
val newPinEnc: ByteArray? = null,
val pinHashEnc: ByteArray? = null
) : Ctap2Request(0x06, CBORObject.NewMap().apply {
set(0x01, pinProtocol.encodeAsCbor())
set(0x02, subCommand.encodeAsCbor())
if (keyAgreement != null) set(0x03, keyAgreement.encodeAsCbor())
if (pinAuth != null) set(0x04, pinAuth.encodeAsCbor())
if (newPinEnc != null) set(0x05, newPinEnc.encodeAsCbor())
if (pinHashEnc != null) set(0x06, pinHashEnc.encodeAsCbor())
}) {
override fun toString(): String {
return "AuthenticatorClientPINRequest(pinProtocol=$pinProtocol, " +
"subCommand=$subCommand, keyAgreement=$keyAgreement, " +
"pinAuth=${pinAuth?.contentToString()}, " +
"newPinEnc=${newPinEnc?.contentToString()}, " +
"pinHashEnc=${pinHashEnc?.contentToString()})"
}

companion object {
// PIN protocol versions
const val PIN_PROTOCOL_VERSION_ONE = 0x01

// PIN subcommands
const val GET_RETRIES = 0x01
const val GET_KEY_AGREEMENT = 0x02
const val SET_PIN = 0x03
const val CHANGE_PIN = 0x04
const val GET_PIN_TOKEN = 0x05
}
}

class AuthenticatorClientPINResponse(
val keyAgreement: CoseKey?,
val pinToken: ByteArray?,
val retries: Int?
) : Ctap2Response {
companion object {
fun decodeFromCbor(obj: CBORObject) = AuthenticatorClientPINResponse(
obj.get(0x01)?.decodeAsCoseKey(),
obj.get(0x02)?.GetByteString(),
obj.get(0x03)?.AsInt32Value()
)
}
}