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 {