From 1a3e8885a48ba80e7af94f0068731474be031999 Mon Sep 17 00:00:00 2001 From: sim Date: Mon, 8 Jun 2026 11:50:01 +0200 Subject: [PATCH 1/2] Fido: Fix algorithm from Cbor We were maintaining 2 different list of algorithmInt to Algorithm which led to missing alg (-8). By using EC2Algorithm and RSAAlgorithm enums directly we avoid further missing algs. --- .../org/microg/gms/fido/core/protocol/Cbor.kt | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/protocol/Cbor.kt b/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/protocol/Cbor.kt index 745f90aa72..b1491c348e 100644 --- a/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/protocol/Cbor.kt +++ b/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/protocol/Cbor.kt @@ -65,24 +65,9 @@ fun CBORObject.decodeAsPublicKeyCredentialUserEntity() = PublicKeyCredentialUser ) 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 - -25 -> EC2Algorithm.ECDH_HKDF_256 - -7 -> EC2Algorithm.ES256 - - else -> Algorithm { algorithmInt } - } + return EC2Algorithm.entries.firstOrNull { it.algoValue == algorithmInt } + ?: RSAAlgorithm.entries.firstOrNull { it.algoValue == algorithmInt } + ?: Algorithm { algorithmInt } } fun PublicKeyCredentialParameters.encodeAsCbor() = CBORObject.NewMap().apply { From 2c40dca21d664e2ce8a00bf9d6b795a5e5b1ec86 Mon Sep 17 00:00:00 2001 From: sim Date: Mon, 8 Jun 2026 12:43:27 +0200 Subject: [PATCH 2/2] Fido: Fix algorithm parameter spec with x25519/x448 The parameter must be retrieved using the NamedParameterSpec cf. https://docs.oracle.com/en/java/javase/25/docs/specs/security/standard-names.html#namedparameterspec --- .../microg/gms/fido/core/protocol/CoseKey.kt | 108 +++++++++++++++--- .../fido/core/transport/TransportHandler.kt | 19 +-- 2 files changed, 96 insertions(+), 31 deletions(-) diff --git a/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/protocol/CoseKey.kt b/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/protocol/CoseKey.kt index 61bec1fec4..ec2de561a4 100644 --- a/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/protocol/CoseKey.kt +++ b/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/protocol/CoseKey.kt @@ -5,19 +5,22 @@ package org.microg.gms.fido.core.protocol +import android.os.Build +import androidx.annotation.RequiresApi 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.RSAAlgorithm import com.upokecenter.cbor.CBOREncodeOptions import com.upokecenter.cbor.CBORObject import java.math.BigInteger import java.security.AlgorithmParameters import java.security.KeyFactory import java.security.PublicKey +import java.security.spec.AlgorithmParameterSpec import java.security.spec.ECGenParameterSpec import java.security.spec.ECParameterSpec import java.security.spec.ECPoint import java.security.spec.ECPublicKeySpec +import java.security.spec.NamedParameterSpec class CoseKey( val algorithm: Algorithm, @@ -38,25 +41,98 @@ class CoseKey( set(Y, y.encodeAsCbor()) } + sealed class AlgSpec(val keyAlg: String, val agreementAlg: String, val paramSpec: AlgorithmParameterSpec) { + class EC(algName: String): AlgSpec( + "EC", + "ECDH", + ECGenParameterSpec(algName) + ) + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + class XDH(algName: String, private val oid: ByteArray, private val keyLength: Int): AlgSpec( + algName, + "XDH", + NamedParameterSpec(algName) + ) { + /** + * Works for key+preamble smaller than 256 bytes (x25519 is 32 + preamble = 42) + */ + val x509Preamble: ByteArray + get() { + require(keyLength + oid.size + 5 < 0x100) + val header = byteArrayOf( + 0x30, oid.size.toByte() // Sequence of OID.size bytes + ) + oid + byteArrayOf( + 0x03, (keyLength + 1).toByte(), 0x00 // Bit string of keyLength +1 + 0x00 beginning of the key + ) + return byteArrayOf( + 0x30, (header.size + keyLength).toByte(), // Sequence of header + keylength bytes + ) + header + } + } + } + + fun getAlgSpec(): AlgSpec? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // cf. https://www.iana.org/assignments/smi-numbers/smi-numbers.xhtml#smi-numbers-1.3.101 + // for OID + when (curveId) { + 1 -> AlgSpec.EC("secp256r1") + 2 -> AlgSpec.EC("secp384r1") + 3 -> AlgSpec.EC("secp521r1") + 4 -> AlgSpec.XDH( + "x25519", + byteArrayOf(0x06, 0x03, 0x2b, 0x65, 0x6e), // OID: 1.3.101.110 (X25519) + 32 + ) + 5 -> AlgSpec.XDH( + "x448", + byteArrayOf(0x06, 0x03, 0x2b, 0x65, 0x6f), // OID: 1.3.101.111 (X448) + 56 + ) + 6 -> AlgSpec.XDH( + "Ed25519", + byteArrayOf(0x06, 0x03, 0x2b, 0x65, 0x70), // OID: 1.3.101.112 (ED25519) + 32 + ) + 7 -> AlgSpec.XDH( + "Ed448", + byteArrayOf(0x06, 0x03, 0x2b, 0x65, 0x77), // OID: 1.3.101.113 (ED448) + 56 + ) + else -> null + } + } else { + when (curveId) { + 1 -> AlgSpec.EC("secp256r1") + 2 -> AlgSpec.EC("secp384r1") + 3 -> AlgSpec.EC("secp521r1") + else -> null + } + } + } + fun asCryptoKey(): PublicKey? { return when(algorithm) { is EC2Algorithm -> { - val curveName = when (curveId) { - 1 -> "secp256r1" - 2 -> "secp384r1" - 3 -> "secp521r1" - 4 -> "x25519" - 5 -> "x448" - 6 -> "Ed25519" - 7 -> "Ed448" - else -> return null + val algSpec = getAlgSpec() ?: return null + when (algSpec) { + is AlgSpec.EC -> { + val parameters = AlgorithmParameters.getInstance("EC") + parameters.init(algSpec.paramSpec) + val parameterSpec = parameters.getParameterSpec(ECParameterSpec::class.java) + val keySpec = ECPublicKeySpec(ECPoint(BigInteger(1, x), BigInteger(1, y)), parameterSpec) + KeyFactory.getInstance("EC").generatePublic(keySpec) + } + is AlgSpec.XDH -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + object : PublicKey { + override fun getAlgorithm(): String = algSpec.keyAlg + override fun getFormat(): String = "x.509" + override fun getEncoded(): ByteArray = algSpec.x509Preamble + x + } + } else { + null + } } - - val parameters = AlgorithmParameters.getInstance("EC") - parameters.init(ECGenParameterSpec(curveName)) - val parameterSpec = parameters.getParameterSpec(ECParameterSpec::class.java) - val keySpec = ECPublicKeySpec(ECPoint(BigInteger(1, x), BigInteger(1, y)), parameterSpec) - KeyFactory.getInstance("EC").generatePublic(keySpec) } else -> null } diff --git a/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/TransportHandler.kt b/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/TransportHandler.kt index e3a3669782..09e818ce19 100644 --- a/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/TransportHandler.kt +++ b/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/TransportHandler.kt @@ -8,7 +8,6 @@ package org.microg.gms.fido.core.transport import android.content.Context import android.os.Build.VERSION.SDK_INT import android.os.Bundle -import android.security.keystore.KeyProperties import android.util.Log import androidx.annotation.RequiresApi import com.google.android.gms.fido.fido2.api.common.* @@ -26,7 +25,6 @@ import java.nio.charset.StandardCharsets import java.security.KeyPairGenerator import java.security.MessageDigest import java.security.interfaces.ECPublicKey -import java.security.spec.ECGenParameterSpec import javax.crypto.Cipher import javax.crypto.KeyAgreement import javax.crypto.Mac @@ -327,24 +325,15 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor return null; } - val curveName = when (sharedSecretResponse.keyAgreement.curveId) { - 1 -> "secp256r1" - 2 -> "secp384r1" - 3 -> "secp521r1" - 4 -> "x25519" - 5 -> "x448" - 6 -> "Ed25519" - 7 -> "Ed448" - else -> return null - } + val algSpec = sharedSecretResponse.keyAgreement.getAlgSpec() ?: return null // Perform Diffie Hellman key generation - val generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC) - generator.initialize(ECGenParameterSpec(curveName)) + val generator = KeyPairGenerator.getInstance(algSpec.keyAlg) + generator.initialize(algSpec.paramSpec) val myKeyPair = generator.generateKeyPair() val serverKey = sharedSecretResponse.keyAgreement.asCryptoKey() - val keyAgreement = KeyAgreement.getInstance("ECDH") + val keyAgreement = KeyAgreement.getInstance(algSpec.agreementAlg) keyAgreement.init(myKeyPair.private) keyAgreement.doPhase(serverKey, true)