diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt index b9c8530..a8405c0 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt @@ -314,6 +314,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { val keyId = KeyIdentifier(callingUid, keyDescriptor.alias) if (userUpdatedKeys.remove(keyId)) { + SystemLogger.trace { "[TRACE-$txId] getKeyEntry $keyId: userUpdated=true, skipping patch" } SystemLogger.debug("[TX_ID: $txId] Skipping cert patch for user-updated key $keyId.") return TransactionResult.SkipTransaction } @@ -324,12 +325,16 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { authorizations?.map { it.keyParameter }?.toTypedArray() ?: emptyArray() ) + SystemLogger.trace { "[TRACE-$txId] getKeyEntry $keyId: isImport=${parsedParameters.isImportKey()} origin=${parsedParameters.origin} inImportedKeys=${KeyMintSecurityLevelInterceptor.importedKeys.contains(keyId)} hasPatchedChain=${KeyMintSecurityLevelInterceptor.getPatchedChain(keyId) != null} isAttestKey=${parsedParameters.isAttestKey()}" } + if (parsedParameters.isImportKey()) { val retainedChain = KeyMintSecurityLevelInterceptor.getPatchedChain(keyId) if (retainedChain == null) { + SystemLogger.trace { "[TRACE-$txId] getKeyEntry $keyId: imported, no retained chain, skip" } SystemLogger.info("[TX_ID: $txId] Skip patching for imported key (no prior attestation).") return TransactionResult.SkipTransaction } + SystemLogger.trace { "[TRACE-$txId] getKeyEntry $keyId: imported, SERVING RETAINED CHAIN (detection vector!)" } SystemLogger.info("[TX_ID: $txId] Imported key overwrote attested alias, serving retained chain for $keyId") CertificateHelper.updateCertificateChain(response.metadata, retainedChain).getOrThrow() response.metadata.authorizations = @@ -341,6 +346,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { } if (KeyMintSecurityLevelInterceptor.importedKeys.contains(keyId)) { + SystemLogger.trace { "[TRACE-$txId] getKeyEntry $keyId: in importedKeys set, skip" } SystemLogger.debug("[TX_ID: $txId] Skipping attest-key override for imported key $keyId") return TransactionResult.SkipTransaction } @@ -464,7 +470,11 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { } if (generatedKeyInfo == null) { - descriptor.alias?.let { userUpdatedKeys.add(KeyIdentifier(callingUid, it)) } + descriptor.alias?.let { + val kid = KeyIdentifier(callingUid, it) + userUpdatedKeys.add(kid) + SystemLogger.trace { "[TRACE] updateSubcomponent $kid: not generated key, added to userUpdatedKeys" } + } return TransactionResult.ContinueAndSkipPost } diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt index bcc620c..682b47c 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt @@ -142,12 +142,14 @@ class KeyMintSecurityLevelInterceptor( } attestationKeys.remove(keyId) importedKeys.add(keyId) + SystemLogger.trace { "[TRACE-$txId] post-importKey $keyId: added to importedKeys, skipUid=${ConfigurationManager.shouldSkipUid(callingUid)}" } if (!ConfigurationManager.shouldSkipUid(callingUid)) { val metadata: KeyMetadata = reply.readTypedObject(KeyMetadata.CREATOR) ?: return TransactionResult.SkipTransaction val originalChain = CertificateHelper.getCertificateChain(metadata) + SystemLogger.trace { "[TRACE-$txId] post-importKey $keyId: chainSize=${originalChain?.size ?: 0}" } if (originalChain != null && originalChain.size > 1) { val newChain = AttestationPatcher.patchCertificateChain(originalChain, callingUid) CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow() @@ -158,6 +160,7 @@ class KeyMintSecurityLevelInterceptor( this.metadata = metadata iSecurityLevel = original } + SystemLogger.trace { "[TRACE-$txId] post-importKey $keyId: PATCHED chain (chainSize=${newChain.size})" } SystemLogger.debug("Cached patched certificate chain for imported key $keyId.") return InterceptorUtils.createTypedObjectReply(metadata) } @@ -420,6 +423,11 @@ class KeyMintSecurityLevelInterceptor( val params = data.createTypedArray(KeyParameter.CREATOR)!! val parsedParams = KeyMintAttestation(params) + SystemLogger.trace { "[TRACE-$txId] generateKey alias=${keyDescriptor.alias} algo=${parsedParams.algorithm} challenge=${parsedParams.attestationChallenge?.size ?: "null"} serial=${parsedParams.serial != null} imei=${parsedParams.imei != null} noAuth=${parsedParams.noAuthRequired} purposes=${parsedParams.purpose}" } + if (SystemLogger.isDebugBuild) params.forEach { p -> + SystemLogger.trace { "[TRACE-$txId] tag=${p.tag} value=${p.value}" } + } + val challenge = parsedParams.attestationChallenge if (challenge != null && challenge.size > AttestationConstants.CHALLENGE_LENGTH_LIMIT) { SystemLogger.warning("[TX_ID: $txId] Rejecting oversized attestation challenge: ${challenge.size} bytes (max ${AttestationConstants.CHALLENGE_LENGTH_LIMIT})") @@ -487,6 +495,8 @@ class KeyMintSecurityLevelInterceptor( if (isAuto) SystemLogger.debug("AUTO dispatch: teePathDecision=${teePathDecision.get()} for ${keyDescriptor.alias}") + SystemLogger.trace { "[TRACE-$txId] dispatch: forceGen=$forceGenerate isAuto=$isAuto teePath=${teePathDecision.get()} hasChallenge=${challenge != null} isSymmetric=$isSymmetric isAttestKey=$isAttestKeyRequest" } + when { forceGenerate -> doSoftwareKeyGen(callingUid, keyDescriptor, attestationKey, parsedParams, keyId, isAttestKeyRequest) isAuto && teePathDecision.get() == null -> raceTeePatch(callingUid, keyDescriptor, attestationKey, params, parsedParams, keyId, isAttestKeyRequest) @@ -578,6 +588,17 @@ class KeyMintSecurityLevelInterceptor( generatedKeys[keyId] = GeneratedKeyInfo(keyData.first, null, keyDescriptor.nspace, response, parsedParams) if (isAttestKeyRequest) attestationKeys.add(keyId) + if (SystemLogger.isDebugBuild) { + val chain = keyData.second + val leaf = chain.firstOrNull() as? java.security.cert.X509Certificate + SystemLogger.trace { + "[certchain] ${keyDescriptor.alias}: depth=${chain.size} " + + "issuer=${leaf?.issuerX500Principal?.name} " + + "subject=${leaf?.subjectX500Principal?.name} " + + "hasAttest=${leaf?.getExtensionValue("1.3.6.1.4.1.11129.2.1.17") != null}" + } + } + val certChainCopy = keyData.second.toList() persistExecutor.execute { GeneratedKeyPersistence.save( diff --git a/app/src/main/java/org/matrix/TEESimulator/logging/SystemLogger.kt b/app/src/main/java/org/matrix/TEESimulator/logging/SystemLogger.kt index 9a8c35e..109a9e7 100644 --- a/app/src/main/java/org/matrix/TEESimulator/logging/SystemLogger.kt +++ b/app/src/main/java/org/matrix/TEESimulator/logging/SystemLogger.kt @@ -117,4 +117,9 @@ object SystemLogger { if (!acquireLogPermit()) return Log.v(TAG, message()) } + + inline fun trace(message: () -> String) { + if (!isDebugBuild) return + Log.w(TAG, message()) + } } 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 6b25b31..9eb92d7 100644 --- a/app/src/main/java/org/matrix/TEESimulator/pki/CertificateGenerator.kt +++ b/app/src/main/java/org/matrix/TEESimulator/pki/CertificateGenerator.kt @@ -93,6 +93,12 @@ object CertificateGenerator { ) return try { + // AOSP ta/src/keys.rs:451-478: no challenge + no attestKey = self-signed, depth 1 + if (challenge == null && attestKeyAlias == null) { + SystemLogger.trace { "[certgen] no-challenge key: self-signed, depth=1, purposes=${params.purpose}" } + return listOf(buildSelfSignedCertificate(subjectKeyPair, params)) + } + val keybox = getKeyboxForAlgorithm(uid, params.algorithm) val (signingKey, issuer) = @@ -257,4 +263,39 @@ object CertificateGenerator { return JcaX509CertificateConverter().getCertificate(builder.build(contentSigner)) } + + // AOSP ta/src/keys.rs:452-478, ta/src/cert.rs:111-114 + private fun buildSelfSignedCertificate( + keyPair: KeyPair, + params: KeyMintAttestation, + ): Certificate { + val subject = params.certificateSubject ?: X500Name("CN=Android Keystore Key") + val notBefore = params.certificateNotBefore ?: Date(0) + val notAfter = params.certificateNotAfter ?: Date(UNDEFINED_NOT_AFTER) + + val builder = JcaX509v3CertificateBuilder( + subject, + params.certificateSerial ?: BigInteger.ONE, + notBefore, + notAfter, + subject, + keyPair.public, + ) + + val keyUsageBits = buildKeyUsageFromPurposes(params.purpose) + if (keyUsageBits != 0) { + builder.addExtension(Extension.keyUsage, true, KeyUsage(keyUsageBits)) + } + + val signerAlgorithm = when (keyPair.private.algorithm) { + "EC", "ECDSA" -> "SHA256withECDSA" + "RSA" -> "SHA256withRSA" + else -> throw IllegalArgumentException("Unsupported key: ${keyPair.private.algorithm}") + } + val contentSigner = JcaContentSignerBuilder(signerAlgorithm) + .setProvider(BouncyCastleProvider.PROVIDER_NAME) + .build(keyPair.private) + + return JcaX509CertificateConverter().getCertificate(builder.build(contentSigner)) + } } diff --git a/native-certgen/src/certbuilder.rs b/native-certgen/src/certbuilder.rs index e0c77fc..98d7c69 100644 --- a/native-certgen/src/certbuilder.rs +++ b/native-certgen/src/certbuilder.rs @@ -14,6 +14,66 @@ const OID_SHA256_WITH_RSA: &[u64] = &[1, 2, 840, 113549, 1, 1, 11]; // Extension OIDs const OID_KEY_USAGE: &[u64] = &[2, 5, 29, 15]; +// AOSP ta/src/keys.rs:451-478: no challenge = self-signed leaf, chain depth 1 +pub fn build_self_signed_cert( + key_pair: &GeneratedKeyPair, + params: &CertGenParams, +) -> Result>> { + let spki_der = extract_spki_from_pkcs8(&key_pair.private_key_pkcs8)?; + let sig_alg_der = signature_algorithm_for_signing_key(&key_pair.private_key_pkcs8, params.algorithm)?; + + let serial_bytes = if let Some(ref serial) = params.cert_serial { + serial.clone() + } else { + vec![1u8] + }; + + let subject_dn_der = if let Some(ref subject) = params.cert_subject { + subject.clone() + } else { + encode_simple_cn_dn("Android Keystore Key") + }; + + let not_before = timestamp_to_datetime(params.cert_not_before)?; + let not_after = if params.cert_not_after == -1 { + // No keybox fallback available; use far-future (year 9999) + OffsetDateTime::from_unix_timestamp(253402300799) + .unwrap_or_else(|_| OffsetDateTime::now_utc() + time::Duration::days(365 * 30)) + } else { + timestamp_to_datetime(params.cert_not_after)? + }; + + let extensions_der = build_extensions(None, ¶ms.purposes)?; + + let version_der = encode_der_explicit_tag(0, &encode_der_integer(&[2])); + let serial_der = encode_der_integer(&serial_bytes); + let validity_der = encode_validity(¬_before, ¬_after); + let extensions_tagged = encode_der_explicit_tag(3, &extensions_der); + + // issuer == subject (self-signed, per AOSP ta/src/cert.rs:111-114) + let tbs_der = encode_der_sequence(&[ + &version_der, + &serial_der, + &sig_alg_der, + &subject_dn_der, + &validity_der, + &subject_dn_der, + &spki_der, + &extensions_tagged, + ]); + + let signature_bytes = sign_tbs(&tbs_der, &key_pair.private_key_pkcs8, params.algorithm)?; + let signature_bit_string = encode_der_bit_string(&signature_bytes); + + let cert_der = encode_der_sequence(&[ + &tbs_der, + &sig_alg_der, + &signature_bit_string, + ]); + + Ok(vec![cert_der]) +} + pub fn build_certificate_chain( key_pair: &GeneratedKeyPair, attestation_ext_der: Option<&[u8]>, diff --git a/native-certgen/src/lib.rs b/native-certgen/src/lib.rs index c1275a9..fa88480 100644 --- a/native-certgen/src/lib.rs +++ b/native-certgen/src/lib.rs @@ -62,18 +62,14 @@ fn generate_attested_inner(env: &mut JNIEnv, config: &JObject) -> Result Some(attestation::build_attestation_extension(¶ms)?), - None => None, + let cert_chain = if params.attestation_challenge.is_some() { + let attest_ext = attestation::build_attestation_extension(¶ms)?; + certbuilder::build_certificate_chain(&key_pair, Some(&attest_ext), &keybox, ¶ms)? + } else { + tracing::info!("no attestation challenge, generating self-signed cert (depth 1)"); + certbuilder::build_self_signed_cert(&key_pair, ¶ms)? }; - let cert_chain = certbuilder::build_certificate_chain( - &key_pair, - attest_ext.as_deref(), - &keybox, - ¶ms, - )?; - let blob = assemble_result(&key_pair.private_key_pkcs8, &cert_chain); let out = env.byte_array_from_slice(&blob)?;