Skip to content

Commit 65f04ed

Browse files
p1gp1gmar-v-in
andauthored
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>
1 parent 0b12b1c commit 65f04ed

3 files changed

Lines changed: 96 additions & 49 deletions

File tree

play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/protocol/Cbor.kt

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -65,24 +65,9 @@ fun CBORObject.decodeAsPublicKeyCredentialUserEntity() = PublicKeyCredentialUser
6565
)
6666

6767
fun getAlgorithm(algorithmInt: Int): Algorithm {
68-
return when (algorithmInt) {
69-
-65535 -> RSAAlgorithm.RS1
70-
-262 -> RSAAlgorithm.LEGACY_RS1
71-
-261 -> EC2Algorithm.ED512
72-
-260 -> EC2Algorithm.ED256
73-
-259 -> RSAAlgorithm.RS512
74-
-258 -> RSAAlgorithm.RS384
75-
-257 -> RSAAlgorithm.RS256
76-
-39 -> RSAAlgorithm.PS512
77-
-38 -> RSAAlgorithm.PS384
78-
-37 -> RSAAlgorithm.PS256
79-
-36 -> EC2Algorithm.ES512
80-
-35 -> EC2Algorithm.ES384
81-
-25 -> EC2Algorithm.ECDH_HKDF_256
82-
-7 -> EC2Algorithm.ES256
83-
84-
else -> Algorithm { algorithmInt }
85-
}
68+
return EC2Algorithm.entries.firstOrNull { it.algoValue == algorithmInt }
69+
?: RSAAlgorithm.entries.firstOrNull { it.algoValue == algorithmInt }
70+
?: Algorithm { algorithmInt }
8671
}
8772

