The INCLUDE_UNIQUE_ID gate in handleGenerateKey (introduced as part of the
PR157 AOSP-compliance work) returns PERMISSION_DENIED when the caller
holds neither SELinux gen_unique_id nor REQUEST_UNIQUE_ID_ATTESTATION.
This breaks Google Wallet card binding on real devices: Wallet's
generateKey carries INCLUDE_UNIQUE_ID without holding the permission, so
its attestation key request is rejected and Wallet surfaces the failure
as "this phone does not meet the security requirements for Google
Wallet". Symptoms reported by users: clearing GMS data only helps for a
few seconds before the state regresses; no card can be added.
Naive removal of the gate is not safe: AttestationBuilder honours
`includeUniqueId == true` by computing an HMAC-SHA256 unique_id and
embedding it in the attestation extension. With the gate gone, GMS
attestation flows that include the tag end up with a unique_id in the
extension that Play Integrity flags as inconsistent for the caller,
turning all three integrity verdicts red.
The fix here splits the difference: when the permission check fails,
silently strip the INCLUDE_UNIQUE_ID tag from the KeyParameter array
(and re-parse `parsedParams`) instead of rejecting the request. The key
generates normally, AttestationBuilder takes the
`else { ByteArray(0) }` branch, and the resulting attestation simply
omits the unique_id field — matching pre-PR157 behaviour, where the
tag effectively had no effect.
Verified on a device that previously failed Wallet binding on the
PR157 baseline:
- Play Integrity: BASIC + DEVICE + STRONG all pass.
- Google Wallet: card binding completes successfully.
- Calls that DO hold the permission are unaffected (still emit
unique_id as before).
The rest of the PR157 compliance work (CALLER_NONCE handling,
AuthorizeCreate ordering, USAGE_COUNT_LIMIT counters, effectiveParams
merging) is preserved.
TEESimulator-RS
Full TEE Emulation for Rooted Android
Note
Fork of JingMatrix/TEESimulator with native Rust certificate generation, key persistence, and AOSP-compliant attestation behavior. For the upstream project, see the original repo.
What It Does
TEESimulator intercepts Binder IPC at the ioctl level inside the keystore2 process and generates entire certificate chains from scratch, signed by your keybox, with correct attestation extensions. Apps that verify hardware attestation see a legitimate device.
This is not TrickyStore. TEESimulator replaces TrickyStore and its forks entirely. It shares the same config paths for drop-in compatibility, but the internals are different: native Rust cert generation, binder-level interception via lsplt, per-UID rate limiting, key persistence, and AOSP-spec attestation behavior.
Requirements
Important
A valid
keybox.xmlis required for hardware-level attestation. Without one, the module generates software-level certificates that won't pass strict hardware checks.
- Android 10+
- Root manager: KernelSU, Magisk, or APatch
keybox.xmlat/data/adb/tricky_store/keybox.xml
Quick Start
- Download the latest ZIP from Releases
- Install via your root manager and reboot
- Place your keybox at
/data/adb/tricky_store/keybox.xml - Configure targets in
/data/adb/tricky_store/target.txt - Verify with Play Integrity or Key Attestation Demo
Architecture
Native Cert Generation — libcertgen.so generates X.509 chains in Rust using ring and manual DER encoding. BouncyCastle fallback for unsupported curves (P-224, P-521, Curve25519).
Binder Interception — PLT hook on ioctl() in libc.so via lsplt inside keystore2. Intercepts generateKey, importKey, and getKeyEntry transactions.
AOSP Compliance — Self-signed certs for non-attested keys (matching ta/src/keys.rs), correct AuthorizationList tag ordering, version-guarded extension fields, authorize_create enforcement.
Key Persistence — Generated keys survive reboots. File-backed with file-level locking.
Rate Limiting — Per-UID hardware keygen cap (2/30s window, 2 concurrent). Overflow falls to software certs.
Configuration
All config files live at /data/adb/tricky_store/ and are hot-reloaded via FileObserver.
target.txt
Controls which apps get intercepted and the simulation mode.
| Suffix | Mode |
|---|---|
! |
Force software key generation |
? |
Force leaf certificate patching (real TEE key, patched cert) |
| (none) | Automatic selection |
Multi-keybox support via [filename.xml] headers:
com.google.android.gms!
io.github.vvb2060.keyattestation?
[aosp_keybox.xml]
com.google.android.gsf
security_patch.txt
Override patch levels reported in attestation certificates. Global defaults at top, per-package overrides with [package.name].
| Key | Scope |
|---|---|
system |
OS patch level |
vendor |
Vendor patch level |
boot |
Boot/kernel patch level |
all |
Sets all three |
Special values: today, YYYY-MM-DD templates, no (omit tag), device_default, prop (read from system property).
system=YYYY-MM-05
vendor=device_default
boot=no
[com.google.android.gms]
system=2025-10-01
Building from Source
Prerequisites: JDK 21, Android SDK/NDK 27, Rust stable with aarch64-linux-android target, cargo-ndk.
git clone --recursive https://github.com/Enginex0/TEESimulator-RS.git
cd TEESimulator-RS
./gradlew zipRelease zipDebug
Output ZIPs in out/. Gradle invokes cargo ndk automatically to cross-compile libcertgen.so.
Push to main or use Actions > Build > Run workflow to trigger CI.
Compatibility
| Root Manager | Status |
|---|---|
| KernelSU | Tested (Action button + lifecycle scripts) |
| Magisk | Supported |
| APatch | Supported |
Community
Credits
- JingMatrix — original TEESimulator and interception architecture
- 5ec1cff — TrickyStore, the project that pioneered keystore interception
- LSPlt — PLT hook library
- ring — Rust cryptography library
- MhmRdd — AOSP compliance work via upstream PR #157
- fatalcoder524 — contributor and collaborator
- huguangares — collaborator and tester