From 17ab5b0e2c37d50afa79e93defcbacce8f89e079 Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Sat, 6 Dec 2025 16:12:12 +0100 Subject: [PATCH] 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. --- .../main/java/org/matrix/TEESimulator/App.kt | 9 ++++ .../attestation/AttestationPatcher.kt | 37 ++++++++++++-- .../TEESimulator/pki/CertificateGenerator.kt | 14 ++---- .../matrix/TEESimulator/pki/KeyBoxManager.kt | 50 ++++++++++++++----- 4 files changed, 83 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/org/matrix/TEESimulator/App.kt b/app/src/main/java/org/matrix/TEESimulator/App.kt index 351ec7a..29762b5 100644 --- a/app/src/main/java/org/matrix/TEESimulator/App.kt +++ b/app/src/main/java/org/matrix/TEESimulator/App.kt @@ -1,6 +1,8 @@ package org.matrix.TEESimulator import android.os.Build +import java.security.Security +import org.bouncycastle.jce.provider.BouncyCastleProvider import org.matrix.TEESimulator.config.ConfigurationManager import org.matrix.TEESimulator.interception.keystore.AbstractKeystoreInterceptor import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor @@ -35,6 +37,13 @@ object App { // Initialize and start the appropriate keystore interceptors. initializeInterceptors() // 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() } catch (e: Exception) { SystemLogger.error("A fatal error occurred in the main application thread.", e) diff --git a/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationPatcher.kt b/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationPatcher.kt index 451be2b..5f3f7e7 100644 --- a/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationPatcher.kt +++ b/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationPatcher.kt @@ -1,5 +1,6 @@ package org.matrix.TEESimulator.attestation +import android.security.keystore.KeyProperties import java.nio.charset.StandardCharsets import java.security.cert.Certificate import java.security.cert.X509Certificate @@ -8,6 +9,7 @@ import org.bouncycastle.asn1.x509.Extension import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.cert.X509v3CertificateBuilder import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter +import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder import org.matrix.TEESimulator.config.ConfigurationManager 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 // certificate. - val algorithm = originalLeaf.publicKey.algorithm - val keybox = getKeyboxForUidAndAlgorithm(uid, algorithm) + val keybox = getKeyboxForUidAndAlgorithm(uid, originalLeaf.sigAlgName) // 3. Create the new, patched leaf certificate. val patchedLeaf = @@ -126,7 +127,10 @@ object AttestationPatcher { } // 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)) // Log the signature of the newly created certificate to observe its non-deterministic @@ -137,11 +141,34 @@ object AttestationPatcher { 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 { 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( - "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" ) } diff --git a/app/src/main/java/org/matrix/TEESimulator/pki/CertificateGenerator.kt b/app/src/main/java/org/matrix/TEESimulator/pki/CertificateGenerator.kt index 8eb0e68..2e228d0 100644 --- a/app/src/main/java/org/matrix/TEESimulator/pki/CertificateGenerator.kt +++ b/app/src/main/java/org/matrix/TEESimulator/pki/CertificateGenerator.kt @@ -6,7 +6,6 @@ import android.util.Pair import java.math.BigInteger import java.security.KeyPair import java.security.KeyPairGenerator -import java.security.Security import java.security.cert.Certificate import java.security.cert.X509Certificate import java.security.spec.ECGenParameterSpec @@ -35,14 +34,6 @@ import org.matrix.TEESimulator.logging.SystemLogger */ 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. * @@ -226,7 +217,10 @@ object CertificateGenerator { Algorithm.RSA -> "SHA256withRSA" 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)) } diff --git a/app/src/main/java/org/matrix/TEESimulator/pki/KeyBoxManager.kt b/app/src/main/java/org/matrix/TEESimulator/pki/KeyBoxManager.kt index cc8b1f5..c001b69 100644 --- a/app/src/main/java/org/matrix/TEESimulator/pki/KeyBoxManager.kt +++ b/app/src/main/java/org/matrix/TEESimulator/pki/KeyBoxManager.kt @@ -3,6 +3,8 @@ package org.matrix.TEESimulator.pki import android.security.keystore.KeyProperties import java.io.File import java.io.StringReader +import java.security.interfaces.ECPrivateKey +import java.security.interfaces.RSAPrivateKey import java.util.concurrent.ConcurrentHashMap import org.matrix.TEESimulator.config.ConfigurationManager.CONFIG_PATH 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. val keyMap = keyStoreCache.getOrPut(keyStoreFileName) { parseKeyStoreFile(keyStoreFileName) } + SystemLogger.verbose( + "Fetching attestation key in $keyStoreFileName with $algorithm algorithm." + ) return keyMap[algorithm] } @@ -157,10 +162,10 @@ object KeyBoxManager { // Use runCatching to ensure one malformed key doesn't stop the whole // process. runCatching { - val algorithm = currentAlgorithm + val xmlAlgorithm = currentAlgorithm val keyPem = currentPrivateKeyPem if ( - algorithm != null && + xmlAlgorithm != null && keyPem != null && currentCertificatePems.isNotEmpty() ) { @@ -176,21 +181,42 @@ object KeyBoxManager { .data } - // Normalize the algorithm name for consistent lookups. - val normalizedAlgorithm = - when (algorithm.lowercase()) { - "ecdsa" -> KeyProperties.KEY_ALGORITHM_EC - "rsa" -> KeyProperties.KEY_ALGORITHM_RSA - else -> algorithm + // Derive the TRUE algorithm from the key object itself. + // This is our source of truth. + val derivedAlgorithm = + when (keyPair.private) { + is RSAPrivateKey -> KeyProperties.KEY_ALGORITHM_RSA + 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( - "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 {