8873
fun PublicKeyCredentialParameters.encodeAsCbor() = CBORObject.NewMap().apply {

play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/protocol/CoseKey.kt

Lines changed: 89 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,22 @@
55

66
package org.microg.gms.fido.core.protocol
77

8+
import android.os.Build.VERSION.SDK_INT
9+
import androidx.annotation.RequiresApi
810
import com.google.android.gms.fido.fido2.api.common.Algorithm
911
import com.google.android.gms.fido.fido2.api.common.EC2Algorithm
10-
import com.google.android.gms.fido.fido2.api.common.RSAAlgorithm
1112
import com.upokecenter.cbor.CBOREncodeOptions
1213
import com.upokecenter.cbor.CBORObject
1314
import java.math.BigInteger
1415
import java.security.AlgorithmParameters
1516
import java.security.KeyFactory
1617
import java.security.PublicKey
18+
import java.security.spec.AlgorithmParameterSpec
1719
import java.security.spec.ECGenParameterSpec
1820
import java.security.spec.ECParameterSpec
1921
import java.security.spec.ECPoint
2022
import java.security.spec.ECPublicKeySpec
23+
import java.security.spec.NamedParameterSpec
2124

2225
class CoseKey(
2326
val algorithm: Algorithm,
@@ -40,25 +43,95 @@ class CoseKey(
4043
}
4144
}
4245

46+
sealed class AlgSpec(val keyAlg: String, val agreementAlg: String, val paramSpec: AlgorithmParameterSpec) {
47+
class EC(algName: String): AlgSpec(
48+
"EC",
49+
"ECDH",
50+
ECGenParameterSpec(algName)
51+
)
52+
@RequiresApi(33)
53+
class XDH(algName: String, private val oid: ByteArray, private val keyLength: Int): AlgSpec(
54+
algName,
55+
"XDH",
56+
NamedParameterSpec(algName)
57+
) {
58+
/**
59+
* Works for key+preamble smaller than 256 bytes (x25519 is 32 + preamble = 42)
60+
*/
61+
val x509Preamble: ByteArray
62+
get() {
63+
require(keyLength + oid.size + 5 < 0x100)
64+
val header = byteArrayOf(
65+
0x30, oid.size.toByte() // Sequence of OID.size bytes
66+
) + oid + byteArrayOf(
67+
0x03, (keyLength + 1).toByte(), 0x00 // Bit string of keyLength +1 + 0x00 beginning of the key
68+
)
69+
return byteArrayOf(
70+
0x30, (header.size + keyLength).toByte(), // Sequence of header + keylength bytes
71+
) + header
72+
}
73+
}
74+
}
75+
76+
fun getAlgSpec(): AlgSpec? {
77+
return if (SDK_INT >= 33) {
78+
// cf. https://www.iana.org/assignments/smi-numbers/smi-numbers.xhtml#smi-numbers-1.3.101
79+
// for OID
80+
when (curveId) {
81+
1 -> AlgSpec.EC("secp256r1")
82+
2 -> AlgSpec.EC("secp384r1")
83+
3 -> AlgSpec.EC("secp521r1")
84+
4 -> AlgSpec.XDH(
85+
"x25519",
86+
byteArrayOf(0x06, 0x03, 0x2b, 0x65, 0x6e), // OID: 1.3.101.110 (X25519)
87+
32
88+
)
89+
5 -> AlgSpec.XDH(
90+
"x448",
91+
byteArrayOf(0x06, 0x03, 0x2b, 0x65, 0x6f), // OID: 1.3.101.111 (X448)
92+
56
93+
)
94+
6 -> AlgSpec.XDH(
95+
"Ed25519",
96+
byteArrayOf(0x06, 0x03, 0x2b, 0x65, 0x70), // OID: 1.3.101.112 (ED25519)
97+
32
98+
)
99+
7 -> AlgSpec.XDH(
100+
"Ed448",
101+
byteArrayOf(0x06, 0x03, 0x2b, 0x65, 0x77), // OID: 1.3.101.113 (ED448)
102+
56
103+
)
104+
else -> null
105+
}
106+
} else {
107+
when (curveId) {
108+
1 -> AlgSpec.EC("secp256r1")
109+
2 -> AlgSpec.EC("secp384r1")
110+
3 -> AlgSpec.EC("secp521r1")
111+
else -> null
112+
}
113+
}
114+
}
115+
43116
fun asCryptoKey(): PublicKey? {
44117
return when(algorithm) {
45118
is EC2Algorithm -> {
46-
val curveName = when (curveId) {
47-
1 -> "secp256r1"
48-
2 -> "secp384r1"
49-
3 -> "secp521r1"
50-
4 -> "x25519"
51-
5 -> "x448"
52-
6 -> "Ed25519"
53-
7 -> "Ed448"
54-
else -> return null
119+
val algSpec = getAlgSpec() ?: return null
120+
if (algSpec is AlgSpec.EC) {
121+
val parameters = AlgorithmParameters.getInstance("EC")
122+
parameters.init(algSpec.paramSpec)
123+
val parameterSpec = parameters.getParameterSpec(ECParameterSpec::class.java)
124+
val keySpec = ECPublicKeySpec(ECPoint(BigInteger(1, x), BigInteger(1, y)), parameterSpec)
125+
KeyFactory.getInstance("EC").generatePublic(keySpec)
126+
} else if (SDK_INT >= 33 && algSpec is AlgSpec.XDH) {
127+
object : PublicKey {
128+
override fun getAlgorithm(): String = algSpec.keyAlg
129+
override fun getFormat(): String = "x.509"
130+
override fun getEncoded(): ByteArray = algSpec.x509Preamble + x
131+
}
132+
} else {
133+
null
55134
}
56-
57-
val parameters = AlgorithmParameters.getInstance("EC")
58-
parameters.init(ECGenParameterSpec(curveName))
59-
val parameterSpec = parameters.getParameterSpec(ECParameterSpec::class.java)
60-
val keySpec = ECPublicKeySpec(ECPoint(BigInteger(1, x), BigInteger(1, y)), parameterSpec)
61-
KeyFactory.getInstance("EC").generatePublic(keySpec)
62135
}
63136
else -> null
64137
}

play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/TransportHandler.kt

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ package org.microg.gms.fido.core.transport
88
import android.content.Context
99
import android.os.Build.VERSION.SDK_INT
1010
import android.os.Bundle
11-
import android.security.keystore.KeyProperties
1211
import android.util.Log
1312
import androidx.annotation.RequiresApi
1413
import com.google.android.gms.fido.fido2.api.common.*
@@ -26,7 +25,6 @@ import java.nio.charset.StandardCharsets
2625
import java.security.KeyPairGenerator
2726
import java.security.MessageDigest
2827
import java.security.interfaces.ECPublicKey
29-
import java.security.spec.ECGenParameterSpec
3028
import javax.crypto.Cipher
3129
import javax.crypto.KeyAgreement
3230
import javax.crypto.Mac
@@ -327,24 +325,15 @@ abstract class TransportHandler(val transport: Transport, val callback: Transpor
327325
return null;
328326
}
329327

330-
val curveName = when (sharedSecretResponse.keyAgreement.curveId) {
331-
1 -> "secp256r1"
332-
2 -> "secp384r1"
333-
3 -> "secp521r1"
334-
4 -> "x25519"
335-
5 -> "x448"
336-
6 -> "Ed25519"
337-
7 -> "Ed448"
338-
else -> return null
339-
}
328+
val algSpec = sharedSecretResponse.keyAgreement.getAlgSpec() ?: return null
340329

341330
// Perform Diffie Hellman key generation
342-
val generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC)
343-
generator.initialize(ECGenParameterSpec(curveName))
331+
val generator = KeyPairGenerator.getInstance(algSpec.keyAlg)
332+
generator.initialize(algSpec.paramSpec)
344333

345334
val myKeyPair = generator.generateKeyPair()
346335
val serverKey = sharedSecretResponse.keyAgreement.asCryptoKey()
347-
val keyAgreement = KeyAgreement.getInstance("ECDH")
336+
val keyAgreement = KeyAgreement.getInstance(algSpec.agreementAlg)
348337
keyAgreement.init(myKeyPair.private)
349338
keyAgreement.doPhase(serverKey, true)
350339

0 commit comments

Comments
 (0)