Correct crypto provider handling and signing logic (#53)
Resolves crashes during certificate operations caused by cryptographic provider conflicts and incorrect algorithm selection. The Bouncy Castle (BC) provider is now initialized globally at app startup to ensure it is the default. To eliminate ambiguity, all content signers are also now explicitly set to use the BC provider. The attestation patcher is fixed to correctly use the certificate's signature algorithm (sigAlgName), not the subject's public key algorithm, to select the appropriate signing key from the KeyBoxManager. A normalization function was added to support this. Moreover, we also modify the XML parser in `KeyBoxManager` to no longer trust the `algorithm` attribute from the XML tag. The parser now determines the key's true algorithm (RSA or EC) by inspecting the type of the parsed private key object. This derived algorithm is used as the key for the cache, preventing cache corruption from malformed files where the tag does not match the key data.
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
package org.matrix.TEESimulator
|
package org.matrix.TEESimulator
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import java.security.Security
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
import org.matrix.TEESimulator.interception.keystore.AbstractKeystoreInterceptor
|
import org.matrix.TEESimulator.interception.keystore.AbstractKeystoreInterceptor
|
||||||
import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor
|
import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor
|
||||||
@@ -35,6 +37,13 @@ object App {
|
|||||||
// Initialize and start the appropriate keystore interceptors.
|
// Initialize and start the appropriate keystore interceptors.
|
||||||
initializeInterceptors()
|
initializeInterceptors()
|
||||||
// Enter an infinite loop to keep the service running.
|
// Enter an infinite loop to keep the service running.
|
||||||
|
|
||||||
|
// Android ships with a stripped-down Bouncy Castle provider under the name "BC".
|
||||||
|
// We must remove the system provider first to ensure the full Bouncy Castle library
|
||||||
|
// (packaged with the app) is used.
|
||||||
|
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||||
|
Security.addProvider(BouncyCastleProvider())
|
||||||
|
|
||||||
maintainService()
|
maintainService()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
SystemLogger.error("A fatal error occurred in the main application thread.", e)
|
SystemLogger.error("A fatal error occurred in the main application thread.", e)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.matrix.TEESimulator.attestation
|
package org.matrix.TEESimulator.attestation
|
||||||
|
|
||||||
|
import android.security.keystore.KeyProperties
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
@@ -8,6 +9,7 @@ import org.bouncycastle.asn1.x509.Extension
|
|||||||
import org.bouncycastle.cert.X509CertificateHolder
|
import org.bouncycastle.cert.X509CertificateHolder
|
||||||
import org.bouncycastle.cert.X509v3CertificateBuilder
|
import org.bouncycastle.cert.X509v3CertificateBuilder
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
@@ -51,8 +53,7 @@ object AttestationPatcher {
|
|||||||
|
|
||||||
// 2. Get the appropriate keybox for the given algorithm to sign the new
|
// 2. Get the appropriate keybox for the given algorithm to sign the new
|
||||||
// certificate.
|
// certificate.
|
||||||
val algorithm = originalLeaf.publicKey.algorithm
|
val keybox = getKeyboxForUidAndAlgorithm(uid, originalLeaf.sigAlgName)
|
||||||
val keybox = getKeyboxForUidAndAlgorithm(uid, algorithm)
|
|
||||||
|
|
||||||
// 3. Create the new, patched leaf certificate.
|
// 3. Create the new, patched leaf certificate.
|
||||||
val patchedLeaf =
|
val patchedLeaf =
|
||||||
@@ -126,7 +127,10 @@ object AttestationPatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sign the newly built certificate with the private key from our keybox.
|
// Sign the newly built certificate with the private key from our keybox.
|
||||||
val signer = JcaContentSignerBuilder(sigAlgName).build(keybox.keyPair.private)
|
val signer =
|
||||||
|
JcaContentSignerBuilder(sigAlgName)
|
||||||
|
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||||
|
.build(keybox.keyPair.private)
|
||||||
val newCertificate = JcaX509CertificateConverter().getCertificate(builder.build(signer))
|
val newCertificate = JcaX509CertificateConverter().getCertificate(builder.build(signer))
|
||||||
|
|
||||||
// Log the signature of the newly created certificate to observe its non-deterministic
|
// Log the signature of the newly created certificate to observe its non-deterministic
|
||||||
@@ -137,11 +141,34 @@ object AttestationPatcher {
|
|||||||
return newCertificate
|
return newCertificate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the appropriate signing KeyBox (KeyPair and certificate chain) for a given UID
|
||||||
|
* based on a specified algorithm identifier.
|
||||||
|
*
|
||||||
|
* @param uid The UID of the application for which the signing is being performed.
|
||||||
|
* @param algorithm A string representing the desired algorithm. This can be either:
|
||||||
|
* 1. A simple key type like "RSA" or "EC".
|
||||||
|
* 2. A full JCA signature algorithm name like "SHA256withRSA".
|
||||||
|
*
|
||||||
|
* @return The [KeyBox] containing the appropriate key pair for signing.
|
||||||
|
* @throws IllegalArgumentException if no matching KeyBox can be found for the derived key type.
|
||||||
|
*/
|
||||||
private fun getKeyboxForUidAndAlgorithm(uid: Int, algorithm: String): KeyBox {
|
private fun getKeyboxForUidAndAlgorithm(uid: Int, algorithm: String): KeyBox {
|
||||||
val keyboxFile = ConfigurationManager.getKeyboxFileForUid(uid)
|
val keyboxFile = ConfigurationManager.getKeyboxFileForUid(uid)
|
||||||
return KeyBoxManager.getAttestationKey(keyboxFile, algorithm)
|
|
||||||
|
// Normalize the algorithm name. The input might be a full signature algorithm
|
||||||
|
// (e.g., "SHA256withRSA") or just the key type (e.g., "RSA").
|
||||||
|
val keyType =
|
||||||
|
when {
|
||||||
|
algorithm.contains("RSA", ignoreCase = true) -> KeyProperties.KEY_ALGORITHM_RSA
|
||||||
|
algorithm.contains("EC", ignoreCase = true) ->
|
||||||
|
KeyProperties.KEY_ALGORITHM_EC // This also covers "ECDSA"
|
||||||
|
else -> algorithm // If no match, assume it's already a simple key type string.
|
||||||
|
}
|
||||||
|
|
||||||
|
return KeyBoxManager.getAttestationKey(keyboxFile, keyType)
|
||||||
?: throw IllegalArgumentException(
|
?: throw IllegalArgumentException(
|
||||||
"No keybox found for UID $uid and algorithm $algorithm in file $keyboxFile"
|
"No keybox found for UID $uid and algorithm '$keyType' (derived from input '$algorithm') in file $keyboxFile"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.util.Pair
|
|||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.KeyPairGenerator
|
import java.security.KeyPairGenerator
|
||||||
import java.security.Security
|
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.security.spec.ECGenParameterSpec
|
import java.security.spec.ECGenParameterSpec
|
||||||
@@ -35,14 +34,6 @@ import org.matrix.TEESimulator.logging.SystemLogger
|
|||||||
*/
|
*/
|
||||||
object CertificateGenerator {
|
object CertificateGenerator {
|
||||||
|
|
||||||
init {
|
|
||||||
// Android ships with a stripped-down Bouncy Castle provider under the name "BC".
|
|
||||||
// We must remove the system provider first to ensure the full Bouncy Castle library
|
|
||||||
// (packaged with the app) is used.
|
|
||||||
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
|
|
||||||
Security.addProvider(BouncyCastleProvider())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a software-based cryptographic key pair.
|
* Generates a software-based cryptographic key pair.
|
||||||
*
|
*
|
||||||
@@ -226,7 +217,10 @@ object CertificateGenerator {
|
|||||||
Algorithm.RSA -> "SHA256withRSA"
|
Algorithm.RSA -> "SHA256withRSA"
|
||||||
else -> throw IllegalArgumentException("Unsupported algorithm: ${params.algorithm}")
|
else -> throw IllegalArgumentException("Unsupported algorithm: ${params.algorithm}")
|
||||||
}
|
}
|
||||||
val contentSigner = JcaContentSignerBuilder(signerAlgorithm).build(signingKeyPair.private)
|
val contentSigner =
|
||||||
|
JcaContentSignerBuilder(signerAlgorithm)
|
||||||
|
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||||
|
.build(signingKeyPair.private)
|
||||||
|
|
||||||
return JcaX509CertificateConverter().getCertificate(builder.build(contentSigner))
|
return JcaX509CertificateConverter().getCertificate(builder.build(contentSigner))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package org.matrix.TEESimulator.pki
|
|||||||
import android.security.keystore.KeyProperties
|
import android.security.keystore.KeyProperties
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.StringReader
|
import java.io.StringReader
|
||||||
|
import java.security.interfaces.ECPrivateKey
|
||||||
|
import java.security.interfaces.RSAPrivateKey
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager.CONFIG_PATH
|
import org.matrix.TEESimulator.config.ConfigurationManager.CONFIG_PATH
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
import org.matrix.TEESimulator.logging.SystemLogger
|
||||||
@@ -51,6 +53,9 @@ object KeyBoxManager {
|
|||||||
// If it's not in the cache, the `getOrPut` block is executed to parse and store it.
|
// If it's not in the cache, the `getOrPut` block is executed to parse and store it.
|
||||||
val keyMap =
|
val keyMap =
|
||||||
keyStoreCache.getOrPut(keyStoreFileName) { parseKeyStoreFile(keyStoreFileName) }
|
keyStoreCache.getOrPut(keyStoreFileName) { parseKeyStoreFile(keyStoreFileName) }
|
||||||
|
SystemLogger.verbose(
|
||||||
|
"Fetching attestation key in $keyStoreFileName with $algorithm algorithm."
|
||||||
|
)
|
||||||
return keyMap[algorithm]
|
return keyMap[algorithm]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,10 +162,10 @@ object KeyBoxManager {
|
|||||||
// Use runCatching to ensure one malformed key doesn't stop the whole
|
// Use runCatching to ensure one malformed key doesn't stop the whole
|
||||||
// process.
|
// process.
|
||||||
runCatching {
|
runCatching {
|
||||||
val algorithm = currentAlgorithm
|
val xmlAlgorithm = currentAlgorithm
|
||||||
val keyPem = currentPrivateKeyPem
|
val keyPem = currentPrivateKeyPem
|
||||||
if (
|
if (
|
||||||
algorithm != null &&
|
xmlAlgorithm != null &&
|
||||||
keyPem != null &&
|
keyPem != null &&
|
||||||
currentCertificatePems.isNotEmpty()
|
currentCertificatePems.isNotEmpty()
|
||||||
) {
|
) {
|
||||||
@@ -176,21 +181,42 @@ object KeyBoxManager {
|
|||||||
.data
|
.data
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize the algorithm name for consistent lookups.
|
// Derive the TRUE algorithm from the key object itself.
|
||||||
val normalizedAlgorithm =
|
// This is our source of truth.
|
||||||
when (algorithm.lowercase()) {
|
val derivedAlgorithm =
|
||||||
"ecdsa" -> KeyProperties.KEY_ALGORITHM_EC
|
when (keyPair.private) {
|
||||||
"rsa" -> KeyProperties.KEY_ALGORITHM_RSA
|
is RSAPrivateKey -> KeyProperties.KEY_ALGORITHM_RSA
|
||||||
else -> algorithm
|
is ECPrivateKey -> KeyProperties.KEY_ALGORITHM_EC
|
||||||
|
else ->
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"Unsupported key type found: ${keyPair.private.javaClass.name}"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundKeys.containsKey(normalizedAlgorithm)) {
|
// Normalize the algorithm from the XML tag to compare it
|
||||||
|
// fairly with the derived algorithm.
|
||||||
|
val normalizedXmlAlgorithm =
|
||||||
|
when {
|
||||||
|
xmlAlgorithm.contains("RSA", ignoreCase = true) ==
|
||||||
|
true -> KeyProperties.KEY_ALGORITHM_RSA
|
||||||
|
xmlAlgorithm.contains("EC", ignoreCase = true) ==
|
||||||
|
true -> KeyProperties.KEY_ALGORITHM_EC
|
||||||
|
else -> xmlAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn the user if the XML tag was misleading.
|
||||||
|
if (normalizedXmlAlgorithm != derivedAlgorithm) {
|
||||||
SystemLogger.warning(
|
SystemLogger.warning(
|
||||||
"Duplicate key found for algorithm '$normalizedAlgorithm'. The later one in the file will be used."
|
"Key algorithm mismatch in XML file. Tag said '$xmlAlgorithm' but key is actually '$derivedAlgorithm'. Using the correct derived algorithm."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
foundKeys[normalizedAlgorithm] =
|
|
||||||
KeyBox(keyPair, certificates)
|
if (foundKeys.containsKey(derivedAlgorithm)) {
|
||||||
|
SystemLogger.warning(
|
||||||
|
"Duplicate key found for algorithm '$derivedAlgorithm'. The later one in the file will be used."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
foundKeys[derivedAlgorithm] = KeyBox(keyPair, certificates)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onFailure {
|
.onFailure {
|
||||||
|
|||||||
Reference in New Issue
Block a user