Fido: Fix credential assertion with Chromium-based browsers (add missing public key) (#3545)

* 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.

* 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

* Fix AlgSpec.XDH requiring SDK >= 33

Also use version ints instead of code names for consistency

---------

Co-authored-by: Marvin W <git@larma.de>
This commit is contained in:
S1m 2026-07-06 20:25:30 +02:00 committed by GitHub
parent 0b12b1c970
commit 65f04ed083
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 96 additions and 49 deletions

View file

@ -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 {

View file

@ -5,19 +5,22 @@
package org.microg.gms.fido.core.protocol
import android.os.Build.VERSION.SDK_INT
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,
@ -40,25 +43,95 @@ class CoseKey(
}
}
sealed class AlgSpec(val keyAlg: String, val agreementAlg: String, val paramSpec: AlgorithmParameterSpec) {
class EC(algName: String): AlgSpec(
"EC",
"ECDH",
ECGenParameterSpec(algName)
)
@RequiresApi(33)
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 (SDK_INT >= 33) {
// 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
if (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)
} else if (SDK_INT >= 33 && algSpec is AlgSpec.XDH) {
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
}

View file

@ -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)