refactor(interception): reset Kotlin base to upstream PR #157

Our reimplementation of PR 157's logic had a silent divergence causing
G10 to still fail under binder stress. Instead of hunting line-by-line,
replace all shared Kotlin/Java/C++ files with PR 157's exact proven
versions that pass all 63 conformance tests. Our Rust-exclusive files
(NativeCertGen, GeneratedKeyPersistence, native-certgen crate) remain
in the repo but are dormant until re-wired in a follow-up commit.
This commit is contained in:
Enginex0
2026-03-20 05:41:15 +01:00
parent 8fdc59a142
commit 94c8e5b182
28 changed files with 966 additions and 1241 deletions
+4
View File
@@ -0,0 +1,4 @@
# Ensure shell scripts always have LF line endings, even on Windows.
# These get packaged into flashable zips and run on Android devices.
*.sh text eol=lf
module/daemon text eol=lf
+16 -46
View File
@@ -29,7 +29,7 @@ val gitExecutor = objects.newInstance(GitExecutor::class.java)
val gitCommitCount = gitExecutor.execute("git rev-list HEAD --count", rootDir).toInt() val gitCommitCount = gitExecutor.execute("git rev-list HEAD --count", rootDir).toInt()
val gitCommitHash = gitExecutor.execute("git rev-parse --verify --short HEAD", rootDir) val gitCommitHash = gitExecutor.execute("git rev-parse --verify --short HEAD", rootDir)
val verName = "v5.0" val verName = "v3.2"
android { android {
namespace = "org.matrix.TEESimulator" namespace = "org.matrix.TEESimulator"
@@ -71,35 +71,6 @@ dependencies {
implementation(libs.bcpkix) implementation(libs.bcpkix)
} }
// --- Rust native cert gen build task ---
val buildRustCertgen by tasks.registering(Exec::class) {
group = "TEESimulator-RS Native Build"
description = "Builds libcertgen.so via cargo-ndk for arm64-v8a."
workingDir = rootProject.projectDir.resolve("native-certgen")
commandLine(
"cargo", "ndk",
"-t", "arm64-v8a",
"-o", rootProject.projectDir.resolve("app/src/main/jniLibs").absolutePath,
"build", "--release"
)
inputs.dir(rootProject.projectDir.resolve("native-certgen/src"))
inputs.file(rootProject.projectDir.resolve("native-certgen/Cargo.toml"))
inputs.file(rootProject.projectDir.resolve("native-certgen/Cargo.lock"))
outputs.dir(rootProject.projectDir.resolve("app/src/main/jniLibs"))
environment("ANDROID_NDK_HOME", android.ndkDirectory.absolutePath)
}
// AGP auto-detects jniLibs/ as an input to mergeJniLibFolders — wire the dependency
tasks.configureEach {
if (name.endsWith("JniLibFolders") && name.startsWith("merge")) {
dependsOn(buildRustCertgen)
}
}
androidComponents { androidComponents {
onVariants(selector().all()) { variant -> onVariants(selector().all()) { variant ->
val capitalized = variant.name.replaceFirstChar { it.uppercase() } val capitalized = variant.name.replaceFirstChar { it.uppercase() }
@@ -108,22 +79,21 @@ androidComponents {
// --- Define output locations and file names --- // --- Define output locations and file names ---
// Stage all files in a temporary directory inside 'build' before zipping // Stage all files in a temporary directory inside 'build' before zipping
val tempModuleDir = project.layout.buildDirectory.dir("module/${variant.name}") val tempModuleDir = project.layout.buildDirectory.dir("module/${variant.name}")
val zipFileName = "TEESimulator-RS-$verName-$gitCommitCount-$capitalized.zip" val zipFileName = "TEESimulator-$verName-$gitCommitCount-$gitCommitHash-$capitalized.zip"
// Task 1: Prepare all module files in the temporary build directory. // Task 1: Prepare all module files in the temporary build directory.
// Using Sync ensures that stale files from previous runs are removed. // Using Sync ensures that stale files from previous runs are removed.
val prepareModuleFilesTask = val prepareModuleFilesTask =
tasks.register<Sync>("prepareModuleFiles${capitalized}") { tasks.register<Sync>("prepareModuleFiles${capitalized}") {
group = "TEESimulator-RS Module Packaging" group = "TEESimulator Module Packaging"
description = "Prepares all files for the ${variant.name} module zip." description = "Prepares all files for the ${variant.name} module zip."
if (isDebug) { if (isDebug) {
dependsOn("package${capitalized}") dependsOn("package${capitalized}")
} else { } else {
dependsOn("minify${capitalized}WithR8") dependsOn("minify${capitalized}WithR8")
dependsOn("strip${capitalized}DebugSymbols")
} }
dependsOn(buildRustCertgen) dependsOn("strip${capitalized}DebugSymbols")
if (isDebug) { if (isDebug) {
from(variant.artifacts.get(SingleArtifact.APK)) { from(variant.artifacts.get(SingleArtifact.APK)) {
@@ -140,14 +110,13 @@ androidComponents {
} }
} }
val nativeLibsDir = if (isDebug) { from(
"intermediates/merged_native_libs/${variant.name}/merge${capitalized}NativeLibs/out/lib" project.layout.buildDirectory.dir(
} else {
"intermediates/stripped_native_libs/${variant.name}/strip${capitalized}DebugSymbols/out/lib" "intermediates/stripped_native_libs/${variant.name}/strip${capitalized}DebugSymbols/out/lib"
} )
from(project.layout.buildDirectory.dir(nativeLibsDir)) { ) {
into("lib") into("lib") // Place them in the 'lib' subfolder of the staging directory.
include("**/libinject.so", "**/libTEESimulator.so", "**/libsupervisor.so", "**/libcertgen.so") include("**/libinject.so", "**/libTEESimulator.so")
} }
// Now, copy and process the files from 'module' directory. // Now, copy and process the files from 'module' directory.
@@ -162,7 +131,8 @@ androidComponents {
// Use expand() for simple key-value replacement. // Use expand() for simple key-value replacement.
expand( expand(
"REPLACEMEVERCODE" to gitCommitCount.toString(), "REPLACEMEVERCODE" to gitCommitCount.toString(),
"REPLACEMEVER" to "$verName-$gitCommitCount", "REPLACEMEVER" to
"$verName ($gitCommitCount-$gitCommitHash-${variant.name})",
) )
} }
@@ -173,7 +143,7 @@ androidComponents {
// Task 2: Zip the prepared files from the temporary directory. // Task 2: Zip the prepared files from the temporary directory.
val zipTask = val zipTask =
tasks.register<Zip>("zip${capitalized}") { tasks.register<Zip>("zip${capitalized}") {
group = "TEESimulator-RS Module Packaging" group = "TEESimulator Module Packaging"
description = "Creates the flashable zip for the ${variant.name} module." description = "Creates the flashable zip for the ${variant.name} module."
dependsOn(prepareModuleFilesTask) dependsOn(prepareModuleFilesTask)
@@ -186,7 +156,7 @@ androidComponents {
fun createInstallTasks(rootProvider: String, installCli: String) { fun createInstallTasks(rootProvider: String, installCli: String) {
val pushTask = val pushTask =
tasks.register<Exec>("push${rootProvider}Module${capitalized}") { tasks.register<Exec>("push${rootProvider}Module${capitalized}") {
group = "TEESimulator-RS Module Installation" group = "TEESimulator Module Installation"
description = description =
"Pushes the ${variant.name} module to the device for $rootProvider." "Pushes the ${variant.name} module to the device for $rootProvider."
dependsOn(zipTask) dependsOn(zipTask)
@@ -200,7 +170,7 @@ androidComponents {
val installTask = val installTask =
tasks.register<Exec>("install${rootProvider}${capitalized}") { tasks.register<Exec>("install${rootProvider}${capitalized}") {
group = "TEESimulator-RS Module Installation" group = "TEESimulator Module Installation"
description = "Installs the ${variant.name} module via $rootProvider." description = "Installs the ${variant.name} module via $rootProvider."
dependsOn(pushTask) dependsOn(pushTask)
commandLine( commandLine(
@@ -213,7 +183,7 @@ androidComponents {
} }
tasks.register<Exec>("install${rootProvider}AndReboot${capitalized}") { tasks.register<Exec>("install${rootProvider}AndReboot${capitalized}") {
group = "TEESimulator-RS Module Installation" group = "TEESimulator Module Installation"
description = "Installs the ${variant.name} module via $rootProvider and reboots." description = "Installs the ${variant.name} module via $rootProvider and reboots."
dependsOn(installTask) dependsOn(installTask)
commandLine("adb", "reboot") commandLine("adb", "reboot")
+17 -24
View File
@@ -235,15 +235,20 @@ class BinderInterceptor : public BBinder {
struct RegistrationEntry { struct RegistrationEntry {
wp<IBinder> target; wp<IBinder> target;
sp<IBinder> callback_interface; sp<IBinder> callback_interface;
// Transaction codes to intercept. Empty = intercept all (legacy behavior).
std::vector<uint32_t> filtered_codes; std::vector<uint32_t> filtered_codes;
}; };
// Reader-Writer lock for the registry to allow concurrent reads (lookups)
mutable std::shared_mutex registry_mutex_; mutable std::shared_mutex registry_mutex_;
std::map<wp<IBinder>, RegistrationEntry> registry_; std::map<wp<IBinder>, RegistrationEntry> registry_;
public: public:
BinderInterceptor() = default; BinderInterceptor() = default;
// Checks if a specific Binder+code combination should be intercepted.
// Returns true if the binder is registered AND the code is in its filter
// (or the filter is empty, meaning intercept everything).
bool shouldIntercept(const wp<BBinder> &target, uint32_t code) const { bool shouldIntercept(const wp<BBinder> &target, uint32_t code) const {
std::shared_lock lock(registry_mutex_); std::shared_lock lock(registry_mutex_);
auto it = registry_.find(target); auto it = registry_.find(target);
@@ -350,22 +355,15 @@ static sp<BinderStub> g_stub_instance = nullptr;
namespace { namespace {
constexpr binder_size_t kMaxInterceptableDataSize = 256 * 1024; /**
* @brief Analyses a binder transaction. If the target is monitored,
* hijacks the transaction by rewriting its destination to our BinderStub.
* @param txn_data Pointer to the transaction data within the ioctl buffer.
*/
void inspectAndRewriteTransaction(binder_transaction_data *txn_data) { void inspectAndRewriteTransaction(binder_transaction_data *txn_data) {
if (!txn_data || txn_data->target.ptr == 0) if (!txn_data || txn_data->target.ptr == 0)
return; return;
// Bypass interception for oversized payloads to prevent thread starvation from flood attacks
if (txn_data->data_size > kMaxInterceptableDataSize)
return;
// AIDL methods use codes in [FIRST_CALL_TRANSACTION, LAST_CALL_TRANSACTION] (1..0x00ffffff).
// System transactions (PING, INTERFACE, DUMP, SHELL_COMMAND) use codes above that range.
// Skip those — intercepting a ping adds measurable latency that timing detectors flag.
if (txn_data->code > 0x00ffffffu && txn_data->code != intercept::kBackdoorCode)
return;
bool hijack = false; bool hijack = false;
ThreadTransactionInfo info; ThreadTransactionInfo info;
@@ -540,11 +538,14 @@ status_t BinderInterceptor::handleRegister(const Parcel &data) {
if (data.readStrongBinder(&callback) != OK || !callback) if (data.readStrongBinder(&callback) != OK || !callback)
return BAD_VALUE; return BAD_VALUE;
// We can only intercept local Binders (BBinder), not remote proxies (BpBinder)
if (target->localBinder() == nullptr) { if (target->localBinder() == nullptr) {
LOGE("Cannot intercept remote binder proxies."); LOGE("Cannot intercept remote binder proxies.");
return BAD_TYPE; return BAD_TYPE;
} }
// Read optional transaction code filter. If present: int32 count + count * uint32 codes.
// If absent or count <= 0: intercept all transaction codes (legacy behavior).
std::vector<uint32_t> codes; std::vector<uint32_t> codes;
int32_t code_count = 0; int32_t code_count = 0;
if (data.dataAvail() >= sizeof(int32_t) && data.readInt32(&code_count) == OK && code_count > 0) { if (data.dataAvail() >= sizeof(int32_t) && data.readInt32(&code_count) == OK && code_count > 0) {
@@ -612,16 +613,9 @@ bool BinderInterceptor::processInterceptedTransaction(uint64_t tx_id, sp<BBinder
Parcel pre_req, pre_resp; Parcel pre_req, pre_resp;
writeTransactionData(pre_req, tx_id, target, code, flags, request); writeTransactionData(pre_req, tx_id, target, code, flags, request);
status_t pre_status = callback->transact(intercept::kPreTransact, pre_req, &pre_resp); if (callback->transact(intercept::kPreTransact, pre_req, &pre_resp) != OK) {
if (pre_status != OK) { LOGW("[TX_ID: %" PRIu64 "] Pre-transaction callback failed. Forwarding original call.", tx_id);
// Block when interceptor is dead to prevent privacy leak to third-party apps return false; // Callback failed, proceed as if not intercepted
if (callback->pingBinder() != OK) {
LOGE("[TX_ID: %" PRIu64 "] Interceptor DEAD. Blocking to prevent attestation leak.", tx_id);
result = DEAD_OBJECT;
return true;
}
LOGW("[TX_ID: %" PRIu64 "] Pre-transaction callback failed (not dead). Forwarding.", tx_id);
return false;
} }
int32_t action = pre_resp.readInt32(); int32_t action = pre_resp.readInt32();
@@ -674,8 +668,7 @@ bool BinderInterceptor::processInterceptedTransaction(uint64_t tx_id, sp<BBinder
VALIDATE_STATUS(tx_id, post_req.appendFrom(reply, 0, reply_size)); VALIDATE_STATUS(tx_id, post_req.appendFrom(reply, 0, reply_size));
} }
status_t post_status = callback->transact(intercept::kPostTransact, post_req, &post_resp); if (callback->transact(intercept::kPostTransact, post_req, &post_resp) == OK) {
if (post_status == OK) {
int32_t post_action = post_resp.readInt32(); int32_t post_action = post_resp.readInt32();
if (post_action == intercept::kActionOverrideReply && reply) { if (post_action == intercept::kActionOverrideReply && reply) {
result = post_resp.readInt32(); // Read new status result = post_resp.readInt32(); // Read new status
@@ -13,7 +13,6 @@ import org.matrix.TEESimulator.interception.keystore.AbstractKeystoreInterceptor
import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor
import org.matrix.TEESimulator.interception.keystore.KeystoreInterceptor import org.matrix.TEESimulator.interception.keystore.KeystoreInterceptor
import org.matrix.TEESimulator.logging.SystemLogger import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.pki.NativeCertGen
import org.matrix.TEESimulator.util.AndroidDeviceUtils import org.matrix.TEESimulator.util.AndroidDeviceUtils
/** /**
@@ -23,6 +22,8 @@ import org.matrix.TEESimulator.util.AndroidDeviceUtils
object App { object App {
// The delay in milliseconds before retrying to initialize the interceptor. // The delay in milliseconds before retrying to initialize the interceptor.
private const val RETRY_DELAY_MS = 1000L private const val RETRY_DELAY_MS = 1000L
// The sleep duration in milliseconds for the main service loop to keep the process alive.
private const val SERVICE_SLEEP_MS = 1000000L
/** /**
* The main entry point of the TEESimulator application. * The main entry point of the TEESimulator application.
@@ -33,18 +34,13 @@ object App {
fun main(args: Array<String>) { fun main(args: Array<String>) {
SystemLogger.info("Welcome to TEESimulator!") SystemLogger.info("Welcome to TEESimulator!")
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
SystemLogger.error("Uncaught exception on ${thread.name}", throwable)
}
try { try {
// Initialize the Android framework environment
prepareEnvironment() prepareEnvironment()
// Initialize and start the appropriate keystore interceptors. // Initialize and start the appropriate keystore interceptors.
initializeInterceptors() initializeInterceptors()
// Load the package configuration.
ConfigurationManager.initialize() ConfigurationManager.initialize()
// Set up the device's boot key and hash, which are crucial for attestation.
AndroidDeviceUtils.setupBootKeyAndHash() AndroidDeviceUtils.setupBootKeyAndHash()
// Android ships with a stripped-down Bouncy Castle provider under the name "BC". // Android ships with a stripped-down Bouncy Castle provider under the name "BC".
@@ -53,8 +49,6 @@ object App {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME) Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
Security.addProvider(BouncyCastleProvider()) Security.addProvider(BouncyCastleProvider())
NativeCertGen.initialize("/data/adb/modules/tricky_store/libcertgen.so")
// This starts the message queue processing. It blocks here indefinitely // This starts the message queue processing. It blocks here indefinitely
// processing messages until Looper.myLooper().quit() is called. // processing messages until Looper.myLooper().quit() is called.
Looper.loop() Looper.loop()
@@ -115,7 +115,6 @@ object AttestationBuilder {
} }
val bootPatch = AndroidDeviceUtils.getBootPatchLevelLong(uid) val bootPatch = AndroidDeviceUtils.getBootPatchLevelLong(uid)
SystemLogger.info("Attestation patch levels for uid=$uid: os=$osPatch, vendor=$vendorPatch, boot=$bootPatch")
properties[AttestationConstants.TAG_BOOT_PATCHLEVEL] = properties[AttestationConstants.TAG_BOOT_PATCHLEVEL] =
if (bootPatch != DO_NOT_REPORT) { if (bootPatch != DO_NOT_REPORT) {
DERTaggedObject( DERTaggedObject(
@@ -130,6 +129,7 @@ object AttestationBuilder {
return properties return properties
} }
/** Constructs the main `KeyDescription` sequence, which is the core of the attestation. */
private fun buildKeyDescription( private fun buildKeyDescription(
params: KeyMintAttestation, params: KeyMintAttestation,
uid: Int, uid: Int,
@@ -148,11 +148,15 @@ object AttestationBuilder {
val fields = val fields =
arrayOf( arrayOf(
ASN1Integer(AndroidDeviceUtils.getAttestVersion(securityLevel).toLong()), ASN1Integer(
ASN1Enumerated(securityLevel), AndroidDeviceUtils.getAttestVersion(securityLevel).toLong()
ASN1Integer(AndroidDeviceUtils.getKeymasterVersion(securityLevel).toLong()), ), // attestationVersion
ASN1Enumerated(securityLevel), ASN1Enumerated(securityLevel), // attestationSecurityLevel
DEROctetString(params.attestationChallenge ?: ByteArray(0)), ASN1Integer(
AndroidDeviceUtils.getKeymasterVersion(securityLevel).toLong()
), // keymasterVersion
ASN1Enumerated(securityLevel), // keymasterSecurityLevel
DEROctetString(params.attestationChallenge ?: ByteArray(0)), // attestationChallenge
DEROctetString(uniqueId), DEROctetString(uniqueId),
softwareEnforced, softwareEnforced,
teeEnforced, teeEnforced,
@@ -160,24 +164,37 @@ object AttestationBuilder {
return DERSequence(fields) return DERSequence(fields)
} }
/**
* Computes the unique ID per the KeyMint HAL spec:
* HMAC-SHA256(T || C || R, HBK) truncated to 128 bits.
*
* T = temporal counter (creationTime / 2592000000, i.e. 30-day periods since epoch)
* C = DER-encoded ATTESTATION_APPLICATION_ID
* R = 0x00 (no factory reset since ID rotation)
* HBK = device-unique secret generated once during module installation
*/
private fun computeUniqueId(creationTimeMs: Long, aaidDer: ByteArray): ByteArray { private fun computeUniqueId(creationTimeMs: Long, aaidDer: ByteArray): ByteArray {
val temporalCounter = creationTimeMs / 2592000000L val temporalCounter = creationTimeMs / 2592000000L
val message = val message =
ByteBuffer.allocate(8 + aaidDer.size + 1) ByteBuffer.allocate(8 + aaidDer.size + 1)
.putLong(temporalCounter) .putLong(temporalCounter)
.put(aaidDer) .put(aaidDer)
.put(0x00) .put(0x00) // RESET_SINCE_ID_ROTATION = false
.array() .array()
val mac = Mac.getInstance("HmacSHA256") val mac = Mac.getInstance("HmacSHA256")
mac.init(SecretKeySpec(hbk, "HmacSHA256")) mac.init(SecretKeySpec(hbk, "HmacSHA256"))
return mac.doFinal(message).copyOf(16) return mac.doFinal(message).copyOf(16)
} }
/** Device-unique key seed, generated once at module installation. */
private val hbk: ByteArray by lazy { private val hbk: ByteArray by lazy {
val file = java.io.File(ConfigurationManager.CONFIG_PATH, "hbk") val file = java.io.File(ConfigurationManager.CONFIG_PATH, "hbk")
if (file.exists() && file.length() == 32L) { if (file.exists() && file.length() == 32L) {
file.readBytes() file.readBytes()
} else { } else {
// Fallback: generate in-memory (won't persist across reboots)
SystemLogger.warning("hbk not found, generating ephemeral HBK.") SystemLogger.warning("hbk not found, generating ephemeral HBK.")
ByteArray(32).also { java.security.SecureRandom().nextBytes(it) } ByteArray(32).also { java.security.SecureRandom().nextBytes(it) }
} }
@@ -260,14 +277,20 @@ object AttestationBuilder {
DERTaggedObject( DERTaggedObject(
true, true,
AttestationConstants.TAG_RSA_OAEP_MGF_DIGEST, AttestationConstants.TAG_RSA_OAEP_MGF_DIGEST,
DERSet(params.rsaOaepMgfDigest.map { ASN1Integer(it.toLong()) }.toTypedArray()), DERSet(
params.rsaOaepMgfDigest.map { ASN1Integer(it.toLong()) }.toTypedArray()
),
) )
) )
} }
if (params.rollbackResistance == true && attestVersion >= 3) { if (params.rollbackResistance == true && attestVersion >= 3) {
list.add( list.add(
DERTaggedObject(true, AttestationConstants.TAG_ROLLBACK_RESISTANCE, DERNull.INSTANCE) DERTaggedObject(
true,
AttestationConstants.TAG_ROLLBACK_RESISTANCE,
DERNull.INSTANCE,
)
) )
} }
@@ -285,19 +308,31 @@ object AttestationBuilder {
if (params.allowWhileOnBody == true) { if (params.allowWhileOnBody == true) {
list.add( list.add(
DERTaggedObject(true, AttestationConstants.TAG_ALLOW_WHILE_ON_BODY, DERNull.INSTANCE) DERTaggedObject(
true,
AttestationConstants.TAG_ALLOW_WHILE_ON_BODY,
DERNull.INSTANCE,
)
) )
} }
if (params.trustedUserPresenceRequired == true && attestVersion >= 3) { if (params.trustedUserPresenceRequired == true && attestVersion >= 3) {
list.add( list.add(
DERTaggedObject(true, AttestationConstants.TAG_TRUSTED_USER_PRESENCE_REQUIRED, DERNull.INSTANCE) DERTaggedObject(
true,
AttestationConstants.TAG_TRUSTED_USER_PRESENCE_REQUIRED,
DERNull.INSTANCE,
)
) )
} }
if (params.trustedConfirmationRequired == true && attestVersion >= 3) { if (params.trustedConfirmationRequired == true && attestVersion >= 3) {
list.add( list.add(
DERTaggedObject(true, AttestationConstants.TAG_TRUSTED_CONFIRMATION_REQUIRED, DERNull.INSTANCE) DERTaggedObject(
true,
AttestationConstants.TAG_TRUSTED_CONFIRMATION_REQUIRED,
DERNull.INSTANCE,
)
) )
} }
@@ -427,6 +462,7 @@ object AttestationBuilder {
) )
) )
// ATTESTATION_APPLICATION_ID is only included when an attestation challenge is present.
if (params.attestationChallenge != null) { if (params.attestationChallenge != null) {
list.add( list.add(
DERTaggedObject( DERTaggedObject(
@@ -436,7 +472,6 @@ object AttestationBuilder {
) )
) )
} }
if (AndroidDeviceUtils.getAttestVersion(securityLevel) >= 400) { if (AndroidDeviceUtils.getAttestVersion(securityLevel) >= 400) {
list.add( list.add(
DERTaggedObject( DERTaggedObject(
@@ -447,11 +482,8 @@ object AttestationBuilder {
) )
} }
if (params.callerNonce == true) { // Keystore2-enforced tags belong in softwareEnforced, not teeEnforced.
list.add( // The HAL does not enforce these; keystore2's authorize_create handles them.
DERTaggedObject(true, AttestationConstants.TAG_CALLER_NONCE, DERNull.INSTANCE)
)
}
params.activeDateTime?.let { params.activeDateTime?.let {
list.add( list.add(
DERTaggedObject(true, AttestationConstants.TAG_ACTIVE_DATETIME, ASN1Integer(it.time)) DERTaggedObject(true, AttestationConstants.TAG_ACTIVE_DATETIME, ASN1Integer(it.time))
@@ -459,22 +491,38 @@ object AttestationBuilder {
} }
params.originationExpireDateTime?.let { params.originationExpireDateTime?.let {
list.add( list.add(
DERTaggedObject(true, AttestationConstants.TAG_ORIGINATION_EXPIRE_DATETIME, ASN1Integer(it.time)) DERTaggedObject(
true,
AttestationConstants.TAG_ORIGINATION_EXPIRE_DATETIME,
ASN1Integer(it.time),
)
) )
} }
params.usageExpireDateTime?.let { params.usageExpireDateTime?.let {
list.add( list.add(
DERTaggedObject(true, AttestationConstants.TAG_USAGE_EXPIRE_DATETIME, ASN1Integer(it.time)) DERTaggedObject(
true,
AttestationConstants.TAG_USAGE_EXPIRE_DATETIME,
ASN1Integer(it.time),
)
) )
} }
params.usageCountLimit?.let { params.usageCountLimit?.let {
list.add( list.add(
DERTaggedObject(true, AttestationConstants.TAG_USAGE_COUNT_LIMIT, ASN1Integer(it.toLong())) DERTaggedObject(
true,
AttestationConstants.TAG_USAGE_COUNT_LIMIT,
ASN1Integer(it.toLong()),
)
) )
} }
if (params.unlockedDeviceRequired == true) { if (params.unlockedDeviceRequired == true) {
list.add( list.add(
DERTaggedObject(true, AttestationConstants.TAG_UNLOCKED_DEVICE_REQUIRED, DERNull.INSTANCE) DERTaggedObject(
true,
AttestationConstants.TAG_UNLOCKED_DEVICE_REQUIRED,
DERNull.INSTANCE,
)
) )
} }
@@ -505,10 +553,16 @@ object AttestationBuilder {
* retrieved. * retrieved.
*/ */
@Throws(Throwable::class) @Throws(Throwable::class)
internal fun createApplicationId(uid: Int): DEROctetString { private fun createApplicationId(uid: Int): DEROctetString {
// AOSP keystore_attestation_id.cpp: gather_attestation_application_id()
// uses a hardcoded identity for AID_SYSTEM (1000) and AID_ROOT (0):
// packageName = "AndroidSystem", versionCode = 1, no signing digests.
val appUid = uid % 100000 val appUid = uid % 100000
if (appUid == 0 || appUid == 1000) { if (appUid == 0 || appUid == 1000) {
return buildApplicationIdDer(listOf("AndroidSystem" to 1L), emptySet()) return buildApplicationIdDer(
listOf("AndroidSystem" to 1L),
emptySet(),
)
} }
val pm = val pm =
@@ -95,5 +95,5 @@ object AttestationConstants {
// --- Other Constants --- // --- Other Constants ---
// https://cs.android.com/android/platform/superproject/main/+/main:system/keymaster/km_openssl/attestation_record.cpp // https://cs.android.com/android/platform/superproject/main/+/main:system/keymaster/km_openssl/attestation_record.cpp
const val CHALLENGE_LENGTH_LIMIT = 128 const val CHALLENGE_LENGTH_LIMIT = 128 // kMaximumAttestationChallengeLength
} }
@@ -1,13 +1,8 @@
package org.matrix.TEESimulator.attestation package org.matrix.TEESimulator.attestation
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import java.security.KeyPairGenerator
import java.security.KeyStore import java.security.KeyStore
import java.security.SecureRandom
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.security.spec.ECGenParameterSpec
import org.bouncycastle.asn1.ASN1Integer import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1ObjectIdentifier import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.ASN1OctetString import org.bouncycastle.asn1.ASN1OctetString
@@ -57,55 +52,14 @@ object DeviceAttestationService {
val bootPatchLevel: Int?, val bootPatchLevel: Int?,
) )
// A unique alias for the key used to perform the TEE functionality check.
private const val TEE_CHECK_KEY_ALIAS = "TEESimulator_AttestationCheck" private const val TEE_CHECK_KEY_ALIAS = "TEESimulator_AttestationCheck"
/**
* Lazily determines if the device's TEE is functional by attempting to generate an
* attestation-backed key pair. The result is cached.
*/
val isTeeFunctional: Boolean by lazy { checkTeeFunctionality() }
/** /**
* Lazily fetches and parses attestation data from a genuinely generated certificate. The result * Lazily fetches and parses attestation data from a genuinely generated certificate. The result
* is cached. Returns null if the TEE is not functional or parsing fails. * is cached. Returns null if the TEE is not functional or parsing fails.
*/ */
val CachedAttestationData: AttestationData? by lazy { fetchAttestationData() } val CachedAttestationData: AttestationData? by lazy { fetchAttestationData() }
/**
* Checks if the TEE is working correctly by generating a key in the Android Keystore with an
* attestation challenge.
*
* @return `true` if a key with attestation was generated successfully, `false` otherwise.
*/
private fun checkTeeFunctionality(): Boolean {
SystemLogger.info("Performing TEE functionality check...")
return try {
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val keyPairGenerator =
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
// A random challenge is required for attestation.
val challenge = ByteArray(16).apply { SecureRandom().nextBytes(this) }
val spec =
KeyGenParameterSpec.Builder(TEE_CHECK_KEY_ALIAS, KeyProperties.PURPOSE_SIGN)
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
.setDigests(KeyProperties.DIGEST_SHA256)
.setAttestationChallenge(challenge)
.build()
keyPairGenerator.initialize(spec)
keyPairGenerator.generateKeyPair()
SystemLogger.info("TEE functionality check successful.")
true
} catch (e: Exception) {
SystemLogger.warning("TEE functionality check failed.", e)
false
}
}
/** /**
* Retrieves the attestation certificate generated during the TEE check. The key entry is * Retrieves the attestation certificate generated during the TEE check. The key entry is
* deleted after retrieval to clean up. * deleted after retrieval to clean up.
@@ -113,8 +67,6 @@ object DeviceAttestationService {
* @return The leaf `X509Certificate` containing the attestation, or `null` if unavailable. * @return The leaf `X509Certificate` containing the attestation, or `null` if unavailable.
*/ */
private fun getAttestationCertificate(): X509Certificate? { private fun getAttestationCertificate(): X509Certificate? {
if (!isTeeFunctional) return null
return try { return try {
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val certChain = keyStore.getCertificateChain(TEE_CHECK_KEY_ALIAS) val certChain = keyStore.getCertificateChain(TEE_CHECK_KEY_ALIAS)
@@ -1,7 +1,6 @@
package org.matrix.TEESimulator.attestation package org.matrix.TEESimulator.attestation
import android.hardware.security.keymint.* import android.hardware.security.keymint.*
import android.hardware.security.keymint.KeyOrigin
import java.math.BigInteger import java.math.BigInteger
import java.util.Date import java.util.Date
import javax.security.auth.x500.X500Principal import javax.security.auth.x500.X500Principal
@@ -17,11 +16,12 @@ import org.matrix.TEESimulator.logging.KeyMintParameterLogger
// Reference: // Reference:
// https://cs.android.com/android/platform/superproject/main/+/main:system/security/keystore2/src/key_parameter.rs // https://cs.android.com/android/platform/superproject/main/+/main:system/security/keystore2/src/key_parameter.rs
data class KeyMintAttestation( data class KeyMintAttestation(
val keySize: Int,
val algorithm: Int, val algorithm: Int,
val ecCurve: Int?, val ecCurve: Int?,
val ecCurveName: String, val ecCurveName: String,
val keySize: Int,
val origin: Int?, val origin: Int?,
val noAuthRequired: Boolean?,
val blockMode: List<Int>, val blockMode: List<Int>,
val padding: List<Int>, val padding: List<Int>,
val purpose: List<Int>, val purpose: List<Int>,
@@ -41,6 +41,7 @@ data class KeyMintAttestation(
val manufacturer: ByteArray?, val manufacturer: ByteArray?,
val model: ByteArray?, val model: ByteArray?,
val secondImei: ByteArray?, val secondImei: ByteArray?,
// Enforcement tags
val activeDateTime: Date?, val activeDateTime: Date?,
val originationExpireDateTime: Date?, val originationExpireDateTime: Date?,
val usageExpireDateTime: Date?, val usageExpireDateTime: Date?,
@@ -53,7 +54,6 @@ data class KeyMintAttestation(
val allowWhileOnBody: Boolean?, val allowWhileOnBody: Boolean?,
val trustedUserPresenceRequired: Boolean?, val trustedUserPresenceRequired: Boolean?,
val trustedConfirmationRequired: Boolean?, val trustedConfirmationRequired: Boolean?,
val noAuthRequired: Boolean?,
val maxUsesPerBoot: Int?, val maxUsesPerBoot: Int?,
val maxBootLevel: Int?, val maxBootLevel: Int?,
val minMacLength: Int?, val minMacLength: Int?,
@@ -63,11 +63,13 @@ data class KeyMintAttestation(
constructor( constructor(
params: Array<KeyParameter> params: Array<KeyParameter>
) : this( ) : this(
keySize = params.findInteger(Tag.KEY_SIZE) ?: params.deriveKeySizeFromCurve(),
// AOSP: [key_param(tag = ALGORITHM, field = Algorithm)] // AOSP: [key_param(tag = ALGORITHM, field = Algorithm)]
algorithm = params.findAlgorithm(Tag.ALGORITHM) ?: 0, algorithm = params.findAlgorithm(Tag.ALGORITHM) ?: 0,
// AOSP: [key_param(tag = KEY_SIZE, field = Integer)]
// For EC keys, derive keySize from EC_CURVE when KEY_SIZE is absent.
keySize = params.findInteger(Tag.KEY_SIZE) ?: params.deriveKeySizeFromCurve(),
// AOSP: [key_param(tag = EC_CURVE, field = EcCurve)] // AOSP: [key_param(tag = EC_CURVE, field = EcCurve)]
ecCurve = params.findEcCurve(Tag.EC_CURVE), ecCurve = params.findEcCurve(Tag.EC_CURVE),
ecCurveName = params.deriveEcCurveName(), ecCurveName = params.deriveEcCurveName(),
@@ -75,6 +77,9 @@ data class KeyMintAttestation(
// AOSP: [key_param(tag = ORIGIN, field = Origin)] // AOSP: [key_param(tag = ORIGIN, field = Origin)]
origin = params.findOrigin(Tag.ORIGIN), origin = params.findOrigin(Tag.ORIGIN),
// AOSP: [key_param(tag = NO_AUTH_REQUIRED, field = BoolValue)]
noAuthRequired = params.findBoolean(Tag.NO_AUTH_REQUIRED),
// AOSP: [key_param(tag = BLOCK_MODE, field = BlockMode)] // AOSP: [key_param(tag = BLOCK_MODE, field = BlockMode)]
blockMode = params.findAllBlockMode(Tag.BLOCK_MODE), blockMode = params.findAllBlockMode(Tag.BLOCK_MODE),
@@ -116,6 +121,8 @@ data class KeyMintAttestation(
manufacturer = params.findBlob(Tag.ATTESTATION_ID_MANUFACTURER), manufacturer = params.findBlob(Tag.ATTESTATION_ID_MANUFACTURER),
model = params.findBlob(Tag.ATTESTATION_ID_MODEL), model = params.findBlob(Tag.ATTESTATION_ID_MODEL),
secondImei = params.findBlob(Tag.ATTESTATION_ID_SECOND_IMEI), secondImei = params.findBlob(Tag.ATTESTATION_ID_SECOND_IMEI),
// Enforcement tags
activeDateTime = params.findDate(Tag.ACTIVE_DATETIME), activeDateTime = params.findDate(Tag.ACTIVE_DATETIME),
originationExpireDateTime = params.findDate(Tag.ORIGINATION_EXPIRE_DATETIME), originationExpireDateTime = params.findDate(Tag.ORIGINATION_EXPIRE_DATETIME),
usageExpireDateTime = params.findDate(Tag.USAGE_EXPIRE_DATETIME), usageExpireDateTime = params.findDate(Tag.USAGE_EXPIRE_DATETIME),
@@ -128,7 +135,6 @@ data class KeyMintAttestation(
allowWhileOnBody = params.findBoolean(Tag.ALLOW_WHILE_ON_BODY), allowWhileOnBody = params.findBoolean(Tag.ALLOW_WHILE_ON_BODY),
trustedUserPresenceRequired = params.findBoolean(Tag.TRUSTED_USER_PRESENCE_REQUIRED), trustedUserPresenceRequired = params.findBoolean(Tag.TRUSTED_USER_PRESENCE_REQUIRED),
trustedConfirmationRequired = params.findBoolean(Tag.TRUSTED_CONFIRMATION_REQUIRED), trustedConfirmationRequired = params.findBoolean(Tag.TRUSTED_CONFIRMATION_REQUIRED),
noAuthRequired = params.findBoolean(Tag.NO_AUTH_REQUIRED),
maxUsesPerBoot = params.findInteger(Tag.MAX_USES_PER_BOOT), maxUsesPerBoot = params.findInteger(Tag.MAX_USES_PER_BOOT),
maxBootLevel = params.findInteger(Tag.MAX_BOOT_LEVEL), maxBootLevel = params.findInteger(Tag.MAX_BOOT_LEVEL),
minMacLength = params.findInteger(Tag.MIN_MAC_LENGTH), minMacLength = params.findInteger(Tag.MIN_MAC_LENGTH),
@@ -138,13 +144,21 @@ data class KeyMintAttestation(
params.forEach { KeyMintParameterLogger.logParameter(it) } params.forEach { KeyMintParameterLogger.logParameter(it) }
} }
fun isAttestKey(): Boolean = purpose.size == 1 && purpose.contains(KeyPurpose.ATTEST_KEY) fun isAttestKey(): Boolean {
return purpose.size == 1 && purpose.contains(KeyPurpose.ATTEST_KEY)
}
fun isImportKey(): Boolean = origin == KeyOrigin.IMPORTED || origin == KeyOrigin.SECURELY_IMPORTED fun isImportKey(): Boolean {
return origin == KeyOrigin.IMPORTED || origin == KeyOrigin.SECURELY_IMPORTED
}
} }
// --- Private helper extension functions for parsing KeyParameter arrays --- // --- Private helper extension functions for parsing KeyParameter arrays ---
/** Maps to AOSP field = Integer */
private fun Array<KeyParameter>.findBoolean(tag: Int): Boolean? =
this.find { it.tag == tag }?.value?.boolValue
/** Maps to AOSP field = Integer */ /** Maps to AOSP field = Integer */
private fun Array<KeyParameter>.findInteger(tag: Int): Int? = private fun Array<KeyParameter>.findInteger(tag: Int): Int? =
this.find { it.tag == tag }?.value?.integer this.find { it.tag == tag }?.value?.integer
@@ -177,7 +191,7 @@ private fun Array<KeyParameter>.findBlob(tag: Int): ByteArray? =
private fun Array<KeyParameter>.findAllBlockMode(tag: Int): List<Int> = private fun Array<KeyParameter>.findAllBlockMode(tag: Int): List<Int> =
this.filter { it.tag == tag }.map { it.value.blockMode } this.filter { it.tag == tag }.map { it.value.blockMode }
/** Maps to AOSP field = BlockMode (Repeated) */ /** Maps to AOSP field = PaddingMode (Repeated) */
private fun Array<KeyParameter>.findAllPaddingMode(tag: Int): List<Int> = private fun Array<KeyParameter>.findAllPaddingMode(tag: Int): List<Int> =
this.filter { it.tag == tag }.map { it.value.paddingMode } this.filter { it.tag == tag }.map { it.value.paddingMode }
@@ -189,9 +203,7 @@ private fun Array<KeyParameter>.findAllKeyPurpose(tag: Int): List<Int> =
private fun Array<KeyParameter>.findAllDigests(tag: Int): List<Int> = private fun Array<KeyParameter>.findAllDigests(tag: Int): List<Int> =
this.filter { it.tag == tag }.map { it.value.digest } this.filter { it.tag == tag }.map { it.value.digest }
private fun Array<KeyParameter>.findBoolean(tag: Int): Boolean? = /** Derives keySize from EC_CURVE tag when KEY_SIZE is not explicitly provided. */
if (this.any { it.tag == tag }) true else null
private fun Array<KeyParameter>.deriveKeySizeFromCurve(): Int { private fun Array<KeyParameter>.deriveKeySizeFromCurve(): Int {
val curveId = this.find { it.tag == Tag.EC_CURVE }?.value?.ecCurve ?: return 0 val curveId = this.find { it.tag == Tag.EC_CURVE }?.value?.ecCurve ?: return 0
return when (curveId) { return when (curveId) {
@@ -65,6 +65,7 @@ object ConfigurationManager {
// Initial load of all configuration files. // Initial load of all configuration files.
loadTargetPackages(File(configRoot, TARGET_PACKAGES_FILE)) loadTargetPackages(File(configRoot, TARGET_PACKAGES_FILE))
loadPatchLevelConfig(File(configRoot, PATCH_LEVEL_FILE)) loadPatchLevelConfig(File(configRoot, PATCH_LEVEL_FILE))
// Start watching for any subsequent file changes. // Start watching for any subsequent file changes.
ConfigObserver.startWatching() ConfigObserver.startWatching()
SystemLogger.info("Configuration initialized and file observer started.") SystemLogger.info("Configuration initialized and file observer started.")
@@ -82,6 +83,7 @@ object ConfigurationManager {
return packages.firstNotNullOfOrNull { pkg -> packageKeyboxes[pkg] } ?: DEFAULT_KEYBOX_FILE return packages.firstNotNullOfOrNull { pkg -> packageKeyboxes[pkg] } ?: DEFAULT_KEYBOX_FILE
} }
/** Determines if the certificate for a given UID needs to be patched. */
fun shouldPatch(uid: Int): Boolean { fun shouldPatch(uid: Int): Boolean {
val mode = getPackageModeForUid(uid) val mode = getPackageModeForUid(uid)
return mode == Mode.PATCH || mode == Mode.AUTO return mode == Mode.PATCH || mode == Mode.AUTO
@@ -90,10 +92,13 @@ object ConfigurationManager {
/** Determines if a new certificate needs to be generated for a given UID. */ /** Determines if a new certificate needs to be generated for a given UID. */
fun shouldGenerate(uid: Int): Boolean = getPackageModeForUid(uid) == Mode.GENERATE fun shouldGenerate(uid: Int): Boolean = getPackageModeForUid(uid) == Mode.GENERATE
/** Determines if no operation is needed for a given UID. */
fun shouldSkipUid(uid: Int): Boolean = getPackageModeForUid(uid) == null fun shouldSkipUid(uid: Int): Boolean = getPackageModeForUid(uid) == null
/** Determines if the UID is in AUTO mode (no explicit ! or ? suffix). */
fun isAutoMode(uid: Int): Boolean = getPackageModeForUid(uid) == Mode.AUTO fun isAutoMode(uid: Int): Boolean = getPackageModeForUid(uid) == Mode.AUTO
/** Resolves the operating mode for a given UID based on its packages and the TEE status. */
private fun getPackageModeForUid(uid: Int): Mode? { private fun getPackageModeForUid(uid: Int): Mode? {
val packages = getPackagesForUid(uid) val packages = getPackagesForUid(uid)
if (packages.isEmpty()) return null if (packages.isEmpty()) return null
@@ -151,25 +156,25 @@ object ConfigurationManager {
return@forEach return@forEach
} }
val mode: Mode
val rawPkg: String
when { when {
// Suffix '!' means force GENERATE mode.
trimmedLine.endsWith("!") -> { trimmedLine.endsWith("!") -> {
val pkg = trimmedLine.removeSuffix("!").trim() mode = Mode.GENERATE
newModes[pkg] = Mode.GENERATE rawPkg = trimmedLine.removeSuffix("!").trim()
newKeyboxes[pkg] = currentKeybox
} }
// Suffix '?' means force PATCH mode.
trimmedLine.endsWith("?") -> { trimmedLine.endsWith("?") -> {
val pkg = trimmedLine.removeSuffix("?").trim() mode = Mode.PATCH
newModes[pkg] = Mode.PATCH rawPkg = trimmedLine.removeSuffix("?").trim()
newKeyboxes[pkg] = currentKeybox
} }
// No suffix means AUTO mode.
else -> { else -> {
newModes[trimmedLine] = Mode.AUTO mode = Mode.AUTO
newKeyboxes[trimmedLine] = currentKeybox rawPkg = trimmedLine
} }
} }
newModes[rawPkg] = mode
newKeyboxes[rawPkg] = currentKeybox
} }
// Atomically update the configuration maps. // Atomically update the configuration maps.
@@ -246,14 +251,7 @@ object ConfigurationManager {
} }
// Parse global and per-package configurations. // Parse global and per-package configurations.
var newGlobalLevel = parseLines(contextLines[""]) val newGlobalLevel = parseLines(contextLines[""])
// TrickyAddon writes Pixel bulletin dates for boot/vendor but system=prop
// resolves to the real device prop — force boot/vendor through the same path
// to prevent cross-component date mismatches on non-Pixel devices.
if (newGlobalLevel?.system.equals("prop", ignoreCase = true)) {
SystemLogger.info("system=prop: forcing boot/vendor to derive from device props (were: boot=${newGlobalLevel?.boot}, vendor=${newGlobalLevel?.vendor})")
newGlobalLevel = newGlobalLevel?.copy(boot = "prop", vendor = "prop")
}
contextLines.remove("") // Remove global context to iterate over packages next contextLines.remove("") // Remove global context to iterate over packages next
for ((pkg, lines) in contextLines) { for ((pkg, lines) in contextLines) {
@@ -284,10 +282,8 @@ object ConfigurationManager {
val file = if (event != DELETE) File(configRoot, path) else null val file = if (event != DELETE) File(configRoot, path) else null
when (path) { when (path) {
TARGET_PACKAGES_FILE -> file?.let { loadTargetPackages(it) } TARGET_PACKAGES_FILE -> loadTargetPackages(file!!)
?: SystemLogger.warning("$TARGET_PACKAGES_FILE was deleted.") PATCH_LEVEL_FILE -> loadPatchLevelConfig(file!!)
PATCH_LEVEL_FILE -> file?.let { loadPatchLevelConfig(it) }
?: SystemLogger.warning("$PATCH_LEVEL_FILE was deleted.")
// Any change to an XML file is assumed to be a keybox. // Any change to an XML file is assumed to be a keybox.
// The cache in KeyBoxManager will handle reloading it on its next use. // The cache in KeyBoxManager will handle reloading it on its next use.
else -> else ->
@@ -330,6 +326,8 @@ object ConfigurationManager {
return iPackageManager return iPackageManager
} }
/** Checks if any package belonging to the UID holds the given permission. */
/** Checks a SELinux permission for a caller identified by PID against the keystore context. */
fun checkSELinuxPermission(callingPid: Int, tclass: String, perm: String): Boolean { fun checkSELinuxPermission(callingPid: Int, tclass: String, perm: String): Boolean {
return try { return try {
val callerCtx = val callerCtx =
@@ -342,6 +340,7 @@ object ConfigurationManager {
} }
} }
/** Checks if any package belonging to the UID holds the given permission. */
fun hasPermissionForUid(uid: Int, permission: String): Boolean { fun hasPermissionForUid(uid: Int, permission: String): Boolean {
val userId = uid / 100000 val userId = uid / 100000
return getPackagesForUid(uid).any { pkg -> return getPackagesForUid(uid).any { pkg ->
@@ -353,6 +352,7 @@ object ConfigurationManager {
} }
} }
/** Retrieves the package names associated with a UID. */
fun getPackagesForUid(uid: Int): Array<String> { fun getPackagesForUid(uid: Int): Array<String> {
return uidToPackagesCache.getOrPut(uid) { return uidToPackagesCache.getOrPut(uid) {
try { try {
@@ -109,17 +109,17 @@ abstract class BinderInterceptor : Binder() {
* `handlePostTransact`). * `handlePostTransact`).
*/ */
final override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { final override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
// The native hook prepends a transaction ID to the data parcel.
val txId = data.readLong() val txId = data.readLong()
val result = try { val result =
when (code) { when (code) {
// These codes are defined in the native layer to distinguish hook types.
PRE_TRANSACT_CODE -> handlePreTransact(txId, data) PRE_TRANSACT_CODE -> handlePreTransact(txId, data)
POST_TRANSACT_CODE -> handlePostTransact(txId, data) POST_TRANSACT_CODE -> handlePostTransact(txId, data)
else -> return super.onTransact(code, data, reply, flags) else -> return super.onTransact(code, data, reply, flags)
} }
} catch (e: Throwable) {
SystemLogger.error("[TX_ID: $txId] Interceptor exception, falling through to HAL", e) // The reply parcel is guaranteed to be non-null for our custom transactions.
TransactionResult.ContinueAndSkipPost
}
writeResultToReply(result, reply!!) writeResultToReply(result, reply!!)
return true return true
} }
@@ -293,6 +293,12 @@ abstract class BinderInterceptor : Binder() {
} }
} }
/**
* Uses the backdoor binder to register an interceptor for a specific target service.
*
* @param filteredCodes If non-empty, only these transaction codes will be intercepted at
* the native level. All other codes pass through without the round-trip to Java.
*/
fun register( fun register(
backdoor: IBinder, backdoor: IBinder,
target: IBinder, target: IBinder,
@@ -68,8 +68,13 @@ abstract class AbstractKeystoreInterceptor : BinderInterceptor() {
} }
} }
/**
* Transaction codes this interceptor needs to handle at the native level. Override in
* subclasses to filter; empty means intercept everything (legacy behavior).
*/
protected open val interceptedCodes: IntArray = intArrayOf() protected open val interceptedCodes: IntArray = intArrayOf()
/** Registers this interceptor with the native hook layer and sets up a death recipient. */
private fun setupInterceptor(service: IBinder, backdoor: IBinder) { private fun setupInterceptor(service: IBinder, backdoor: IBinder) {
keystoreService = service keystoreService = service
SystemLogger.info("Registering interceptor for service: $serviceName") SystemLogger.info("Registering interceptor for service: $serviceName")
@@ -17,18 +17,6 @@ data class KeyIdentifier(val uid: Int, val alias: String)
/** A collection of utility functions to support binder interception. */ /** A collection of utility functions to support binder interception. */
object InterceptorUtils { object InterceptorUtils {
private const val EX_SERVICE_SPECIFIC = -8
fun createErrorReply(errorCode: Int): BinderInterceptor.TransactionResult.OverrideReply {
val parcel = Parcel.obtain().apply {
writeInt(EX_SERVICE_SPECIFIC)
writeString(null)
writeInt(0) // empty remote stack trace header (AOSP Status.cpp:196)
writeInt(errorCode)
}
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
}
/** /**
* Uses reflection to get the integer transaction code for a given method name from a Stub * Uses reflection to get the integer transaction code for a given method name from a Stub
* class. This is necessary for older Android versions where codes are not public constants. * class. This is necessary for older Android versions where codes are not public constants.
@@ -130,6 +118,10 @@ object InterceptorUtils {
return exception != null return exception != null
} }
/**
* Creates an `OverrideReply` that writes a `ServiceSpecificException` with the given error
* code via EX_SERVICE_SPECIFIC.
*/
fun createServiceSpecificErrorReply( fun createServiceSpecificErrorReply(
errorCode: Int errorCode: Int
): BinderInterceptor.TransactionResult.OverrideReply { ): BinderInterceptor.TransactionResult.OverrideReply {
@@ -140,6 +132,14 @@ object InterceptorUtils {
return BinderInterceptor.TransactionResult.OverrideReply(parcel) return BinderInterceptor.TransactionResult.OverrideReply(parcel)
} }
/**
* Patches the system-level authorization values (OS_PATCHLEVEL, VENDOR_PATCHLEVEL,
* BOOT_PATCHLEVEL) in an authorization array to match the configured patch levels for the
* given calling UID. Each authorization's original [Authorization.securityLevel] is preserved.
*
* When a patch level is configured as "no" ([AndroidDeviceUtils.DO_NOT_REPORT]), the original
* hardware value is kept as-is.
*/
fun patchAuthorizations( fun patchAuthorizations(
authorizations: Array<Authorization>?, authorizations: Array<Authorization>?,
callingUid: Int, callingUid: Int,
@@ -15,7 +15,6 @@ import java.util.concurrent.ConcurrentHashMap
import org.matrix.TEESimulator.attestation.AttestationPatcher import org.matrix.TEESimulator.attestation.AttestationPatcher
import org.matrix.TEESimulator.attestation.KeyMintAttestation import org.matrix.TEESimulator.attestation.KeyMintAttestation
import org.matrix.TEESimulator.config.ConfigurationManager import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.interception.keystore.shim.GeneratedKeyPersistence
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
import org.matrix.TEESimulator.logging.KeyMintParameterLogger import org.matrix.TEESimulator.logging.KeyMintParameterLogger
import org.matrix.TEESimulator.logging.SystemLogger import org.matrix.TEESimulator.logging.SystemLogger
@@ -58,8 +57,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
.associate { field -> (field.get(null) as Int) to field.name.split("_")[1] } .associate { field -> (field.get(null) as Int) to field.name.split("_")[1] }
} }
private const val RESPONSE_KEY_NOT_FOUND = 7 // Keys whose certs were updated via updateSubcomponent; skip re-patching on getKeyEntry.
private val deletedSoftwareKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
private val userUpdatedKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>() private val userUpdatedKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>()
override val serviceName = "android.system.keystore2.IKeystoreService/default" override val serviceName = "android.system.keystore2.IKeystoreService/default"
@@ -100,7 +98,6 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
interceptor, interceptor,
KeyMintSecurityLevelInterceptor.INTERCEPTED_CODES, KeyMintSecurityLevelInterceptor.INTERCEPTED_CODES,
) )
interceptor.loadPersistedKeys()
} }
} }
.onFailure { SystemLogger.error("Failed to intercept TEE SecurityLevel.", it) } .onFailure { SystemLogger.error("Failed to intercept TEE SecurityLevel.", it) }
@@ -117,7 +114,6 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
interceptor, interceptor,
KeyMintSecurityLevelInterceptor.INTERCEPTED_CODES, KeyMintSecurityLevelInterceptor.INTERCEPTED_CODES,
) )
interceptor.loadPersistedKeys()
} }
} }
.onFailure { SystemLogger.error("Failed to intercept StrongBox SecurityLevel.", it) } .onFailure { SystemLogger.error("Failed to intercept StrongBox SecurityLevel.", it) }
@@ -145,22 +141,8 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
if (isGMS || ConfigurationManager.shouldSkipUid(callingUid)) { if (isGMS || ConfigurationManager.shouldSkipUid(callingUid)) {
return TransactionResult.ContinueAndSkipPost return TransactionResult.ContinueAndSkipPost
}
return runCatching {
val isBatchMode = code == LIST_ENTRIES_BATCHED_TRANSACTION
if (ListEntriesHandler.cacheParameters(txId, data, isBatchMode)) {
TransactionResult.Continue
} else { } else {
TransactionResult.ContinueAndSkipPost return TransactionResult.Continue
}
}
.getOrElse {
SystemLogger.error(
"[TX_ID: $txId] Failed to parse parameters for ${transactionNames[code]!!}",
it,
)
TransactionResult.ContinueAndSkipPost
} }
} else if ( } else if (
code == GET_KEY_ENTRY_TRANSACTION || code == GET_KEY_ENTRY_TRANSACTION ||
@@ -181,6 +163,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
?: return TransactionResult.ContinueAndSkipPost ?: return TransactionResult.ContinueAndSkipPost
if (code == DELETE_KEY_TRANSACTION) { if (code == DELETE_KEY_TRANSACTION) {
// Handle delete by alias (APP domain) or nspace (KEY_ID domain).
val keyId = val keyId =
if (descriptor.alias != null) { if (descriptor.alias != null) {
KeyIdentifier(callingUid, descriptor.alias) KeyIdentifier(callingUid, descriptor.alias)
@@ -199,7 +182,6 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
KeyMintSecurityLevelInterceptor.generatedKeys.containsKey(keyId) KeyMintSecurityLevelInterceptor.generatedKeys.containsKey(keyId)
KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId) KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId)
if (isSoftwareKey) { if (isSoftwareKey) {
deletedSoftwareKeys.add(keyId)
SystemLogger.info( SystemLogger.info(
"[TX_ID: $txId] Deleted cached keypair ${keyId.alias}, replying with empty response." "[TX_ID: $txId] Deleted cached keypair ${keyId.alias}, replying with empty response."
) )
@@ -214,14 +196,9 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
} }
val keyId = KeyIdentifier(callingUid, descriptor.alias) val keyId = KeyIdentifier(callingUid, descriptor.alias)
val response = KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId) val response =
if (response == null) { KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId)
if (deletedSoftwareKeys.remove(keyId)) { ?: return TransactionResult.Continue
SystemLogger.info("[TX_ID: $txId] Returning KEY_NOT_FOUND for deleted key ${descriptor.alias}")
return InterceptorUtils.createErrorReply(RESPONSE_KEY_NOT_FOUND)
}
return TransactionResult.Continue
}
if (KeyMintSecurityLevelInterceptor.isAttestationKey(keyId)) if (KeyMintSecurityLevelInterceptor.isAttestationKey(keyId))
SystemLogger.info("${descriptor.alias} was an attestation key") SystemLogger.info("${descriptor.alias} was an attestation key")
@@ -282,8 +259,12 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid) logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
return runCatching { return runCatching {
val isBatchMode = code == LIST_ENTRIES_BATCHED_TRANSACTION
val params =
ListEntriesHandler.cacheParameters(txId, data, isBatchMode)
?: throw Exception("Abort updating entries for invalid parameters.")
val updatedKeyDescriptors = val updatedKeyDescriptors =
ListEntriesHandler.injectGeneratedKeys(txId, callingUid, reply) ListEntriesHandler.injectGeneratedKeys(txId, callingUid, params, reply)
InterceptorUtils.createTypedArrayReply(updatedKeyDescriptors) InterceptorUtils.createTypedArrayReply(updatedKeyDescriptors)
} }
.getOrElse { .getOrElse {
@@ -306,13 +287,11 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
callingPid, callingPid,
) )
if (!ConfigurationManager.shouldPatch(callingUid))
return TransactionResult.SkipTransaction
runCatching { runCatching {
val response = reply.readTypedObject(KeyEntryResponse.CREATOR)!! val response = reply.readTypedObject(KeyEntryResponse.CREATOR)!!
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias) val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
// Skip patching for keys whose certs were explicitly set via updateSubcomponent.
if (userUpdatedKeys.remove(keyId)) { if (userUpdatedKeys.remove(keyId)) {
SystemLogger.debug("[TX_ID: $txId] Skipping cert patch for user-updated key $keyId.") SystemLogger.debug("[TX_ID: $txId] Skipping cert patch for user-updated key $keyId.")
return TransactionResult.SkipTransaction return TransactionResult.SkipTransaction
@@ -324,26 +303,13 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
authorizations?.map { it.keyParameter }?.toTypedArray() ?: emptyArray() authorizations?.map { it.keyParameter }?.toTypedArray() ?: emptyArray()
) )
if (parsedParameters.isImportKey()) { if (parsedParameters.isAttestKey() &&
val retainedChain = KeyMintSecurityLevelInterceptor.getPatchedChain(keyId) !KeyMintSecurityLevelInterceptor.importedKeys.contains(keyId)
if (retainedChain == null) { ) {
SystemLogger.info("[TX_ID: $txId] Skip patching for imported key (no prior attestation).")
return TransactionResult.SkipTransaction
}
SystemLogger.info("[TX_ID: $txId] Imported key overwrote attested alias, serving retained chain for $keyId")
CertificateHelper.updateCertificateChain(response.metadata, retainedChain).getOrThrow()
return InterceptorUtils.createTypedObjectReply(response)
}
if (KeyMintSecurityLevelInterceptor.importedKeys.contains(keyId)) {
SystemLogger.debug("[TX_ID: $txId] Skipping attest-key override for imported key $keyId")
return TransactionResult.SkipTransaction
}
if (parsedParameters.isAttestKey()) {
SystemLogger.warning( SystemLogger.warning(
"[TX_ID: $txId] Found hardware attest key ${keyId.alias} in the reply." "[TX_ID: $txId] Found hardware attest key ${keyId.alias} in the reply."
) )
// Attest keys that are not under our control should be overriden.
val keyData = val keyData =
CertificateGenerator.generateAttestedKeyPair( CertificateGenerator.generateAttestedKeyPair(
callingUid, callingUid,
@@ -375,26 +341,12 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
parsedParameters, parsedParameters,
) )
KeyMintSecurityLevelInterceptor.attestationKeys.add(keyId) KeyMintSecurityLevelInterceptor.attestationKeys.add(keyId)
GeneratedKeyPersistence.save(
keyId = keyId,
keyPair = keyData.first,
nspace = newNspace,
securityLevel = response.metadata.keySecurityLevel,
certChain = keyData.second,
algorithm = parsedParameters.algorithm,
keySize = parsedParameters.keySize,
ecCurve = parsedParameters.ecCurve ?: 0,
purposes = parsedParameters.purpose,
digests = parsedParameters.digest,
isAttestationKey = true,
)
return InterceptorUtils.createTypedObjectReply(response) return InterceptorUtils.createTypedObjectReply(response)
} }
val originalChain = CertificateHelper.getCertificateChain(response) val originalChain = CertificateHelper.getCertificateChain(response)
// Check if we should perform attestation patch.
if (originalChain == null || originalChain.size < 2) { if (originalChain == null || originalChain.size < 2) {
SystemLogger.info( SystemLogger.info(
"[TX_ID: $txId] Skip patching short certificate chain of length ${originalChain?.size}." "[TX_ID: $txId] Skip patching short certificate chain of length ${originalChain?.size}."
@@ -402,6 +354,8 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
return TransactionResult.SkipTransaction return TransactionResult.SkipTransaction
} }
// First, try to retrieve the already-patched chain from our cache to ensure
// consistency.
val cachedChain = KeyMintSecurityLevelInterceptor.getPatchedChain(keyId) val cachedChain = KeyMintSecurityLevelInterceptor.getPatchedChain(keyId)
val finalChain: Array<Certificate> val finalChain: Array<Certificate>
@@ -411,12 +365,16 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
) )
finalChain = cachedChain finalChain = cachedChain
} else { } else {
// If no chain is cached (e.g., key existed before simulator started),
// perform a live patch as a fallback. This may still be detectable.
SystemLogger.info( SystemLogger.info(
"[TX_ID: $txId] No cached chain for $keyId. Performing live patch as a fallback." "[TX_ID: $txId] No cached chain for $keyId. Performing live patch as a fallback."
) )
finalChain = finalChain =
AttestationPatcher.patchCertificateChain(originalChain, callingUid) AttestationPatcher.patchCertificateChain(originalChain, callingUid)
KeyMintSecurityLevelInterceptor.patchedChains[keyId] = finalChain KeyMintSecurityLevelInterceptor.patchedChains[keyId] = finalChain
SystemLogger.debug("Cached patched certificate chain for $keyId.")
} }
CertificateHelper.updateCertificateChain(response.metadata, finalChain) CertificateHelper.updateCertificateChain(response.metadata, finalChain)
@@ -445,11 +403,13 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
val descriptor = data.readTypedObject(KeyDescriptor.CREATOR) val descriptor = data.readTypedObject(KeyDescriptor.CREATOR)
?: return TransactionResult.ContinueAndSkipPost ?: return TransactionResult.ContinueAndSkipPost
// Resolve by nspace (KEY_ID) or alias (APP), same as createOperation.
val generatedKeyInfo = val generatedKeyInfo =
when (descriptor.domain) { when (descriptor.domain) {
Domain.KEY_ID -> Domain.KEY_ID ->
KeyMintSecurityLevelInterceptor.findGeneratedKeyByKeyId( KeyMintSecurityLevelInterceptor.findGeneratedKeyByKeyId(
callingUid, descriptor.nspace callingUid,
descriptor.nspace,
) )
Domain.APP -> Domain.APP ->
descriptor.alias?.let { descriptor.alias?.let {
@@ -459,6 +419,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
} }
if (generatedKeyInfo == null) { if (generatedKeyInfo == null) {
// Hardware key: mark so getKeyEntry skips cert re-patching.
descriptor.alias?.let { userUpdatedKeys.add(KeyIdentifier(callingUid, it)) } descriptor.alias?.let { userUpdatedKeys.add(KeyIdentifier(callingUid, it)) }
return TransactionResult.ContinueAndSkipPost return TransactionResult.ContinueAndSkipPost
} }
@@ -470,9 +431,6 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
metadata.certificate = publicCert metadata.certificate = publicCert
metadata.certificateChain = certificateChain metadata.certificateChain = certificateChain
GeneratedKeyPersistence.rePersistIfNeeded(callingUid, generatedKeyInfo)
SystemLogger.verbose( SystemLogger.verbose(
"Key updated with sizes: [publicCert, certificateChain] = [${publicCert?.size}, ${certificateChain?.size}]" "Key updated with sizes: [publicCert, certificateChain] = [${publicCert?.size}, ${certificateChain?.size}]"
) )
@@ -399,17 +399,18 @@ private data class LegacyKeygenParameters(
/** /**
* Converts the legacy parameters into the modern [KeyMintAttestation] data structure, which is * Converts the legacy parameters into the modern [KeyMintAttestation] data structure, which is
* required by the refactored [AttestationBuilder] and [CertificateGenerator]. * required by [AttestationBuilder] and [CertificateGenerator].
*/ */
fun toKeyMintAttestation(): KeyMintAttestation { fun toKeyMintAttestation(): KeyMintAttestation {
// This conversion acts as a bridge, allowing our new generic components // This conversion acts as a bridge, allowing our new generic components
// to be used by the legacy interceptor. // to be used by the legacy interceptor.
return KeyMintAttestation( return KeyMintAttestation(
keySize = this.keySize,
algorithm = this.algorithm, algorithm = this.algorithm,
ecCurve = 0, ecCurve = 0, // Not explicitly available in legacy args, but not critical
ecCurveName = this.ecCurveName ?: "", ecCurveName = this.ecCurveName ?: "",
origin = null, keySize = this.keySize,
origin = null, // Not needed to build attestaion
noAuthRequired = null,
blockMode = listOf<Int>(), blockMode = listOf<Int>(),
padding = listOf<Int>(), padding = listOf<Int>(),
purpose = this.purpose, purpose = this.purpose,
@@ -443,7 +444,6 @@ private data class LegacyKeygenParameters(
allowWhileOnBody = null, allowWhileOnBody = null,
trustedUserPresenceRequired = null, trustedUserPresenceRequired = null,
trustedConfirmationRequired = null, trustedConfirmationRequired = null,
noAuthRequired = null,
maxUsesPerBoot = null, maxUsesPerBoot = null,
maxBootLevel = null, maxBootLevel = null,
minMacLength = null, minMacLength = null,
@@ -5,7 +5,6 @@ import android.system.keystore2.Domain
import android.system.keystore2.IKeystoreService import android.system.keystore2.IKeystoreService
import android.system.keystore2.KeyDescriptor import android.system.keystore2.KeyDescriptor
import java.util.TreeMap import java.util.TreeMap
import java.util.concurrent.ConcurrentHashMap
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
import org.matrix.TEESimulator.logging.SystemLogger import org.matrix.TEESimulator.logging.SystemLogger
@@ -22,15 +21,6 @@ object ListEntriesHandler {
// Estimate for maximum size of a Binder response in bytes. // Estimate for maximum size of a Binder response in bytes.
private const val RESPONSE_SIZE_LIMIT = 358400 private const val RESPONSE_SIZE_LIMIT = 358400
// Parameters of AOSP function `list_key_entries` in utils.rs.
private data class ListEntriesParams(
val domain: Int,
val namespace: Long,
val startPastAlias: String?,
)
private val pendingParams = ConcurrentHashMap<Long, ListEntriesParams>()
// Based on AOSP function `estimate_safe_amount_to_return` in utils.rs. // Based on AOSP function `estimate_safe_amount_to_return` in utils.rs.
private fun estimateSafeAmountToReturn( private fun estimateSafeAmountToReturn(
keyDescriptors: Array<KeyDescriptor>, keyDescriptors: Array<KeyDescriptor>,
@@ -60,7 +50,7 @@ object ListEntriesHandler {
} }
// Parse and store parameters for later use (in post-transaction). // Parse and store parameters for later use (in post-transaction).
fun cacheParameters(txId: Long, data: Parcel, isBatchMode: Boolean): Boolean { fun cacheParameters(txId: Long, data: Parcel, isBatchMode: Boolean): ListEntriesParams? {
data.enforceInterface(IKeystoreService.DESCRIPTOR) data.enforceInterface(IKeystoreService.DESCRIPTOR)
val domain = data.readInt() val domain = data.readInt()
@@ -71,20 +61,21 @@ object ListEntriesHandler {
// See AOSP function `get_key_descriptor_for_lookup` in service.rs. // See AOSP function `get_key_descriptor_for_lookup` in service.rs.
// Note that all generated keys belong to Domain::APP. // Note that all generated keys belong to Domain::APP.
if (domain == Domain.APP) { if (domain == Domain.APP) {
pendingParams[txId] = ListEntriesParams(domain, namespace, startPastAlias) val params = ListEntriesParams(domain, namespace, startPastAlias)
SystemLogger.debug("[TX_ID: $txId] Cached ${pendingParams[txId]}.") SystemLogger.debug("[TX_ID: $txId] Cached $params.")
return true return params
} }
return false return null
} }
// Merge software-backed keys with hardware-backed keys in the reply parcel. // Merge software-backed keys with hardware-backed keys in the reply parcel.
fun injectGeneratedKeys(txId: Long, callingUid: Int, reply: Parcel): Array<KeyDescriptor> { fun injectGeneratedKeys(
val params = txId: Long,
pendingParams.remove(txId) callingUid: Int,
?: throw IllegalStateException("No params found for listing entries") params: ListEntriesParams,
reply: Parcel,
): Array<KeyDescriptor> {
// By default we use the calling uid as namespace if domain is Domain::APP. // By default we use the calling uid as namespace if domain is Domain::APP.
// The namespace parameter is thus ignored for non-privileged applications. // The namespace parameter is thus ignored for non-privileged applications.
// See AOSP function `get_key_descriptor_for_lookup` in service.rs. // See AOSP function `get_key_descriptor_for_lookup` in service.rs.
@@ -140,3 +131,6 @@ object ListEntriesHandler {
} }
} }
} }
// Parameters of AOSP function `list_key_entries` in utils.rs.
data class ListEntriesParams(val domain: Int, val namespace: Long, val startPastAlias: String?)
@@ -1,76 +0,0 @@
package org.matrix.TEESimulator.interception.keystore.shim
import android.hardware.security.keymint.Algorithm
import android.hardware.security.keymint.KeyPurpose
import android.hardware.security.keymint.KeyParameter
import android.hardware.security.keymint.Tag
import org.matrix.TEESimulator.attestation.KeyMintAttestation
object AuthorizeCreate {
fun check(
keyParams: KeyMintAttestation?,
opParams: KeyMintAttestation,
rawOpParams: Array<KeyParameter>? = null,
): Int? {
if (keyParams == null) return null
val purpose = opParams.purpose.firstOrNull() ?: return null
// Algorithm-level rejection runs before purpose-list check (AOSP HAL behavior)
return checkAlgorithmPurpose(keyParams, purpose)
?: checkPurpose(keyParams, purpose)
?: checkTemporalValidity(keyParams, purpose)
?: checkCallerNonce(keyParams, purpose, rawOpParams)
}
private fun checkAlgorithmPurpose(keyParams: KeyMintAttestation, purpose: Int): Int? {
val algo = keyParams.algorithm
if ((algo == Algorithm.EC || algo == Algorithm.RSA) &&
(purpose == KeyPurpose.VERIFY || purpose == KeyPurpose.ENCRYPT)
) {
return KeystoreErrorCodes.unsupportedPurpose
}
if (algo == Algorithm.EC && purpose == KeyPurpose.DECRYPT)
return KeystoreErrorCodes.unsupportedPurpose
if (algo == Algorithm.RSA && purpose == KeyPurpose.AGREE_KEY)
return KeystoreErrorCodes.unsupportedPurpose
return null
}
private fun checkPurpose(keyParams: KeyMintAttestation, purpose: Int): Int? {
if (purpose == KeyPurpose.WRAP_KEY)
return KeystoreErrorCodes.incompatiblePurpose
if (purpose !in keyParams.purpose)
return KeystoreErrorCodes.incompatiblePurpose
return null
}
private fun checkTemporalValidity(keyParams: KeyMintAttestation, purpose: Int): Int? {
val now = System.currentTimeMillis()
keyParams.activeDateTime?.let { activeDate ->
if (now < activeDate.time) return KeystoreErrorCodes.keyNotYetValid
}
keyParams.originationExpireDateTime?.let { expireDate ->
if (purpose == KeyPurpose.SIGN || purpose == KeyPurpose.ENCRYPT) {
if (now > expireDate.time) return KeystoreErrorCodes.keyExpired
}
}
keyParams.usageExpireDateTime?.let { expireDate ->
if (purpose == KeyPurpose.VERIFY || purpose == KeyPurpose.DECRYPT) {
if (now > expireDate.time) return KeystoreErrorCodes.keyExpired
}
}
return null
}
private fun checkCallerNonce(keyParams: KeyMintAttestation, purpose: Int, rawOpParams: Array<KeyParameter>?): Int? {
if (purpose != KeyPurpose.SIGN && purpose != KeyPurpose.ENCRYPT) return null
if (keyParams.callerNonce == true) return null
if (rawOpParams?.any { it.tag == Tag.NONCE } == true)
return KeystoreErrorCodes.callerNonceProhibited
return null
}
}
@@ -44,6 +44,7 @@ class OperationInterceptor(
private val ABORT_TRANSACTION = private val ABORT_TRANSACTION =
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "abort") InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "abort")
/** Only intercept finish/abort for cleanup. Other ops pass through without round-trip. */
val INTERCEPTED_CODES = intArrayOf(FINISH_TRANSACTION, ABORT_TRANSACTION) val INTERCEPTED_CODES = intArrayOf(FINISH_TRANSACTION, ABORT_TRANSACTION)
private val transactionNames: Map<Int, String> by lazy { private val transactionNames: Map<Int, String> by lazy {
@@ -8,8 +8,8 @@ import android.hardware.security.keymint.KeyParameterValue
import android.hardware.security.keymint.KeyPurpose import android.hardware.security.keymint.KeyPurpose
import android.hardware.security.keymint.PaddingMode import android.hardware.security.keymint.PaddingMode
import android.hardware.security.keymint.Tag import android.hardware.security.keymint.Tag
import android.os.RemoteException
import android.os.ServiceSpecificException import android.os.ServiceSpecificException
import java.util.concurrent.locks.LockSupport
import android.system.keystore2.IKeystoreOperation import android.system.keystore2.IKeystoreOperation
import android.system.keystore2.KeyParameters import android.system.keystore2.KeyParameters
import java.security.KeyPair import java.security.KeyPair
@@ -20,16 +20,48 @@ import org.matrix.TEESimulator.attestation.KeyMintAttestation
import org.matrix.TEESimulator.logging.KeyMintParameterLogger import org.matrix.TEESimulator.logging.KeyMintParameterLogger
import org.matrix.TEESimulator.logging.SystemLogger import org.matrix.TEESimulator.logging.SystemLogger
private sealed interface CryptoPrimitive { /** Keystore2 error codes for ServiceSpecificException. Negative = KeyMint, positive = Keystore. */
fun updateAad(aadInput: ByteArray?) { internal object KeystoreErrorCode {
throw ServiceSpecificException(KeystoreErrorCodes.invalidTag) const val INVALID_OPERATION_HANDLE = -28
const val VERIFICATION_FAILED = -30
const val UNSUPPORTED_PURPOSE = -2
const val INCOMPATIBLE_PURPOSE = -3
const val SYSTEM_ERROR = 4
const val TOO_MUCH_DATA = 21
const val KEY_EXPIRED = -25
const val KEY_NOT_YET_VALID = -24
/** KeyMint ErrorCode::CALLER_NONCE_PROHIBITED */
const val CALLER_NONCE_PROHIBITED = -55
/** KeyMint ErrorCode::INVALID_ARGUMENT */
const val INVALID_ARGUMENT = -38
/** KeyMint ErrorCode::INVALID_TAG */
const val INVALID_TAG = -40
/** Keystore2 ResponseCode::PERMISSION_DENIED */
const val PERMISSION_DENIED = 6
/** Keystore2 ResponseCode::KEY_NOT_FOUND */
const val KEY_NOT_FOUND = 7
} }
// A sealed interface to represent the different cryptographic operations we can perform.
private sealed interface CryptoPrimitive {
fun updateAad(data: ByteArray?)
fun update(data: ByteArray?): ByteArray? fun update(data: ByteArray?): ByteArray?
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? fun finish(data: ByteArray?, signature: ByteArray?): ByteArray?
fun abort() fun abort()
/** Returns parameters from the begin phase (e.g. GCM nonce), or null if none. */
fun getBeginParameters(): Array<KeyParameter>? = null fun getBeginParameters(): Array<KeyParameter>? = null
} }
// Helper object to map KeyMint constants to JCA algorithm strings.
private object JcaAlgorithmMapper { private object JcaAlgorithmMapper {
fun mapSignatureAlgorithm(params: KeyMintAttestation): String { fun mapSignatureAlgorithm(params: KeyMintAttestation): String {
val digest = val digest =
@@ -39,18 +71,17 @@ private object JcaAlgorithmMapper {
Digest.SHA_2_512 -> "SHA512" Digest.SHA_2_512 -> "SHA512"
else -> "NONE" else -> "NONE"
} }
return when (params.algorithm) { val keyAlgo =
Algorithm.EC -> "${digest}withECDSA" when (params.algorithm) {
Algorithm.RSA -> { Algorithm.EC -> "ECDSA"
val isPss = params.padding.firstOrNull() == PaddingMode.RSA_PSS Algorithm.RSA -> "RSA"
if (isPss) "${digest}withRSA/PSS" else "${digest}withRSA"
}
else -> else ->
throw ServiceSpecificException( throw ServiceSpecificException(
KeystoreErrorCodes.incompatibleAlgorithm, KeystoreErrorCode.SYSTEM_ERROR,
"Unsupported signature algorithm: ${params.algorithm}", "Unsupported signature algorithm: ${params.algorithm}",
) )
} }
return "${digest}with${keyAlgo}"
} }
fun mapCipherAlgorithm(params: KeyMintAttestation): String { fun mapCipherAlgorithm(params: KeyMintAttestation): String {
@@ -60,7 +91,7 @@ private object JcaAlgorithmMapper {
Algorithm.AES -> "AES" Algorithm.AES -> "AES"
else -> else ->
throw ServiceSpecificException( throw ServiceSpecificException(
KeystoreErrorCodes.incompatibleAlgorithm, KeystoreErrorCode.SYSTEM_ERROR,
"Unsupported cipher algorithm: ${params.algorithm}", "Unsupported cipher algorithm: ${params.algorithm}",
) )
} }
@@ -68,29 +99,32 @@ private object JcaAlgorithmMapper {
when (params.blockMode.firstOrNull()) { when (params.blockMode.firstOrNull()) {
BlockMode.ECB -> "ECB" BlockMode.ECB -> "ECB"
BlockMode.CBC -> "CBC" BlockMode.CBC -> "CBC"
BlockMode.CTR -> "CTR"
BlockMode.GCM -> "GCM" BlockMode.GCM -> "GCM"
else -> "ECB" else -> "ECB" // Default for RSA
} }
val padding = val padding =
when (params.padding.firstOrNull()) { when (params.padding.firstOrNull()) {
PaddingMode.NONE -> "NoPadding" PaddingMode.NONE -> "NoPadding"
PaddingMode.PKCS7 -> "PKCS7Padding" PaddingMode.PKCS7 -> "PKCS7Padding"
PaddingMode.RSA_PKCS1_1_5_ENCRYPT -> "PKCS1Padding" PaddingMode.RSA_PKCS1_1_5_ENCRYPT -> "PKCS1Padding"
PaddingMode.RSA_PKCS1_1_5_SIGN -> "PKCS1Padding"
PaddingMode.RSA_OAEP -> "OAEPPadding" PaddingMode.RSA_OAEP -> "OAEPPadding"
else -> "NoPadding" else -> "NoPadding" // Default for GCM
} }
return "$keyAlgo/$blockMode/$padding" return "$keyAlgo/$blockMode/$padding"
} }
} }
// Concrete implementation for Signing.
private class Signer(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive { private class Signer(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
private val signature: Signature = private val signature: Signature =
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply { Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
initSign(keyPair.private) initSign(keyPair.private)
} }
override fun updateAad(data: ByteArray?) {
throw ServiceSpecificException(KeystoreErrorCode.INVALID_TAG)
}
override fun update(data: ByteArray?): ByteArray? { override fun update(data: ByteArray?): ByteArray? {
if (data != null) signature.update(data) if (data != null) signature.update(data)
return null return null
@@ -104,12 +138,17 @@ private class Signer(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimi
override fun abort() {} override fun abort() {}
} }
// Concrete implementation for Verification.
private class Verifier(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive { private class Verifier(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
private val signature: Signature = private val signature: Signature =
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply { Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
initVerify(keyPair.public) initVerify(keyPair.public)
} }
override fun updateAad(data: ByteArray?) {
throw ServiceSpecificException(KeystoreErrorCode.INVALID_TAG)
}
override fun update(data: ByteArray?): ByteArray? { override fun update(data: ByteArray?): ByteArray? {
if (data != null) signature.update(data) if (data != null) signature.update(data)
return null return null
@@ -117,11 +156,16 @@ private class Verifier(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPri
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? { override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
if (data != null) update(data) if (data != null) update(data)
if (signature == null) { if (signature == null)
throw ServiceSpecificException(KeystoreErrorCodes.verificationFailed, "Signature to verify is null") throw ServiceSpecificException(
} KeystoreErrorCode.VERIFICATION_FAILED,
"Signature to verify is null",
)
if (!this.signature.verify(signature)) { if (!this.signature.verify(signature)) {
throw ServiceSpecificException(KeystoreErrorCodes.verificationFailed, "Signature verification failed") throw ServiceSpecificException(
KeystoreErrorCode.VERIFICATION_FAILED,
"Signature/MAC verification failed",
)
} }
return null return null
} }
@@ -129,20 +173,19 @@ private class Verifier(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPri
override fun abort() {} override fun abort() {}
} }
// Concrete implementation for Encryption/Decryption.
private class CipherPrimitive( private class CipherPrimitive(
cryptoKey: java.security.Key, cryptoKey: java.security.Key,
params: KeyMintAttestation, params: KeyMintAttestation,
private val opMode: Int, private val opMode: Int,
) : CryptoPrimitive { ) : CryptoPrimitive {
private val isAead = params.blockMode.firstOrNull() == BlockMode.GCM
private val cipher: Cipher = private val cipher: Cipher =
Cipher.getInstance(JcaAlgorithmMapper.mapCipherAlgorithm(params)).apply { Cipher.getInstance(JcaAlgorithmMapper.mapCipherAlgorithm(params)).apply {
init(opMode, cryptoKey) init(opMode, cryptoKey)
} }
override fun updateAad(aadInput: ByteArray?) { override fun updateAad(data: ByteArray?) {
if (!isAead) throw ServiceSpecificException(KeystoreErrorCodes.invalidTag) if (data != null) cipher.updateAAD(data)
if (aadInput != null) cipher.updateAAD(aadInput)
} }
override fun update(data: ByteArray?): ByteArray? = override fun update(data: ByteArray?): ByteArray? =
@@ -151,6 +194,9 @@ private class CipherPrimitive(
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? = override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? =
if (data != null) cipher.doFinal(data) else cipher.doFinal() if (data != null) cipher.doFinal(data) else cipher.doFinal()
override fun abort() {}
/** Returns the cipher IV as a NONCE parameter for GCM operations. */
override fun getBeginParameters(): Array<KeyParameter>? { override fun getBeginParameters(): Array<KeyParameter>? {
val iv = cipher.iv ?: return null val iv = cipher.iv ?: return null
return arrayOf( return arrayOf(
@@ -160,20 +206,23 @@ private class CipherPrimitive(
} }
) )
} }
override fun abort() {}
} }
// Concrete implementation for ECDH Key Agreement.
private class KeyAgreementPrimitive(keyPair: KeyPair) : CryptoPrimitive { private class KeyAgreementPrimitive(keyPair: KeyPair) : CryptoPrimitive {
private val agreement: javax.crypto.KeyAgreement = private val agreement: javax.crypto.KeyAgreement =
javax.crypto.KeyAgreement.getInstance("ECDH").apply { init(keyPair.private) } javax.crypto.KeyAgreement.getInstance("ECDH").apply { init(keyPair.private) }
override fun updateAad(data: ByteArray?) {
throw ServiceSpecificException(KeystoreErrorCode.INVALID_TAG)
}
override fun update(data: ByteArray?): ByteArray? = null override fun update(data: ByteArray?): ByteArray? = null
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? { override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
if (data == null) if (data == null)
throw ServiceSpecificException( throw ServiceSpecificException(
KeystoreErrorCodes.invalidArgument, KeystoreErrorCode.INVALID_ARGUMENT,
"Peer public key required for key agreement", "Peer public key required for key agreement",
) )
val peerKey = val peerKey =
@@ -186,25 +235,23 @@ private class KeyAgreementPrimitive(keyPair: KeyPair) : CryptoPrimitive {
override fun abort() {} override fun abort() {}
} }
/**
* A software-only implementation of a cryptographic operation. This class acts as a controller,
* delegating to a specific cryptographic primitive based on the operation's purpose.
*
* Tracks operation lifecycle: once [finish] or [abort] is called, subsequent calls throw
* [ServiceSpecificException] with [KeystoreErrorCode.INVALID_OPERATION_HANDLE].
*/
class SoftwareOperation( class SoftwareOperation(
private val txId: Long, private val txId: Long,
keyPair: KeyPair?, keyPair: KeyPair?,
secretKey: javax.crypto.SecretKey?, secretKey: javax.crypto.SecretKey?,
params: KeyMintAttestation, params: KeyMintAttestation,
private val latencyFloorMs: Long = 0L, var onFinishCallback: (() -> Unit)? = null,
) { ) {
private val primitive: CryptoPrimitive private val primitive: CryptoPrimitive
@Volatile var finalized = false
private set
var onFinishCallback: (() -> Unit)? = null @Volatile private var finalized = false
val beginParameters: KeyParameters?
get() {
val params = primitive.getBeginParameters() ?: return null
if (params.isEmpty()) return null
return KeyParameters().apply { keyParameter = params }
}
init { init {
val purpose = params.purpose.firstOrNull() val purpose = params.purpose.firstOrNull()
@@ -226,175 +273,121 @@ class SoftwareOperation(
KeyPurpose.AGREE_KEY -> KeyAgreementPrimitive(keyPair!!) KeyPurpose.AGREE_KEY -> KeyAgreementPrimitive(keyPair!!)
else -> else ->
throw ServiceSpecificException( throw ServiceSpecificException(
KeystoreErrorCodes.unsupportedPurpose, KeystoreErrorCode.UNSUPPORTED_PURPOSE,
"Unsupported operation purpose: $purpose", "Unsupported operation purpose: $purpose",
) )
} }
} }
/** Parameters produced during begin (e.g. GCM nonce), to populate CreateOperationResponse. */
val beginParameters: KeyParameters?
get() {
val params = primitive.getBeginParameters() ?: return null
if (params.isEmpty()) return null
return KeyParameters().apply { keyParameter = params }
}
private fun checkActive() { private fun checkActive() {
if (finalized) { if (finalized)
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Rejected: operation already finalized (pruned or completed)") throw ServiceSpecificException(
throw ServiceSpecificException(KeystoreErrorCodes.invalidOperationHandle) KeystoreErrorCode.INVALID_OPERATION_HANDLE,
} "Operation already finalized.",
)
} }
private fun checkInputLength(data: ByteArray?) { fun updateAad(data: ByteArray?) {
if (data != null && data.size > MAX_RECEIVE_DATA) {
SystemLogger.info("[SoftwareOp TX_ID: $txId] Input too large: ${data.size} > $MAX_RECEIVE_DATA, throwing TOO_MUCH_DATA(${KeystoreErrorCodes.tooMuchData})")
throw ServiceSpecificException(KeystoreErrorCodes.tooMuchData)
}
}
fun updateAad(aadInput: ByteArray?) {
SystemLogger.debug("[SoftwareOp TX_ID: $txId] updateAad() inputSize=${aadInput?.size ?: 0}")
checkActive() checkActive()
checkInputLength(aadInput) try {
primitive.updateAad(aadInput) primitive.updateAad(data)
} catch (e: ServiceSpecificException) {
finalized = true
throw e
} catch (e: Exception) {
finalized = true
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to updateAad.", e)
throw ServiceSpecificException(KeystoreErrorCode.SYSTEM_ERROR, e.message)
}
} }
fun update(data: ByteArray?): ByteArray? { fun update(data: ByteArray?): ByteArray? {
SystemLogger.debug("[SoftwareOp TX_ID: $txId] update() inputSize=${data?.size ?: 0}")
checkActive() checkActive()
checkInputLength(data)
try { try {
return primitive.update(data) return primitive.update(data)
} catch (e: ServiceSpecificException) { } catch (e: ServiceSpecificException) {
finalized = true
throw e throw e
} catch (e: Exception) { } catch (e: Exception) {
finalized = true
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e) SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e)
throw mapToServiceSpecificException(e) throw ServiceSpecificException(KeystoreErrorCode.SYSTEM_ERROR, e.message)
} }
} }
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? { fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
checkActive() checkActive()
checkInputLength(data)
try { try {
val startNs = if (latencyFloorMs > 0) System.nanoTime() else 0L
val result = primitive.finish(data, signature) val result = primitive.finish(data, signature)
if (latencyFloorMs > 0) {
val elapsedMs = (System.nanoTime() - startNs) / 1_000_000
val delayMs = latencyFloorMs - elapsedMs
if (delayMs > 0) LockSupport.parkNanos(delayMs * 1_000_000)
}
finalized = true
onFinishCallback?.invoke()
SystemLogger.info("[SoftwareOp TX_ID: $txId] Finished operation successfully.") SystemLogger.info("[SoftwareOp TX_ID: $txId] Finished operation successfully.")
onFinishCallback?.invoke()
return result return result
} catch (e: ServiceSpecificException) { } catch (e: ServiceSpecificException) {
throw e throw e
} catch (e: Exception) { } catch (e: Exception) {
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to finish operation.", e) SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to finish operation.", e)
throw mapToServiceSpecificException(e) throw ServiceSpecificException(KeystoreErrorCode.SYSTEM_ERROR, e.message)
} finally {
finalized = true
} }
} }
fun abort() { fun abort() {
checkActive()
finalized = true finalized = true
primitive.abort() primitive.abort()
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Operation aborted.") SystemLogger.debug("[SoftwareOp TX_ID: $txId] Operation aborted.")
} }
}
private fun mapToServiceSpecificException(e: Exception): ServiceSpecificException = when (e) { /** Binder interface for [SoftwareOperation]. Synchronized and input-length validated. */
is SignatureException -> ServiceSpecificException(KeystoreErrorCodes.verificationFailed, e.message) class SoftwareOperationBinder(private val operation: SoftwareOperation) :
is javax.crypto.BadPaddingException -> ServiceSpecificException(KeystoreErrorCodes.invalidArgument, e.message) IKeystoreOperation.Stub() {
is javax.crypto.IllegalBlockSizeException -> ServiceSpecificException(KeystoreErrorCodes.invalidInputLength, e.message)
is java.security.InvalidKeyException -> ServiceSpecificException(KeystoreErrorCodes.incompatibleKey, e.message) private fun checkInputLength(data: ByteArray?) {
else -> ServiceSpecificException(KeystoreErrorCodes.unknownError, e.message) if (data != null && data.size > MAX_RECEIVE_DATA)
throw ServiceSpecificException(KeystoreErrorCode.TOO_MUCH_DATA)
}
@Throws(RemoteException::class)
override fun updateAad(aadInput: ByteArray?) {
synchronized(this) {
checkInputLength(aadInput)
operation.updateAad(aadInput)
}
}
@Throws(RemoteException::class)
override fun update(input: ByteArray?): ByteArray? {
synchronized(this) {
checkInputLength(input)
return operation.update(input)
}
}
@Throws(RemoteException::class)
override fun finish(input: ByteArray?, signature: ByteArray?): ByteArray? {
synchronized(this) {
checkInputLength(input)
checkInputLength(signature)
return operation.finish(input, signature)
}
}
@Throws(RemoteException::class)
override fun abort() {
synchronized(this) { operation.abort() }
} }
companion object { companion object {
private const val MAX_RECEIVE_DATA = 0x8000 private const val MAX_RECEIVE_DATA = 0x8000
} }
} }
internal object KeystoreErrorCodes {
val tooMuchData: Int by lazy {
resolveField("android.system.keystore2.ResponseCode", "TOO_MUCH_DATA", 21)
}
val invalidOperationHandle: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_OPERATION_HANDLE", -28)
}
val invalidTag: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_TAG", -76)
}
val verificationFailed: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "VERIFICATION_FAILED", -30)
}
val invalidArgument: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_ARGUMENT", -38)
}
val invalidInputLength: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_INPUT_LENGTH", -21)
}
val incompatibleKey: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INCOMPATIBLE_KEY", -31)
}
val incompatiblePurpose: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INCOMPATIBLE_PURPOSE", -13)
}
val unsupportedPurpose: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "UNSUPPORTED_PURPOSE", -14)
}
val incompatibleAlgorithm: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INCOMPATIBLE_ALGORITHM", -18)
}
val keyNotYetValid: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "KEY_NOT_YET_VALID", -39)
}
val keyExpired: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "KEY_EXPIRED", -40)
}
val callerNonceProhibited: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "CALLER_NONCE_PROHIBITED", -55)
}
val unknownError: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "UNKNOWN_ERROR", -1000)
}
fun resolveField(className: String, fieldName: String, fallback: Int): Int =
runCatching {
Class.forName(className).getField(fieldName).getInt(null)
}.getOrElse {
SystemLogger.debug("Resolved $className.$fieldName via fallback: $fallback")
fallback
}
}
class SoftwareOperationBinder(private val operation: SoftwareOperation) :
IKeystoreOperation.Stub() {
@Synchronized
override fun updateAad(aadInput: ByteArray?) {
operation.updateAad(aadInput)
}
@Synchronized
override fun update(input: ByteArray?): ByteArray? {
return operation.update(input)
}
@Synchronized
override fun finish(input: ByteArray?, signature: ByteArray?): ByteArray? {
return operation.finish(input, signature)
}
@Synchronized
override fun abort() {
operation.abort()
}
}
@@ -37,6 +37,22 @@ object KeyMintParameterLogger {
.associate { field -> (field.get(null) as Int) to field.name } .associate { field -> (field.get(null) as Int) to field.name }
} }
val hardwareAuthenticatorTypeNames: Map<Int, String> by lazy {
HardwareAuthenticatorType::class
.java
.fields
.filter { it.type == Int::class.java }
.associate { field -> (field.get(null) as Int) to field.name }
}
val keyOriginNames: Map<Int, String> by lazy {
KeyOrigin::class
.java
.fields
.filter { it.type == Int::class.java }
.associate { field -> (field.get(null) as Int) to field.name }
}
val paddingNames: Map<Int, String> by lazy { val paddingNames: Map<Int, String> by lazy {
PaddingMode::class PaddingMode::class
.java .java
@@ -81,22 +97,33 @@ object KeyMintParameterLogger {
when (param.tag) { when (param.tag) {
Tag.ALGORITHM -> algorithmNames[value.algorithm] Tag.ALGORITHM -> algorithmNames[value.algorithm]
Tag.BLOCK_MODE -> blockModeNames[value.blockMode] Tag.BLOCK_MODE -> blockModeNames[value.blockMode]
Tag.DIGEST -> digestNames[value.digest]
Tag.EC_CURVE -> ecCurveNames[value.ecCurve] Tag.EC_CURVE -> ecCurveNames[value.ecCurve]
Tag.ORIGIN -> keyOriginNames[value.origin]
Tag.PADDING -> paddingNames[value.paddingMode] Tag.PADDING -> paddingNames[value.paddingMode]
Tag.PURPOSE -> purposeNames[value.keyPurpose] Tag.PURPOSE -> purposeNames[value.keyPurpose]
Tag.DIGEST -> digestNames[value.digest] Tag.USER_AUTH_TYPE ->
hardwareAuthenticatorTypeNames[value.hardwareAuthenticatorType]
Tag.AUTH_TIMEOUT, Tag.AUTH_TIMEOUT,
Tag.BOOT_PATCHLEVEL,
Tag.KEY_SIZE, Tag.KEY_SIZE,
Tag.MIN_MAC_LENGTH -> value.integer.toString() Tag.MAC_LENGTH,
Tag.MIN_MAC_LENGTH,
Tag.OS_VERSION,
Tag.OS_PATCHLEVEL,
Tag.USER_ID,
Tag.VENDOR_PATCHLEVEL -> value.integer.toString()
Tag.CERTIFICATE_SERIAL -> BigInteger(value.blob).toString() Tag.CERTIFICATE_SERIAL -> BigInteger(value.blob).toString()
Tag.ACTIVE_DATETIME, Tag.ACTIVE_DATETIME,
Tag.CERTIFICATE_NOT_AFTER, Tag.CERTIFICATE_NOT_AFTER,
Tag.CERTIFICATE_NOT_BEFORE, Tag.CERTIFICATE_NOT_BEFORE,
Tag.CREATION_DATETIME,
Tag.ORIGINATION_EXPIRE_DATETIME, Tag.ORIGINATION_EXPIRE_DATETIME,
Tag.USAGE_EXPIRE_DATETIME -> Date(value.dateTime).toString() Tag.USAGE_EXPIRE_DATETIME -> Date(value.dateTime).toString()
Tag.CERTIFICATE_SUBJECT -> X500Name(X500Principal(value.blob).name).toString() Tag.CERTIFICATE_SUBJECT -> X500Name(X500Principal(value.blob).name).toString()
Tag.USER_SECURE_ID,
Tag.RSA_PUBLIC_EXPONENT -> value.longInteger.toString() Tag.RSA_PUBLIC_EXPONENT -> value.longInteger.toString()
Tag.NO_AUTH_REQUIRED -> "true" Tag.NO_AUTH_REQUIRED -> value.boolValue.toString()
Tag.ATTESTATION_CHALLENGE, Tag.ATTESTATION_CHALLENGE,
Tag.ATTESTATION_ID_BRAND, Tag.ATTESTATION_ID_BRAND,
Tag.ATTESTATION_ID_DEVICE, Tag.ATTESTATION_ID_DEVICE,
@@ -35,6 +35,7 @@ import org.matrix.TEESimulator.logging.SystemLogger
*/ */
object CertificateGenerator { object CertificateGenerator {
// RFC 5280 GeneralizedTime maximum: 9999-12-31T23:59:59 UTC (millis since epoch).
private const val UNDEFINED_NOT_AFTER = 253402300799000L private const val UNDEFINED_NOT_AFTER = 253402300799000L
/** /**
@@ -198,7 +199,9 @@ object CertificateGenerator {
private fun buildKeyUsageFromPurposes(purposes: List<Int>): Int { private fun buildKeyUsageFromPurposes(purposes: List<Int>): Int {
var bits = 0 var bits = 0
for (purpose in purposes) { for (purpose in purposes) {
bits = bits or when (purpose) { bits =
bits or
when (purpose) {
KeyPurpose.SIGN -> KeyUsage.digitalSignature KeyPurpose.SIGN -> KeyUsage.digitalSignature
KeyPurpose.DECRYPT -> KeyUsage.dataEncipherment KeyPurpose.DECRYPT -> KeyUsage.dataEncipherment
KeyPurpose.WRAP_KEY -> KeyUsage.keyEncipherment KeyPurpose.WRAP_KEY -> KeyUsage.keyEncipherment
@@ -220,6 +223,8 @@ object CertificateGenerator {
securityLevel: Int, securityLevel: Int,
): Certificate { ): Certificate {
val subject = params.certificateSubject ?: X500Name("CN=Android Keystore Key") val subject = params.certificateSubject ?: X500Name("CN=Android Keystore Key")
// Default validity: epoch to 9999-12-31T23:59:59 UTC (matches add_required_parameters).
val notBefore = params.certificateNotBefore ?: Date(0) val notBefore = params.certificateNotBefore ?: Date(0)
val notAfter = params.certificateNotAfter ?: Date(UNDEFINED_NOT_AFTER) val notAfter = params.certificateNotAfter ?: Date(UNDEFINED_NOT_AFTER)
@@ -243,11 +248,16 @@ object CertificateGenerator {
AttestationBuilder.buildAttestationExtension(params, uid, securityLevel) AttestationBuilder.buildAttestationExtension(params, uid, securityLevel)
) )
// The signature algorithm must match the SIGNING key, not the subject key.
// An EC attestation key may sign an RSA subject key's certificate (or vice versa).
val signerAlgorithm = val signerAlgorithm =
when (signingKeyPair.private.algorithm) { when (signingKeyPair.private) {
"EC", "ECDSA" -> "SHA256withECDSA" is java.security.interfaces.ECKey -> "SHA256withECDSA"
"RSA" -> "SHA256withRSA" is java.security.interfaces.RSAKey -> "SHA256withRSA"
else -> throw IllegalArgumentException("Unsupported signing key: ${signingKeyPair.private.algorithm}") else ->
throw IllegalArgumentException(
"Unsupported signing key type: ${signingKeyPair.private.javaClass}"
)
} }
val contentSigner = val contentSigner =
JcaContentSignerBuilder(signerAlgorithm) JcaContentSignerBuilder(signerAlgorithm)
@@ -91,33 +91,27 @@ object AndroidDeviceUtils {
attestationValueProvider: () -> ByteArray?, attestationValueProvider: () -> ByteArray?,
expectedSize: Int, expectedSize: Int,
): ByteArray { ): ByteArray {
// 1. Attempt to get the value from the system property.
getProperty(propertyName, expectedSize)?.let { getProperty(propertyName, expectedSize)?.let {
SystemLogger.debug("Using $propertyName from system property: ${it.toHex()}") SystemLogger.debug("Using $propertyName from system property: ${it.toHex()}")
persistToFile(propertyName, it)
return it return it
} }
// 2. Fallback to the value from a cached TEE attestation.
try { try {
attestationValueProvider()?.let { attestationValueProvider()?.let {
SystemLogger.debug("Using $propertyName from TEE attestation: ${it.toHex()}") SystemLogger.debug("Using $propertyName from TEE attestation: ${it.toHex()}")
setProperty(propertyName, it) setProperty(propertyName, it) // Persist for consistency
persistToFile(propertyName, it)
return it return it
} }
} catch (e: Exception) { } catch (e: Exception) {
SystemLogger.error("Failed to get $propertyName from attestation.", e) SystemLogger.error("Failed to get $propertyName from attestation.", e)
} }
readFromFile(propertyName, expectedSize)?.let { // 3. As a final fallback, generate a random value.
SystemLogger.debug("Using $propertyName from persistent file: ${it.toHex()}")
setProperty(propertyName, it)
return it
}
return generateRandomBytes(expectedSize).also { return generateRandomBytes(expectedSize).also {
SystemLogger.debug("Using randomly generated $propertyName: ${it.toHex()}") SystemLogger.debug("Using randomly generated $propertyName: ${it.toHex()}")
setProperty(propertyName, it) setProperty(propertyName, it)
persistToFile(propertyName, it)
} }
} }
@@ -164,37 +158,10 @@ object AndroidDeviceUtils {
} }
} }
/** Generates a cryptographically random byte array of a specified length. */
private fun generateRandomBytes(size: Int): ByteArray = private fun generateRandomBytes(size: Int): ByteArray =
ByteArray(size).also { ThreadLocalRandom.current().nextBytes(it) } ByteArray(size).also { ThreadLocalRandom.current().nextBytes(it) }
private val PERSIST_DIR = File("/data/adb/tricky_store")
private fun fileForProperty(propertyName: String): File = when (propertyName) {
"ro.boot.vbmeta.digest" -> File(PERSIST_DIR, "boot_hash.bin")
"ro.boot.vbmeta.public_key_digest" -> File(PERSIST_DIR, "boot_key.bin")
else -> File(PERSIST_DIR, "${propertyName.replace('.', '_')}.bin")
}
private fun persistToFile(propertyName: String, bytes: ByteArray) {
try {
fileForProperty(propertyName).writeBytes(bytes)
} catch (e: Exception) {
SystemLogger.error("Failed to persist $propertyName to file.", e)
}
}
private fun readFromFile(propertyName: String, expectedSize: Int): ByteArray? {
return try {
val file = fileForProperty(propertyName)
if (!file.exists()) return null
val bytes = file.readBytes()
if (bytes.size == expectedSize) bytes else null
} catch (e: Exception) {
SystemLogger.error("Failed to read $propertyName from file.", e)
null
}
}
// --- Patch Level Properties --- // --- Patch Level Properties ---
fun getPatchLevel(uid: Int): Int { fun getPatchLevel(uid: Int): Int {
@@ -273,12 +240,11 @@ object AndroidDeviceUtils {
val resolvedValue = resolveDateKeywords(value) val resolvedValue = resolveDateKeywords(value)
return when { return when {
// "device_default" indicates falling back to the system property.
resolvedValue.equals("device_default", ignoreCase = true) -> null resolvedValue.equals("device_default", ignoreCase = true) -> null
// Resolve from live system prop — matches what detectors see via getprop, // "no" indicates this value should not be reported.
// even when PIF has spoofed ro.build.version.security_patch via resetprop
resolvedValue.equals("prop", ignoreCase = true) ->
parsePatchLevelValue(SystemProperties.get("ro.build.version.security_patch", ""), isLong)
resolvedValue.equals("no", ignoreCase = true) -> DO_NOT_REPORT resolvedValue.equals("no", ignoreCase = true) -> DO_NOT_REPORT
// Otherwise, parse the resolved date string.
else -> parsePatchLevelValue(resolvedValue, isLong) else -> parsePatchLevelValue(resolvedValue, isLong)
} }
} }
@@ -405,7 +371,10 @@ object AndroidDeviceUtils {
// --- APEX and Module Hash Properties --- // --- APEX and Module Hash Properties ---
// Minimal protobuf parser for apex_manifest.pb (field 1: name, field 2: version) // https://cs.android.com/android/platform/superproject/+/android-latest-release:system/apex/proto/apex_manifest.proto
// --- Minimal Protobuf Parser for ApexManifest ---
// Field 1: name (string)
// Field 2: version (int64)
private class MinimalApexManifestParser(private val data: ByteArray) { private class MinimalApexManifestParser(private val data: ByteArray) {
var pos = 0 var pos = 0
@@ -419,13 +388,13 @@ object AndroidDeviceUtils {
val wireType = (tag and 0x07).toInt() val wireType = (tag and 0x07).toInt()
when (fieldNum) { when (fieldNum) {
1L -> { 1L -> { // name
val length = readVarint().toInt() val length = readVarint().toInt()
if (pos + length > data.size) return null if (pos + length > data.size) return null
name = String(data, pos, length, Charsets.UTF_8) name = String(data, pos, length, Charsets.UTF_8)
pos += length pos += length
} }
2L -> { 2L -> { // version
version = readVarint() version = readVarint()
} }
else -> skipField(wireType) else -> skipField(wireType)
@@ -453,18 +422,19 @@ object AndroidDeviceUtils {
private fun skipField(wireType: Int) { private fun skipField(wireType: Int) {
when (wireType) { when (wireType) {
0 -> readVarint() 0 -> readVarint() // Varint
1 -> pos += 8 1 -> pos += 8 // 64-bit
2 -> { 2 -> { // Length-delimited
val len = readVarint().toInt() val len = readVarint().toInt()
pos += len pos += len
} }
5 -> pos += 4 5 -> pos += 4 // 32-bit
else -> throw IllegalStateException("Unknown wire type $wireType") else -> throw IllegalStateException("Unknown wire type $wireType")
} }
} }
} }
// https://cs.android.com/android/platform/superproject/main/+/main:system/apex/libs/libapexutil/apexutil.cpp
private val apexInfos: List<Pair<String, Long>> by lazy { private val apexInfos: List<Pair<String, Long>> by lazy {
val results = mutableListOf<Pair<String, Long>>() val results = mutableListOf<Pair<String, Long>>()
val apexRoot = File("/apex") val apexRoot = File("/apex")
@@ -473,14 +443,22 @@ object AndroidDeviceUtils {
return@lazy emptyList() return@lazy emptyList()
} }
// Logic from: GetActivePackages in apexutil.cpp
apexRoot.listFiles()?.forEach { file -> apexRoot.listFiles()?.forEach { file ->
if (!file.isDirectory) return@forEach if (!file.isDirectory) return@forEach
val name = file.name val name = file.name
// 1. Ignore "." (and implicitly "..")
if (name.startsWith(".")) return@forEach if (name.startsWith(".")) return@forEach
// 2. Ignore directories containing '@' (active mounts usually don't have version in
// path)
if (name.contains("@")) return@forEach if (name.contains("@")) return@forEach
// 3. Ignore "sharedlibs"
if (name == "sharedlibs") return@forEach if (name == "sharedlibs") return@forEach
// 4. Parse apex_manifest.pb
val manifestFile = File(file, "apex_manifest.pb") val manifestFile = File(file, "apex_manifest.pb")
if (manifestFile.exists()) { if (manifestFile.exists()) {
runCatching { runCatching {
@@ -491,46 +469,59 @@ object AndroidDeviceUtils {
} }
} }
// Ensure uniqueness (though filesystem scan usually prevents exact dupes,
// strictly speaking we want to behave like a Map keyed by package name)
results.distinctBy { it.first } results.distinctBy { it.first }
} }
// https://cs.android.com/android/platform/superproject/main/+/main:system/security/keystore2/src/maintenance.rs
val moduleHash: ByteArray by lazy { val moduleHash: ByteArray by lazy {
DeviceAttestationService.CachedAttestationData?.moduleHash DeviceAttestationService.CachedAttestationData?.moduleHash
?: runCatching { ?: runCatching {
// 1. Create a container to hold the sort key (name encoded) and the full data
// (sequence encoded)
data class ModuleEntry( data class ModuleEntry(
val nameEncoded: ByteArray, val nameEncoded: ByteArray, // The sort key
val fullEncoded: ByteArray, val fullEncoded: ByteArray, // The data to hash
) )
val modules = val modules =
apexInfos.map { (packageName, versionCode) -> apexInfos.map { (packageName, versionCode) ->
// Create the components
val nameOctet = DEROctetString(packageName.toByteArray(Charsets.UTF_8)) val nameOctet = DEROctetString(packageName.toByteArray(Charsets.UTF_8))
val versionInt = ASN1Integer(versionCode) val versionInt = ASN1Integer(versionCode)
// Create the Sequence: SEQUENCE { packageName, version }
val vec = ASN1EncodableVector() val vec = ASN1EncodableVector()
vec.add(nameOctet) vec.add(nameOctet)
vec.add(versionInt) vec.add(versionInt)
val sequence = DERSequence(vec) val sequence = DERSequence(vec)
// AOSP sorts by encoded name only, not full sequence // We store the encoded name separately because Rust sorts ONLY by this
ModuleEntry( ModuleEntry(
nameEncoded = nameOctet.encoded, nameEncoded = nameOctet.encoded,
fullEncoded = sequence.encoded, fullEncoded = sequence.encoded,
) )
} }
// 2. Sort manually based on the encoded Package Name (lexicographically)
// This mimics the Rust 'impl DerOrd for ModuleInfo' which delegates to
// 'self.name'
val sortedModules = val sortedModules =
modules.sortedWith { m1, m2 -> modules.sortedWith { m1, m2 ->
compareByteArrays(m1.nameEncoded, m2.nameEncoded) compareByteArrays(m1.nameEncoded, m2.nameEncoded)
} }
// 3. Concatenate the full sequences in the specific sorted order
val payloadStream = ByteArrayOutputStream() val payloadStream = ByteArrayOutputStream()
sortedModules.forEach { payloadStream.write(it.fullEncoded) } sortedModules.forEach { payloadStream.write(it.fullEncoded) }
val payload = payloadStream.toByteArray() val payload = payloadStream.toByteArray()
// Wrap in DER SET tag manually — DERSet() re-sorts by full encoding // 4. Wrap manually in a DER SET tag (0x31)
// We cannot use DERSet(vector) because it would re-sort incorrectly.
val finalDerSet = encodeAsDerSet(payload) val finalDerSet = encodeAsDerSet(payload)
// 5. Compute SHA-256
MessageDigest.getInstance("SHA-256").digest(finalDerSet) MessageDigest.getInstance("SHA-256").digest(finalDerSet)
} }
.getOrElse { .getOrElse {
@@ -539,6 +530,7 @@ object AndroidDeviceUtils {
} }
} }
/** Compares two byte arrays lexicographically (unsigned). */
private fun compareByteArrays(a: ByteArray, b: ByteArray): Int { private fun compareByteArrays(a: ByteArray, b: ByteArray): Int {
val length = minOf(a.size, b.size) val length = minOf(a.size, b.size)
for (i in 0 until length) { for (i in 0 until length) {
@@ -551,25 +543,31 @@ object AndroidDeviceUtils {
return a.size - b.size return a.size - b.size
} }
/** Manually wraps the payload in an ASN.1 SET (0x31) tag with correct length encoding. */
private fun encodeAsDerSet(payload: ByteArray): ByteArray { private fun encodeAsDerSet(payload: ByteArray): ByteArray {
val out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
out.write(0x31) out.write(0x31) // ASN.1 Tag for SET
writeDerLength(out, payload.size) writeDerLength(out, payload.size)
out.write(payload) out.write(payload)
return out.toByteArray() return out.toByteArray()
} }
/** Writes the ASN.1 length field to the stream. */
private fun writeDerLength(out: ByteArrayOutputStream, length: Int) { private fun writeDerLength(out: ByteArrayOutputStream, length: Int) {
if (length < 128) { if (length < 128) {
// Short form
out.write(length) out.write(length)
} else { } else {
// Long form
var size = length var size = length
val bytes = ArrayList<Byte>() val bytes = ArrayList<Byte>()
while (size > 0) { while (size > 0) {
bytes.add((size and 0xFF).toByte()) bytes.add((size and 0xFF).toByte())
size = size ushr 8 size = size ushr 8
} }
// First byte: 0x80 | number of length bytes
out.write(0x80 or bytes.size) out.write(0x80 or bytes.size)
// Write length bytes in big-endian (reverse of how we extracted them)
for (i in bytes.indices.reversed()) { for (i in bytes.indices.reversed()) {
out.write(bytes[i].toInt()) out.write(bytes[i].toInt())
} }
@@ -1,72 +0,0 @@
package org.matrix.TEESimulator.util
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import org.matrix.TEESimulator.logging.SystemLogger
object AndroidPermissionUtils {
@SuppressLint("PrivateApi", "DiscouragedPrivateApi")
private fun getGlobalContext(): Context? {
return try {
// 1. Get the hidden ActivityThread class via reflection
val activityThreadClass = Class.forName("android.app.ActivityThread")
// 2. Invoke the static currentActivityThread() method
val currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread")
currentActivityThreadMethod.isAccessible = true
val activityThread = currentActivityThreadMethod.invoke(null)
if (activityThread == null) {
SystemLogger.warning("Reflection: ActivityThread.currentActivityThread() returned null")
return null
}
// 3. Try to get the application context
val getApplicationMethod = activityThreadClass.getDeclaredMethod("getApplication")
getApplicationMethod.isAccessible = true
val application = getApplicationMethod.invoke(activityThread) as? Context
if (application != null) return application
// 4. Fallback to getSystemContext() if application is null (often happens in system_server)
val getSystemContextMethod = activityThreadClass.getDeclaredMethod("getSystemContext")
getSystemContextMethod.isAccessible = true
getSystemContextMethod.invoke(activityThread) as? Context
} catch (e: Exception) {
SystemLogger.error("Reflection failed to get global context for permission check", e)
null
}
}
/**
* Core permission check.
*/
fun hasPermission(uid: Int, permission: String): Boolean {
val context = getGlobalContext() ?: run {
SystemLogger.warning("AndroidPermissionUtils: Context is null, failing permission check safely.")
return false
}
val result = context.checkPermission(permission, -1, uid)
return result == PackageManager.PERMISSION_GRANTED
}
fun hasDeviceAttestationPermission(uid: Int): Boolean {
return hasPermission(uid, "android.permission.READ_PRIVILEGED_PHONE_STATE")
}
fun hasUniqueIdAttestationPermission(uid: Int): Boolean {
return hasPermission(uid, "android.permission.REQUEST_UNIQUE_ID_ATTESTATION")
}
fun hasManageUsersPermission(uid: Int): Boolean {
return hasPermission(uid, "android.permission.MANAGE_USERS")
}
fun hasDumpPermission(uid: Int): Boolean {
return hasPermission(uid, "android.permission.DUMP")
}
}
@@ -8,6 +8,21 @@ import kotlin.math.exp
import kotlin.math.ln import kotlin.math.ln
import kotlin.math.max import kotlin.math.max
/**
* Simulates realistic TEE hardware latency for software key generation.
*
* The delay model is derived from 64+ timing measurements across QTEE (Qualcomm) and Trustonic
* (MediaTek) hardware. It combines four independent noise sources that model different physical
* latency origins in a real TrustZone-based TEE:
*
* 1. Base crypto processing (log-normal): hardware RNG + key derivation + cert signing
* 2. Binder/kernel transit (exponential): IPC scheduling, context switches
* 3. TrustZone scheduler jitter (Gaussian): world-switch non-determinism
* 4. Cold-start penalty (half-normal): first operation after idle is slower due to TEE
* secure world re-initialization and TLB/cache warming
*
* Per-boot session bias models manufacturing variance between TEE hardware instances.
*/
object TeeLatencySimulator { object TeeLatencySimulator {
private val rng = SecureRandom() private val rng = SecureRandom()
@@ -41,6 +56,11 @@ object TeeLatencySimulator {
return max(20.0, base + transit + jitter + sessionBiasMs + cold) return max(20.0, base + transit + jitter + sessionBiasMs + cold)
} }
/**
* Log-normal base delay. Parameters tuned to match observed hardware profiles:
* EC P-256 on QTEE averages ~65ms, RSA-2048 ~75ms, AES ~40ms.
* Sigma kept low (0.08) to match the tight clustering seen in real measurements.
*/
private fun sampleBaseCryptoDelay(algorithm: Int): Double { private fun sampleBaseCryptoDelay(algorithm: Int): Double {
val (mu, sigma) = val (mu, sigma) =
when (algorithm) { when (algorithm) {
+3 -6
View File
@@ -15,7 +15,7 @@ fi
# --- Version Info --- # --- Version Info ---
VERSION=$(grep_prop version "${TMPDIR}/module.prop") VERSION=$(grep_prop version "${TMPDIR}/module.prop")
ui_print "- Installing TEESimulator-RS $VERSION" ui_print "- Installing TEESimulator $VERSION"
ui_print "" ui_print ""
# --- Architecture Handling --- # --- Architecture Handling ---
@@ -48,7 +48,7 @@ install_file() {
# --- Installation --- # --- Installation ---
ui_print "- Extracting module files" ui_print "- Extracting module files"
for file in customize.sh module.prop service.sh sepolicy.rule daemon action.sh uninstall.sh; do for file in customize.sh module.prop service.sh sepolicy.rule daemon; do
install_file "$file" "$MODPATH" install_file "$file" "$MODPATH"
done done
@@ -67,14 +67,10 @@ ui_print ""
ui_print "- Extracting $ARCH libraries" ui_print "- Extracting $ARCH libraries"
install_file "lib/$ABI_DIR/libTEESimulator.so" "$MODPATH" install_file "lib/$ABI_DIR/libTEESimulator.so" "$MODPATH"
install_file "lib/$ABI_DIR/libinject.so" "$MODPATH" install_file "lib/$ABI_DIR/libinject.so" "$MODPATH"
install_file "lib/$ABI_DIR/libsupervisor.so" "$MODPATH"
install_file "lib/$ABI_DIR/libcertgen.so" "$MODPATH"
ui_print "" ui_print ""
mv "$MODPATH/libinject.so" "$MODPATH/inject" mv "$MODPATH/libinject.so" "$MODPATH/inject"
mv "$MODPATH/libsupervisor.so" "$MODPATH/supervisor"
chmod 755 "$MODPATH/inject" chmod 755 "$MODPATH/inject"
chmod 755 "$MODPATH/supervisor"
# --- Configuration Files --- # --- Configuration Files ---
if [ ! -d "$CONFIG_DIR" ]; then if [ ! -d "$CONFIG_DIR" ]; then
@@ -92,6 +88,7 @@ if [ ! -f "$CONFIG_DIR/target.txt" ]; then
install_file "target.txt" "$CONFIG_DIR" install_file "target.txt" "$CONFIG_DIR"
fi fi
# Remove legacy TEE status file; TEE status is now determined at runtime.
rm -f "$CONFIG_DIR/tee_status.txt" rm -f "$CONFIG_DIR/tee_status.txt"
if [ ! -f "$CONFIG_DIR/hbk" ]; then if [ ! -f "$CONFIG_DIR/hbk" ]; then
@@ -0,0 +1,8 @@
package android.hardware.security.keymint;
public @interface HardwareAuthenticatorType {
int NONE = 0;
int PASSWORD = 1;
int FINGERPRINT = 2;
int ANY = -1;
}
@@ -1,5 +1,6 @@
package android.os; package android.os;
/** Stub for android.os.SELinux. */
public class SELinux { public class SELinux {
public static boolean checkSELinuxAccess( public static boolean checkSELinuxAccess(
String scon, String tcon, String tclass, String perm) { String scon, String tcon, String tclass, String perm) {
@@ -1,14 +1,21 @@
package android.os; package android.os;
/**
* Stub for android.os.ServiceSpecificException.
*
* <p>Used by AIDL-generated binder stubs to report service-specific errors with numeric codes.
* The binder framework serializes this as EX_SERVICE_SPECIFIC on the wire, preserving the integer
* error code for the client.
*/
public class ServiceSpecificException extends RuntimeException { public class ServiceSpecificException extends RuntimeException {
public final int errorCode; public final int errorCode;
public ServiceSpecificException(int errorCode) {
this.errorCode = errorCode;
}
public ServiceSpecificException(int errorCode, String message) { public ServiceSpecificException(int errorCode, String message) {
super(message); super(message);
this.errorCode = errorCode; this.errorCode = errorCode;
} }
public ServiceSpecificException(int errorCode) {
this(errorCode, null);
}
} }